OpenRouter reports per-request USD cost in usage.cost. We already parse it and record it on the llm.request span as llm.cost_usd (src/ai/openrouter.rs), but we don't return it to the caller. LlmClient::chat_with_tools returns (ChatMessage, Option<i32> prompt_tokens, Option<i32> completion_tokens) — no cost.
Surface cost to clients so a client can display/report per-turn spend.
Design
Cost is OpenRouter-only. llama-swap / Ollama don't report per-request USD, so their impls return None for cost.
A turn = many calls. The agentic loop makes one chat_with_tools call per iteration, so the response should carry the summed cost across all iterations of the turn (tokens likewise).
Shape. Prefer a small struct over a growing tuple:
## Summary
OpenRouter reports per-request USD cost in `usage.cost`. We already parse it and record it on the `llm.request` span as `llm.cost_usd` (`src/ai/openrouter.rs`), but we don't return it to the caller. `LlmClient::chat_with_tools` returns `(ChatMessage, Option<i32> prompt_tokens, Option<i32> completion_tokens)` — no cost.
Surface cost to clients so a client can display/report per-turn spend.
## Design
- **Cost is OpenRouter-only.** llama-swap / Ollama don't report per-request USD, so their impls return `None` for cost.
- **A turn = many calls.** The agentic loop makes one `chat_with_tools` call per iteration, so the response should carry the **summed** cost across all iterations of the turn (tokens likewise).
- **Shape.** Prefer a small struct over a growing tuple:
```rust
struct Usage {
prompt_tokens: Option<i32>,
completion_tokens: Option<i32>,
cost_usd: Option<f64>,
}
```
and return `Result<(ChatMessage, Usage)>` from `chat_with_tools` (and the stream's terminal `Done`).
## Ripples
- `LlmClient` trait (`src/ai/llm_client.rs`) + 3 impls (openrouter, llamacpp, ollama) + inherent helpers (`describe_image`).
- ~11 `chat_with_tools` call sites (insight_generator, insight_chat, persona_chat, ...).
- Agentic loop accumulates `Usage`; add `cost_usd` (and token totals) to the chat/insight response JSON.
## Source
PR #129 review comment 1794 (cameron).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
OpenRouter reports per-request USD cost in
usage.cost. We already parse it and record it on thellm.requestspan asllm.cost_usd(src/ai/openrouter.rs), but we don't return it to the caller.LlmClient::chat_with_toolsreturns(ChatMessage, Option<i32> prompt_tokens, Option<i32> completion_tokens)— no cost.Surface cost to clients so a client can display/report per-turn spend.
Design
Cost is OpenRouter-only. llama-swap / Ollama don't report per-request USD, so their impls return
Nonefor cost.A turn = many calls. The agentic loop makes one
chat_with_toolscall per iteration, so the response should carry the summed cost across all iterations of the turn (tokens likewise).Shape. Prefer a small struct over a growing tuple:
and return
Result<(ChatMessage, Usage)>fromchat_with_tools(and the stream's terminalDone).Ripples
LlmClienttrait (src/ai/llm_client.rs) + 3 impls (openrouter, llamacpp, ollama) + inherent helpers (describe_image).chat_with_toolscall sites (insight_generator, insight_chat, persona_chat, ...).Usage; addcost_usd(and token totals) to the chat/insight response JSON.Source
PR #129 review comment 1794 (cameron).