Skip to content

Missing cache_write cost on non-Anthropic models causes inconsistent cost tracking #4

@s2x

Description

@s2x

Description

In models.config.mjs, Claude models define all four cost fields (input, output, cache_read, cache_write), but GPT, Gemini, and Kimi models omit cache_write:

// Claude — has cache_write ✓
"Claude Opus 4.6": {
  cost: { input: 5.5, output: 27.5, cache_read: 0.55, cache_write: 6.75 },
},

// GPT — missing cache_write ✗
"GPT 5.2": {
  cost: { input: 1.75, output: 14.0, cache_read: 0.175 },
},

// Gemini — missing cache_write ✗
"Gemini 2.5 Pro": {
  cost: { input: 1.25, output: 10.0, cache_read: 0.125 },
},

// Kimi — missing cache_write ✗
"Kimi K2.5": {
  cost: { input: 0.6, output: 3.0, cache_read: 0.1 },
},

Impact

When the downstream consumer (opencode) reads the generated opencode.json and attempts to calculate cache write costs:

const cacheWriteCost = model.cost.cache_write * tokensWrittenToCache;
// → NaN for GPT, Gemini, Kimi (because undefined * number = NaN)

This leads to:

  • NaN cost calculations displayed to the user.
  • Incorrect total cost summaries.
  • Potential runtime errors if the consumer expects a number.

Meanwhile, DEFAULT_FALLBACK_COSTS (line 107) does include cache_write: 6.25, so unsupported/unknown models paradoxically get a cache_write value while explicitly supported models like GPT 5.2 do not.

Proposed fix

For models where the provider does not charge for cache writes, explicitly set cache_write: 0:

"GPT 5.2": {
  cost: { input: 1.75, output: 14.0, cache_read: 0.175, cache_write: 0 },
},

Alternatively, if cache write pricing is unknown, document it clearly and ensure downstream consumers handle undefined gracefully.

Acceptance criteria

  • All models in SUPPORTED_MODELS have a consistent cost schema: all four fields (input, output, cache_read, cache_write) are present.
  • Models where the provider does not charge for cache writes have cache_write: 0 (not omitted).
  • DEFAULT_FALLBACK_COSTS schema matches SUPPORTED_MODELS cost schema.

Test cases

test("all supported models should have consistent cost fields", () => {
  const requiredFields = ["input", "output", "cache_read", "cache_write"];
  
  for (const [modelName, config] of Object.entries(SUPPORTED_MODELS)) {
    if (!config.cost) continue;
    for (const field of requiredFields) {
      expect(config.cost[field]).toBeDefined();
      expect(typeof config.cost[field]).toBe("number");
    }
  }
});

test("generated config should have numeric cache_write for all models", () => {
  const modelsList = [
    { id: "gpt-5", name: "GPT 5.2" },
    { id: "claude", name: "Claude Opus 4.6" },
    { id: "gemini", name: "Gemini 2.5 Pro" },
  ];
  
  const { models } = processModels(modelsList, {}, true);
  
  for (const [name, model] of Object.entries(models)) {
    expect(typeof model.cost.cache_write).toBe("number");
  }
});

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions