Skip to content

NanoGPT cache-write usage and billed cost are lost in session accounting #48478

Open

Description

@huggix

Description

The nano-gpt integration loses cache-write usage and recomputes an estimated cost even when NanoGPT returns the settled USD charge. The OpenAI-compatible SDK does not map usage.cache_creation_input_tokens into its normalized usage, and OpenCode does not consume x_nanogpt_pricing.

This can display zero cache writes and a cost that differs from the charge when cache-write rates, long-context pricing, or discounts apply. Cache writes should remain distinct from cache reads. A valid reported USD cost should take precedence over the estimate, including an explicit zero; otherwise the existing estimate should remain.

OpenCode version

Reported in desktop v1.18.30; reproduced through the SDK and session accounting tests on dev commit 193de13.

Steps to reproduce

Use the nano-gpt provider with @ai-sdk/openai-compatible. Feed its SDK a synthetic completion with:

{
  "usage": {
    "prompt_tokens": 1000,
    "completion_tokens": 20,
    "total_tokens": 1020,
    "cache_creation_input_tokens": 800,
    "prompt_tokens_details": { "cached_tokens": 100 }
  },
  "x_nanogpt_pricing": { "amount": 0.0123, "currency": "USD" }
}

Observe session accounting: the cache-write count is lost and the cost uses catalog rates. Expected: 100 ordinary input tokens, 100 cache-read tokens, 800 cache-write tokens, and cost $0.0123. The same issue affects streaming usage frames.

Local regression tests and a fix are in #48477. No live inference is required for the reproduction.

Activity

  1. github-actions commented on Sep 11, 2026

    @github-actions
    Contributor

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

    The NanoGPT-specific x_nanogpt_pricing cost override aspect may be unique to this report, but the cache-write token loss overlaps with the above.

  2. huggix commented on Sep 14, 2026

    @huggix
    Author

    Thanks—checked all three.

    #37745 overlaps with the missing cache-write count. #35974 concerns DeepSeek’s prompt_cache_miss_tokens, whereas this report concerns NanoGPT’s explicit usage.cache_creation_input_tokens. #34022 concerns missing cache reads; our reproduction preserves reads but loses writes.

    The additional issue here is that OpenCode ignores NanoGPT’s settled USD charge in x_nanogpt_pricing and recomputes an estimate from catalog rates. Fixing cache-write mapping alone would leave that discrepancy unresolved.

    #48477 includes a proposed fix and regression tests for both aspects, including streaming. Happy to consolidate the cache-write portion if preferred, while retaining coverage of the reported-cost handling.

  3. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I can reproduce this on the latest dev (1e7ba46).

    I replayed your usage payload as a streamed chat-completions response through OpenCode's OpenAI-compatible protocol (packages/ai), with the provider set to nano-gpt. The parsed usage came back as:

    • input 1000, cache read 100: correct
    • cache write: missing, expected 800
    • non-cached input: 900, expected 100, so the cache-write tokens are counted as ordinary input
    • cost: missing, so session accounting falls back to the catalog estimate instead of the reported $0.0123

    The cause is in mapUsage in packages/ai/src/protocols/openai-chat.ts. It reads cache writes only from prompt_tokens_details.cache_write_tokens and cache_created_input_tokens, never from the top-level cache_creation_input_tokens. Nothing reads x_nanogpt_pricing.

    The test below reproduces it from packages/ai/test/. I only tested the streaming path. A related gap is the TODO for #35765 in packages/core/src/session/usage.ts, which is about using Copilot's reported billed amount in the same way.

    Reproduction script (nanogpt-repro.test.ts)
    // Reproduction for anomalyco/opencode#48478: NanoGPT usage through the OpenAI-compatible chat protocol.
    import { describe, expect } from "bun:test"
    import { Effect } from "effect"
    import { LLM } from "../src/effect.js"
    import { OpenAICompatible } from "../src/providers/openai-compatible.js"
    import { LLMClient } from "../src/route.js"
    import { it } from "./lib/effect.js"
    import { fixedResponse } from "./lib/http.js"
    import { sseEvents } from "./lib/sse.js"
    
    const model = OpenAICompatible.configure({ baseURL: "https://nano-gpt.com/api/v1", apiKey: "k", provider: "nano-gpt" }).model(
      "claude-sonnet-4-6",
    )
    
    describe("NanoGPT usage (#48478)", () => {
      it.effect("keeps cache writes and the billed cost", () =>
        Effect.gen(function* () {
          const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Say OK" })).pipe(
            Effect.provide(
              fixedResponse(
                sseEvents(
                  { id: "c", object: "chat.completion.chunk", created: 1, model: "m",
                    choices: [{ index: 0, delta: { content: "OK" }, finish_reason: null }] },
                  { id: "c", object: "chat.completion.chunk", created: 1, model: "m",
                    choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
                    usage: { prompt_tokens: 1000, completion_tokens: 20, total_tokens: 1020,
                      cache_creation_input_tokens: 800, prompt_tokens_details: { cached_tokens: 100 } },
                    x_nanogpt_pricing: { amount: 0.0123, currency: "USD" } },
                  "[DONE]",
                ),
              ),
            ),
          )
          const u = response.usage!
          console.log("USAGE", JSON.stringify({ input: u.inputTokens, nonCached: u.nonCachedInputTokens,
            cacheRead: u.cacheReadInputTokens, cacheWrite: u.cacheWriteInputTokens, cost: u.cost }))
          expect(u.cacheReadInputTokens).toBe(100)
          expect(u.cacheWriteInputTokens).toBe(800)
          expect(u.nonCachedInputTokens).toBe(100)
          expect(u.cost).toBe(0.0123)
        }),
      )
    })

    Run it: bun nanogpt-repro.test.ts

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions