Skip to content

tokens_cache_read stays 0 for OpenAI-compatible providers even when usage.prompt_tokens_details.cached_tokens is returned #33997

Closed

Description

@Svtter

Summary

session.tokens_cache_read (and the per-prompt info.tokens.cache.read) is never populated for OpenAI-compatible providers, even when the upstream returns usage.prompt_tokens_details.cached_tokens. The cache hit is real and billed at the discount rate by the upstream, but opencode attributes 0 cache-read tokens, so cache savings are invisible in the cost/usage UI and in the SDK info.tokens.

Confirmed for a litellm proxy fronting DeepSeek. Native OpenAI, Anthropic, and others appear unaffected (see contrast below).

Environment

  • opencode 1.17.11
  • Provider configured via the OpenAI-compatible route (npm: "@ai-sdk/openai", custom baseURL), e.g. a litellm proxy at https://…/v1 fronting deepseek-v4-flash.

Repro

  1. Point opencode at an OpenAI-compatible endpoint that returns prompt_tokens_details.cached_tokens (litellm → DeepSeek does).
  2. Using the SDK, create a session and send two prompts so turn 2 reuses turn 1's prefix (DeepSeek auto-caches the shared prefix).
const s = await client.session.create();
const long = "stable-context-block. ".repeat(400); // >1024 tokens
await client.session.prompt({ path: { id: s.data.id }, body: { parts: [{ type: "text", text: long + " Reply: OK1" }] } });
const p2 = await client.session.prompt({ path: { id: s.data.id }, body: { parts: [{ type: "text", text: "Reply: OK2" }] } });
console.log(p2.data?.info?.tokens?.cache_read); // → undefined
  1. Inspect the session row: tokens_cache_read = 0.

Evidence the upstream IS caching and reporting it

Same endpoint, same prefix, direct curl — both non-streaming and streaming return the cache hit:

# streaming final chunk (what opencode actually consumes), stream_options.include_usage=true
data: {"usage":{"prompt_tokens":6004,"prompt_tokens_details":{"cached_tokens":5888}, …}}

cached_tokens: 5888 of 6004 prompt tokens (~98%) is present in the SSE usage chunk.

What the code already does correctly

  • packages/llm/src/protocols/openai-chat.ts mapUsage maps it:
    const cached = usage.prompt_tokens_details?.cached_tokens
    return new Usage({ …, cacheReadInputTokens: cached, … })
  • openai-compatible-chat.ts reuses OpenAIChat.protocol end-to-end (protocol: OpenAIChat.protocol), so it inherits that mapping.
  • packages/core/src/session/runner/publish-llm-event.ts reads usage?.cacheReadInputTokens → cache.read, and projector.ts writes tokens_cache_read.

So the mapping exists; yet for the OpenAI-compatible route the value lands as 0 in the session.

Contrast (works elsewhere)

For Anthropic / Zhipu (glm) / MiniMax the same field is populated correctly — e.g. a local opencode DB shows glm-5.1 with 1.07 billion tokens_cache_read across 397 sessions, and a live 2-turn probe there reports non-zero cache.read. Only the OpenAI-compatible path records 0 under the same conditions.

Suspected area

Something between openai-chat.ts mapUsage (which sets cacheReadInputTokens) and the session's tokens_cache_read drops the value specifically on the OpenAI-compatible route — possibly the streaming parser not retaining usage from the final SSE chunk before mapUsage runs, or the event-publication path. I wasn't able to pin the exact line from source alone, but the repro + evidence above localizes it.

Impact

Any OpenAI-compatible provider (litellm, openrouter, vLLM, Ollama's compat endpoint, LM Studio, etc.) that returns prompt_tokens_details.cached_tokens gets zero cache attribution in opencode. Token-cache savings from session resume / long conversations are silently unmeasured.

Happy to follow up with a tighter bisection or a PR if you can point me at where the OpenAI-compatible route's streaming usage is finalized.

🤖 Generated with Claude Code

Activity

  1. github-actions commented on Jun 26, 2026

    @github-actions
    Contributor

    This issue doesn't fully meet our contributing guidelines.

    What needs to be fixed:

    • No official issue template was used. Please use one of the three required templates: Bug Report, Feature Request, or Question.
    • The issue appears to be AI-generated (explicitly noted with "🤖 Generated with Claude Code"). AI-generated walls of text are not acceptable — please rewrite this in your own words with the relevant details.

    Please edit this issue to address the above within 2 hours, or it will be automatically closed.


    This issue might be a duplicate of existing issues. Please check:

    If you believe this was flagged incorrectly, please let a maintainer know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs:complianceThis means the issue will auto-close after 2 hours.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions