AI: surface OpenRouter per-request cost to clients (usage.cost) #131

Open
opened 2026-09-29 01:52:09 +00:00 by cameron · 0 comments
Owner

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:

    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).

## 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).
Sign in to join this conversation.