Skip to content

DeepSeek provider: prompt_cache_miss_tokens not mapped to cache.write #35974

Closed as not planned

Description

@22xuan

Problem

The DeepSeek API returns two cache fields in every response:

  • prompt_cache_hit_tokens — tokens served from disk KV cache
  • prompt_cache_miss_tokens — tokens that missed the cache

opencode correctly maps prompt_cache_hit_tokens → cache.read, but prompt_cache_miss_tokens is never recorded, resulting in cache.write: 0 for every single message.

Steps to reproduce

  1. Use deepseek provider (any model)
  2. Make multiple requests in the same session (multi-turn conversation)
  3. Check stats: opencode stats --models
  4. Check per-message data: opencode db "SELECT json_extract(data, '$.tokens.cache.read'), json_extract(data, '$.tokens.cache.write') FROM message WHERE json_extract(data, '$.role')='assistant'" --format tsv

Expected

cache.write should equal prompt_cache_miss_tokens from the DeepSeek API response, enabling calculation of cache hit rate as hit / (hit + miss).

Actual

cache.write is always 0 for every message. Cache read (hit) works fine.

Impact

Cannot calculate cache hit rate. The opencode stats display shows Cache Write: 0 which is misleading.

Environment

  • opencode v1.17.15
  • deepseek/deepseek-v4-pro, deepseek/deepseek-v4-flash
  • Linux (WSL2 Ubuntu 22.04)

Activity

  1. github-actions commented on Jul 8, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of or related to an existing issue. Please check:

  2. Zaczero commented on Aug 18, 2026

    @Zaczero

    This is the same gap as #36749, in the same function, and the two are one patch rather than two.

    On V2 (0.0.0-beta-17498) the DeepSeek route goes through the shared OpenAI Chat protocol —
    openai-compatible-chat.ts reuses OpenAIChat.protocol end to end — and the mapping reads cache
    counts only from prompt_tokens_details:

    const mapUsage = (usage: OpenAIChatEvent["usage"]): Usage | undefined => {
    if (!usage) return undefined
    const input = usage.prompt_tokens ?? undefined
    const output = usage.completion_tokens ?? undefined
    const cached = usage.prompt_tokens_details?.cached_tokens ?? undefined
    const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? undefined
    const reasoning = usage.completion_tokens_details?.reasoning_tokens ?? undefined
    const nonCached = ProviderShared.subtractTokens(input, ProviderShared.sumTokens(cached, cacheWrite))
    return new Usage({
    inputTokens: input,
    outputTokens: output,
    nonCachedInputTokens: nonCached,
    cacheReadInputTokens: cached,
    cacheWriteInputTokens: cacheWrite,
    reasoningTokens: reasoning,
    totalTokens: ProviderShared.totalTokens(input, output, usage.total_tokens ?? undefined),
    providerMetadata: { openai: usage },
    })
    }

    DeepSeek reports prompt_cache_hit_tokens / prompt_cache_miss_tokens at the top level of usage
    (per your report — I have no DeepSeek traffic to verify the wire shape myself), so neither key is
    matched. #36749 hits the identical wall with a gateway sending cache_creation_input_tokens.

    Both keys already survive decoding, because the usage struct carries a rest record:

    const OpenAIChatUsage = Schema.StructWithRest(
    Schema.Struct({
    prompt_tokens: optionalNull(Schema.Number),
    completion_tokens: optionalNull(Schema.Number),
    total_tokens: optionalNull(Schema.Number),
    prompt_tokens_details: optionalNull(
    Schema.StructWithRest(
    Schema.Struct({
    cached_tokens: optionalNull(Schema.Number),
    cache_write_tokens: optionalNull(Schema.Number),
    }),
    [Schema.Record(Schema.String, Schema.Unknown)],
    ),
    ),
    completion_tokens_details: optionalNull(
    Schema.StructWithRest(
    Schema.Struct({
    reasoning_tokens: optionalNull(Schema.Number),
    accepted_prediction_tokens: optionalNull(Schema.Number),
    rejected_prediction_tokens: optionalNull(Schema.Number),
    }),
    [Schema.Record(Schema.String, Schema.Unknown)],
    ),
    ),
    }),
    [Schema.Record(Schema.String, Schema.Unknown)],
    )

    So the fix is additive inside mapUsage — read the provider's own key names before falling back to
    the subtraction. No schema change.

    Worth flagging why nobody notices until the bill arrives: prompt_tokens is inclusive, and nonCached
    is derived as prompt_tokens - (cached + cacheWrite). With cacheWrite undefined the miss tokens stay
    inside nonCached and the totals still reconcile exactly, so cache.write: 0 is not accompanied by
    any other symptom. Your requested hit / (hit + miss) ratio is unrecoverable from the stored message
    for the same reason — providerMetadata is not persisted, so the raw counts are gone by then.

  3. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions