From 3423870eea9723dcdab02cc8e061cd3585e19bc2 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 13:07:52 +0000 Subject: [PATCH 1/7] fix(cli): extract Kilo provider cost from Anthropic Messages API responses Cost reporting already worked when Kilo used OpenRouter chat completions internally. Extend providerCost to also read cost from the Anthropic Messages API stream metadata, covering both OpenRouter (Anthropic-style `usage.cost` / `cost_details.upstream_inference_cost`) and Vercel AI Gateway (`gateway.cost` / `gateway.marketCost`). --- .changeset/kilo-anthropic-messages-cost.md | 5 + .../opencode/src/kilocode/session/index.ts | 65 ++++++-- .../opencode/test/session/compaction.test.ts | 149 ++++++++++++++++++ 3 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 .changeset/kilo-anthropic-messages-cost.md diff --git a/.changeset/kilo-anthropic-messages-cost.md b/.changeset/kilo-anthropic-messages-cost.md new file mode 100644 index 00000000000..e6e10e3f7ab --- /dev/null +++ b/.changeset/kilo-anthropic-messages-cost.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Report accurate Kilo provider costs for models served through the Anthropic Messages API (via OpenRouter or Vercel AI Gateway). diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index b77f3b0d658..6322adde6a1 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -101,9 +101,18 @@ export namespace KiloSession { // --------------------------------------------------------------------------- /** - * Extract provider-reported cost from OpenRouter metadata when available. - * For the Kilo provider (BYOK), prefers `upstreamInferenceCost` over the - * regular `cost` field (which is just the OpenRouter 5% fee). + * Extract provider-reported cost from response metadata when available. + * + * Supports three internal transports: + * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` + * (`costDetails.upstreamInferenceCost` for BYOK) + * 2. Anthropic Messages via OpenRouter -> `metadata.anthropic.usage.cost` + * (`cost_details.upstream_inference_cost` for BYOK) + * 3. Anthropic Messages via Vercel AI Gateway -> `metadata.gateway.cost` + * (`metadata.gateway.marketCost` for BYOK) + * + * For the Kilo provider (always BYOK), prefers the upstream/market cost over + * the regular `cost` field (which represents the gateway fee, often 0). * * Returns `undefined` when no provider cost is available, so the caller * should fall back to the standard token-based calculation. @@ -115,23 +124,45 @@ export namespace KiloSession { provider?: Provider.Info providerID: string }): number | undefined { - const openrouterUsage = input.metadata?.["openrouter"]?.["usage"] as - | { - cost?: number - costDetails?: { upstreamInferenceCost?: number } - } - | undefined - - if (!openrouterUsage) return undefined - const isKilo = (input.provider?.id ?? input.providerID) === "kilo" - const upstream = openrouterUsage.costDetails?.upstreamInferenceCost - const regular = openrouterUsage.cost - // Kilo is always BYOK, so prefer upstream cost. For OpenRouter, use regular cost. - const cost = isKilo && upstream !== undefined ? upstream : regular + const num = (value: unknown): number | undefined => { + if (value === undefined || value === null) return undefined + const n = typeof value === "string" ? Number(value) : (value as number) + return Number.isFinite(n) ? n : undefined + } + + const pick = (regular: unknown, upstream: unknown): number | undefined => { + const u = num(upstream) + const r = num(regular) + return isKilo && u !== undefined ? u : (r ?? u) + } + + // 1. OpenRouter chat completions + const orUsage = input.metadata?.["openrouter"]?.["usage"] as + | { cost?: number; costDetails?: { upstreamInferenceCost?: number } } + | undefined + if (orUsage) { + const cost = pick(orUsage.cost, orUsage.costDetails?.upstreamInferenceCost) + if (cost !== undefined) return cost + } + + // 2. Anthropic Messages API (passes through OpenRouter `usage` fields verbatim) + const anthropicUsage = input.metadata?.["anthropic"]?.["usage"] as + | { cost?: number; cost_details?: { upstream_inference_cost?: number } } + | undefined + if (anthropicUsage) { + const cost = pick(anthropicUsage.cost, anthropicUsage.cost_details?.upstream_inference_cost) + if (cost !== undefined) return cost + } + + // 3. Vercel AI Gateway (cost / marketCost are emitted as strings) + const gateway = input.metadata?.["gateway"] as { cost?: string | number; marketCost?: string | number } | undefined + if (gateway) { + const cost = pick(gateway.cost, gateway.marketCost) + if (cost !== undefined) return cost + } - if (cost !== undefined && cost !== null && Number.isFinite(cost)) return cost return undefined } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index c7b0b21de04..dbed0730c7d 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -2329,6 +2329,155 @@ describe("SessionNs.getUsage", () => { // When upstream cost is missing for Kilo, fall back to regular cost field expect(result.cost).toBe(0.01) }) + + test("uses anthropic messages api cost for OpenRouter (Kilo BYOK)", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "kilo" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + }, + metadata: { + anthropic: { + usage: { + cost: 0.0057550875, + is_byok: true, + cost_details: { + upstream_inference_cost: 0.11510175, + }, + }, + }, + }, + }) + + expect(result.cost).toBe(0.11510175) + }) + + test("uses anthropic messages api cost for OpenRouter (non-BYOK)", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "openrouter" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + }, + metadata: { + anthropic: { + usage: { + cost: 0.5, + cost_details: { upstream_inference_cost: 0.45 }, + }, + }, + }, + }) + + expect(result.cost).toBe(0.5) + }) + + test("uses Vercel AI Gateway marketCost for Kilo provider", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "kilo" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + }, + metadata: { + gateway: { + // Strings, exactly as emitted by the AI Gateway message_delta event + cost: "0", + marketCost: "0.35349075", + }, + }, + }) + + expect(result.cost).toBe(0.35349075) + }) + + test("uses Vercel AI Gateway cost when marketCost missing", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "kilo" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + }, + metadata: { + gateway: { + cost: "0.123", + }, + }, + }) + + expect(result.cost).toBe(0.123) + }) + + test("falls back to calculated cost when no provider cost is reported", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "kilo" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + }, + metadata: { + anthropic: { + usage: { + // no cost / cost_details + }, + }, + }, + }) + + // 1M input * $3 + 100k output * $15 = 3 + 1.5 + expect(result.cost).toBe(3 + 1.5) + }) // kilocode_change end test.each(["@ai-sdk/anthropic", "@ai-sdk/amazon-bedrock", "@ai-sdk/google-vertex/anthropic"])( From 861eb522adc32939a6c7bcb000051a682316af1d Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 14:27:14 +0000 Subject: [PATCH 2/7] fix(cli): never use gateway-fee `cost` for Anthropic Messages paths For the Anthropic Messages API (via OpenRouter or Vercel AI Gateway), the top-level `cost` field is the gateway fee that Kilo paid (typically 0 for BYOK), not the user's true spend. Only use `upstream_inference_cost` / `marketCost`; fall back to the token-based calculation when those are absent. --- .../opencode/src/kilocode/session/index.ts | 48 ++++++++----------- .../opencode/test/session/compaction.test.ts | 24 ++++++---- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 6322adde6a1..9e7ca7966bb 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -104,15 +104,14 @@ export namespace KiloSession { * Extract provider-reported cost from response metadata when available. * * Supports three internal transports: - * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` - * (`costDetails.upstreamInferenceCost` for BYOK) - * 2. Anthropic Messages via OpenRouter -> `metadata.anthropic.usage.cost` - * (`cost_details.upstream_inference_cost` for BYOK) - * 3. Anthropic Messages via Vercel AI Gateway -> `metadata.gateway.cost` - * (`metadata.gateway.marketCost` for BYOK) + * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` + * (`costDetails.upstreamInferenceCost` for Kilo BYOK) + * 2. Anthropic Messages via OpenRouter -> `metadata.anthropic.usage.cost_details.upstream_inference_cost` + * 3. Anthropic Messages via Vercel AI Gateway -> `metadata.gateway.marketCost` * - * For the Kilo provider (always BYOK), prefers the upstream/market cost over - * the regular `cost` field (which represents the gateway fee, often 0). + * For the Anthropic Messages API paths only the upstream/market cost is used: + * the gateway-level `cost` field represents the gateway fee paid by Kilo + * (typically 0 for BYOK) and would understate the user's true spend. * * Returns `undefined` when no provider cost is available, so the caller * should fall back to the standard token-based calculation. @@ -132,36 +131,31 @@ export namespace KiloSession { return Number.isFinite(n) ? n : undefined } - const pick = (regular: unknown, upstream: unknown): number | undefined => { - const u = num(upstream) - const r = num(regular) - return isKilo && u !== undefined ? u : (r ?? u) - } - // 1. OpenRouter chat completions const orUsage = input.metadata?.["openrouter"]?.["usage"] as | { cost?: number; costDetails?: { upstreamInferenceCost?: number } } | undefined if (orUsage) { - const cost = pick(orUsage.cost, orUsage.costDetails?.upstreamInferenceCost) + const upstream = num(orUsage.costDetails?.upstreamInferenceCost) + const regular = num(orUsage.cost) + // Kilo is always BYOK, so prefer upstream cost. For OpenRouter, use regular cost. + const cost = isKilo && upstream !== undefined ? upstream : regular if (cost !== undefined) return cost } - // 2. Anthropic Messages API (passes through OpenRouter `usage` fields verbatim) + // 2. Anthropic Messages API via OpenRouter. Only the upstream BYOK cost is meaningful; + // the top-level `cost` field is the OpenRouter fee, which we deliberately ignore. const anthropicUsage = input.metadata?.["anthropic"]?.["usage"] as - | { cost?: number; cost_details?: { upstream_inference_cost?: number } } + | { cost_details?: { upstream_inference_cost?: number } } | undefined - if (anthropicUsage) { - const cost = pick(anthropicUsage.cost, anthropicUsage.cost_details?.upstream_inference_cost) - if (cost !== undefined) return cost - } + const upstream = num(anthropicUsage?.cost_details?.upstream_inference_cost) + if (upstream !== undefined) return upstream - // 3. Vercel AI Gateway (cost / marketCost are emitted as strings) - const gateway = input.metadata?.["gateway"] as { cost?: string | number; marketCost?: string | number } | undefined - if (gateway) { - const cost = pick(gateway.cost, gateway.marketCost) - if (cost !== undefined) return cost - } + // 3. Anthropic Messages API via Vercel AI Gateway. `cost` is what Kilo paid the gateway + // (typically 0 for BYOK), so we always use `marketCost`. Values are emitted as strings. + const gateway = input.metadata?.["gateway"] as { marketCost?: string | number } | undefined + const marketCost = num(gateway?.marketCost) + if (marketCost !== undefined) return marketCost return undefined } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index dbed0730c7d..17407f3cd6b 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -2330,7 +2330,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.01) }) - test("uses anthropic messages api cost for OpenRouter (Kilo BYOK)", () => { + test("uses upstream_inference_cost for Anthropic Messages API via OpenRouter", () => { const model = createModel({ context: 100_000, output: 32_000, @@ -2350,6 +2350,7 @@ describe("SessionNs.getUsage", () => { metadata: { anthropic: { usage: { + // Top-level `cost` is the OpenRouter fee and must be ignored cost: 0.0057550875, is_byok: true, cost_details: { @@ -2363,13 +2364,13 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.11510175) }) - test("uses anthropic messages api cost for OpenRouter (non-BYOK)", () => { + test("ignores Anthropic Messages `cost` when no upstream_inference_cost is reported", () => { const model = createModel({ context: 100_000, output: 32_000, cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, }) - const provider = { id: "openrouter" } as Provider.Info + const provider = { id: "kilo" } as Provider.Info const result = SessionNs.getUsage({ model, provider, @@ -2384,16 +2385,17 @@ describe("SessionNs.getUsage", () => { anthropic: { usage: { cost: 0.5, - cost_details: { upstream_inference_cost: 0.45 }, + // cost_details missing }, }, }, }) - expect(result.cost).toBe(0.5) + // Falls back to calculated cost: 1M input * $3 + 100k output * $15 = 3 + 1.5 + expect(result.cost).toBe(3 + 1.5) }) - test("uses Vercel AI Gateway marketCost for Kilo provider", () => { + test("uses Vercel AI Gateway marketCost", () => { const model = createModel({ context: 100_000, output: 32_000, @@ -2412,7 +2414,8 @@ describe("SessionNs.getUsage", () => { }, metadata: { gateway: { - // Strings, exactly as emitted by the AI Gateway message_delta event + // Strings, exactly as emitted by the AI Gateway message_delta event. + // `cost` (gateway fee, often 0 for BYOK) must be ignored. cost: "0", marketCost: "0.35349075", }, @@ -2422,7 +2425,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.35349075) }) - test("uses Vercel AI Gateway cost when marketCost missing", () => { + test("ignores Vercel AI Gateway `cost` when marketCost is missing", () => { const model = createModel({ context: 100_000, output: 32_000, @@ -2446,7 +2449,8 @@ describe("SessionNs.getUsage", () => { }, }) - expect(result.cost).toBe(0.123) + // Falls back to calculated cost: 1M input * $3 + 100k output * $15 = 3 + 1.5 + expect(result.cost).toBe(3 + 1.5) }) test("falls back to calculated cost when no provider cost is reported", () => { @@ -2469,7 +2473,7 @@ describe("SessionNs.getUsage", () => { metadata: { anthropic: { usage: { - // no cost / cost_details + // no cost_details }, }, }, From c8073d9014bf87a14fa73da615f6116508641e13 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 14:51:18 +0000 Subject: [PATCH 3/7] fix(cli): also extract Kilo cost from OpenAI Responses API responses Refactor providerCost to read `usage.raw.cost_details.upstream_inference_cost`, which both `@ai-sdk/anthropic` (`convertAnthropicUsage`) and `@ai-sdk/openai` (`convertOpenAIResponsesUsage`) populate with the verbatim OpenRouter usage object. This unifies the Anthropic Messages and OpenAI Responses cost paths through a single branch and removes the redundant `metadata.anthropic.usage` branch (the SDK exposes the same data under both keys for Anthropic, and only under `raw` for Responses). --- .changeset/kilo-anthropic-messages-cost.md | 2 +- .../opencode/src/kilocode/session/index.ts | 50 ++++++++---- packages/opencode/src/session/session.ts | 1 + .../opencode/test/session/compaction.test.ts | 78 +++++++++++++------ 4 files changed, 88 insertions(+), 43 deletions(-) diff --git a/.changeset/kilo-anthropic-messages-cost.md b/.changeset/kilo-anthropic-messages-cost.md index e6e10e3f7ab..f0c123a6cb2 100644 --- a/.changeset/kilo-anthropic-messages-cost.md +++ b/.changeset/kilo-anthropic-messages-cost.md @@ -2,4 +2,4 @@ "@kilocode/cli": patch --- -Report accurate Kilo provider costs for models served through the Anthropic Messages API (via OpenRouter or Vercel AI Gateway). +Report accurate Kilo provider costs for models served through the Anthropic Messages API or the OpenAI Responses API (via OpenRouter). diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 9e7ca7966bb..64f36f41917 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -14,7 +14,7 @@ import { ProjectID } from "@/project/schema" import { Filesystem } from "@/util/filesystem" import { SessionTable } from "@/session/session.sql" import * as Log from "@opencode-ai/core/util/log" -import type { ProviderMetadata } from "ai" +import type { LanguageModelUsage, ProviderMetadata } from "ai" import type { Provider } from "@/provider/provider" export namespace KiloSession { @@ -103,15 +103,22 @@ export namespace KiloSession { /** * Extract provider-reported cost from response metadata when available. * - * Supports three internal transports: - * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` - * (`costDetails.upstreamInferenceCost` for Kilo BYOK) - * 2. Anthropic Messages via OpenRouter -> `metadata.anthropic.usage.cost_details.upstream_inference_cost` - * 3. Anthropic Messages via Vercel AI Gateway -> `metadata.gateway.marketCost` + * Supports the following internal transports: + * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` + * (`costDetails.upstreamInferenceCost` for Kilo BYOK) + * 2. Anthropic Messages or OpenAI Responses via OpenRouter + * -> `usage.raw.cost_details.upstream_inference_cost` + * (the `@ai-sdk/anthropic` and `@ai-sdk/openai` providers both surface the verbatim + * provider usage object on `LanguageModelUsage.raw`, so OpenRouter's BYOK cost lands + * there with snake_case preserved) + * 3. Anthropic Messages or OpenAI Responses via Vercel AI Gateway + * -> `metadata.gateway.marketCost` (defensive: the + * gateway emits this in the SSE `provider_metadata` field, which the current AI SDK + * providers drop before they reach this layer) * - * For the Anthropic Messages API paths only the upstream/market cost is used: - * the gateway-level `cost` field represents the gateway fee paid by Kilo - * (typically 0 for BYOK) and would understate the user's true spend. + * The top-level `cost` field for non-OpenRouter-chat-completions paths represents the + * gateway fee paid by Kilo (typically 0 for BYOK) and would understate the user's true + * spend, so it is deliberately never used. * * Returns `undefined` when no provider cost is available, so the caller * should fall back to the standard token-based calculation. @@ -120,6 +127,7 @@ export namespace KiloSession { */ export function providerCost(input: { metadata?: ProviderMetadata + usage?: LanguageModelUsage provider?: Provider.Info providerID: string }): number | undefined { @@ -143,16 +151,24 @@ export namespace KiloSession { if (cost !== undefined) return cost } - // 2. Anthropic Messages API via OpenRouter. Only the upstream BYOK cost is meaningful; - // the top-level `cost` field is the OpenRouter fee, which we deliberately ignore. - const anthropicUsage = input.metadata?.["anthropic"]?.["usage"] as - | { cost_details?: { upstream_inference_cost?: number } } - | undefined - const upstream = num(anthropicUsage?.cost_details?.upstream_inference_cost) + // 2. Anthropic Messages or OpenAI Responses via OpenRouter. The `@ai-sdk/anthropic` + // (`convertAnthropicUsage`) and `@ai-sdk/openai` (`convertOpenAIResponsesUsage`) + // providers both copy the verbatim provider usage object onto `usage.raw`, so + // OpenRouter's BYOK upstream cost lands at `usage.raw.cost_details.upstream_inference_cost` + // with snake_case preserved. Only the upstream BYOK cost is meaningful here; the + // top-level `cost` is the OpenRouter fee. + const raw = input.usage?.raw as { cost_details?: { upstream_inference_cost?: number } } | undefined + const upstream = num(raw?.cost_details?.upstream_inference_cost) if (upstream !== undefined) return upstream - // 3. Anthropic Messages API via Vercel AI Gateway. `cost` is what Kilo paid the gateway - // (typically 0 for BYOK), so we always use `marketCost`. Values are emitted as strings. + // 3. Anthropic Messages or OpenAI Responses via Vercel AI Gateway. `cost` is what Kilo + // paid the gateway (typically 0 for BYOK), so we always use `marketCost`. Values are + // emitted as strings on the wire. + // + // NOTE: this branch is currently dormant because neither `@ai-sdk/anthropic` nor + // `@ai-sdk/openai` (responses) forwards the SSE-level `provider_metadata.gateway` + // block to `providerMetadata`. Kept as defensive code so the cost starts flowing + // automatically once the SDK gap is closed. const gateway = input.metadata?.["gateway"] as { marketCost?: string | number } | undefined const marketCost = num(gateway?.marketCost) if (marketCost !== undefined) return marketCost diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 2b9f644e15e..89e7ddeb7c4 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -385,6 +385,7 @@ export const getUsage = (input: { // kilocode_change start - Use provider-reported cost when available for OpenRouter/Kilo const reported = KiloSession.providerCost({ metadata: input.metadata, + usage: input.usage, provider: input.provider, providerID: input.model.providerID, }) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 17407f3cd6b..0802eaed2f8 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -2330,7 +2330,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.01) }) - test("uses upstream_inference_cost for Anthropic Messages API via OpenRouter", () => { + test("uses usage.raw.cost_details.upstream_inference_cost for Anthropic Messages via OpenRouter", () => { const model = createModel({ context: 100_000, output: 32_000, @@ -2346,16 +2346,17 @@ describe("SessionNs.getUsage", () => { totalTokens: 1_100_000, inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - }, - metadata: { - anthropic: { - usage: { - // Top-level `cost` is the OpenRouter fee and must be ignored - cost: 0.0057550875, - is_byok: true, - cost_details: { - upstream_inference_cost: 0.11510175, - }, + // `convertAnthropicUsage` copies the verbatim provider usage onto `raw`. + // Top-level `cost` is the OpenRouter fee and must be ignored. + raw: { + input_tokens: 1, + output_tokens: 1121, + cache_creation_input_tokens: 5385, + cache_read_input_tokens: 106831, + cost: 0.0057550875, + is_byok: true, + cost_details: { + upstream_inference_cost: 0.11510175, }, }, }, @@ -2364,7 +2365,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.11510175) }) - test("ignores Anthropic Messages `cost` when no upstream_inference_cost is reported", () => { + test("uses usage.raw.cost_details.upstream_inference_cost for OpenAI Responses via OpenRouter", () => { const model = createModel({ context: 100_000, output: 32_000, @@ -2380,17 +2381,50 @@ describe("SessionNs.getUsage", () => { totalTokens: 1_100_000, inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - }, - metadata: { - anthropic: { - usage: { - cost: 0.5, - // cost_details missing + // `convertOpenAIResponsesUsage` copies the verbatim provider usage onto `raw`. + raw: { + input_tokens: 622051, + input_tokens_details: { cached_tokens: 594944 }, + output_tokens: 304, + output_tokens_details: { reasoning_tokens: 0 }, + total_tokens: 622355, + cost: 0.0439847, + is_byok: true, + cost_details: { + upstream_inference_cost: 0.879694, + upstream_inference_input_cost: 0.866014, + upstream_inference_output_cost: 0.01368, }, }, }, }) + expect(result.cost).toBe(0.879694) + }) + + test("ignores raw `cost` when no upstream_inference_cost is reported", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + const provider = { id: "kilo" } as Provider.Info + const result = SessionNs.getUsage({ + model, + provider, + usage: { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, + raw: { + cost: 0.5, + // cost_details missing + }, + }, + }) + // Falls back to calculated cost: 1M input * $3 + 100k output * $15 = 3 + 1.5 expect(result.cost).toBe(3 + 1.5) }) @@ -2470,13 +2504,7 @@ describe("SessionNs.getUsage", () => { inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, }, - metadata: { - anthropic: { - usage: { - // no cost_details - }, - }, - }, + // No metadata, no usage.raw — should fall back }) // 1M input * $3 + 100k output * $15 = 3 + 1.5 From 2f3068e18dfe1cde316f93041e0dc55506d63ab2 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 14:56:24 +0000 Subject: [PATCH 4/7] test(cli): move new providerCost tests into test/kilocode/ Move the new Anthropic Messages, OpenAI Responses and Vercel AI Gateway cost-extraction tests out of the shared upstream `test/session/compaction.test.ts` file into `test/kilocode/provider-cost.test.ts` to keep Kilo-specific test coverage in a Kilo-owned location and minimize merge conflicts with upstream. --- .../test/kilocode/provider-cost.test.ts | 171 ++++++++++++++++ .../opencode/test/session/compaction.test.ts | 182 +----------------- 2 files changed, 173 insertions(+), 180 deletions(-) create mode 100644 packages/opencode/test/kilocode/provider-cost.test.ts diff --git a/packages/opencode/test/kilocode/provider-cost.test.ts b/packages/opencode/test/kilocode/provider-cost.test.ts new file mode 100644 index 00000000000..293eaca533b --- /dev/null +++ b/packages/opencode/test/kilocode/provider-cost.test.ts @@ -0,0 +1,171 @@ +import { describe, expect, test } from "bun:test" +import { Session as SessionNs } from "@/session/session" +import type { Provider } from "@/provider/provider" + +function createModel(opts: { + context: number + output: number + input?: number + cost?: Provider.Model["cost"] + npm?: string +}): Provider.Model { + return { + id: "test-model", + providerID: "test", + name: "Test", + limit: { + context: opts.context, + input: opts.input, + output: opts.output, + }, + cost: opts.cost ?? { input: 0, output: 0, cache: { read: 0, write: 0 } }, + capabilities: { + toolcall: true, + attachment: false, + reasoning: false, + temperature: true, + input: { text: true, image: false, audio: false, video: false }, + output: { text: true, image: false, audio: false, video: false }, + }, + api: { npm: opts.npm ?? "@ai-sdk/anthropic" }, + options: {}, + } as Provider.Model +} + +const baseUsage = { + inputTokens: 1_000_000, + outputTokens: 100_000, + totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, +} + +const model = () => + createModel({ + context: 100_000, + output: 32_000, + cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, + }) + +const kilo = { id: "kilo" } as Provider.Info + +// Calculated cost for the `model()` + `baseUsage` pair: 1M input * $3 + 100k output * $15 = 3 + 1.5 +const fallback = 3 + 1.5 + +describe("KiloSession.providerCost — Anthropic Messages / OpenAI Responses", () => { + test("uses usage.raw.cost_details.upstream_inference_cost for Anthropic Messages via OpenRouter", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: { + ...baseUsage, + // `convertAnthropicUsage` copies the verbatim provider usage onto `raw`. + // Top-level `cost` is the OpenRouter fee and must be ignored. + raw: { + input_tokens: 1, + output_tokens: 1121, + cache_creation_input_tokens: 5385, + cache_read_input_tokens: 106831, + cost: 0.0057550875, + is_byok: true, + cost_details: { + upstream_inference_cost: 0.11510175, + }, + }, + }, + }) + + expect(result.cost).toBe(0.11510175) + }) + + test("uses usage.raw.cost_details.upstream_inference_cost for OpenAI Responses via OpenRouter", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: { + ...baseUsage, + // `convertOpenAIResponsesUsage` copies the verbatim provider usage onto `raw`. + raw: { + input_tokens: 622051, + input_tokens_details: { cached_tokens: 594944 }, + output_tokens: 304, + output_tokens_details: { reasoning_tokens: 0 }, + total_tokens: 622355, + cost: 0.0439847, + is_byok: true, + cost_details: { + upstream_inference_cost: 0.879694, + upstream_inference_input_cost: 0.866014, + upstream_inference_output_cost: 0.01368, + }, + }, + }, + }) + + expect(result.cost).toBe(0.879694) + }) + + test("ignores raw `cost` when no upstream_inference_cost is reported", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: { + ...baseUsage, + raw: { + cost: 0.5, + // cost_details missing + }, + }, + }) + + expect(result.cost).toBe(fallback) + }) +}) + +describe("KiloSession.providerCost — Vercel AI Gateway", () => { + test("uses metadata.gateway.marketCost", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: baseUsage, + metadata: { + gateway: { + // Strings, exactly as emitted by the AI Gateway. `cost` (gateway fee, often 0 + // for BYOK) must be ignored. + cost: "0", + marketCost: "0.35349075", + }, + }, + }) + + expect(result.cost).toBe(0.35349075) + }) + + test("ignores metadata.gateway.cost when marketCost is missing", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: baseUsage, + metadata: { + gateway: { + cost: "0.123", + }, + }, + }) + + expect(result.cost).toBe(fallback) + }) +}) + +describe("KiloSession.providerCost — fallback", () => { + test("falls back to calculated cost when no provider cost is reported", () => { + const result = SessionNs.getUsage({ + model: model(), + provider: kilo, + usage: baseUsage, + // No metadata, no usage.raw — should fall back + }) + + expect(result.cost).toBe(fallback) + }) +}) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 0802eaed2f8..9243bdea5be 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -2330,186 +2330,8 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.01) }) - test("uses usage.raw.cost_details.upstream_inference_cost for Anthropic Messages via OpenRouter", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - // `convertAnthropicUsage` copies the verbatim provider usage onto `raw`. - // Top-level `cost` is the OpenRouter fee and must be ignored. - raw: { - input_tokens: 1, - output_tokens: 1121, - cache_creation_input_tokens: 5385, - cache_read_input_tokens: 106831, - cost: 0.0057550875, - is_byok: true, - cost_details: { - upstream_inference_cost: 0.11510175, - }, - }, - }, - }) - - expect(result.cost).toBe(0.11510175) - }) - - test("uses usage.raw.cost_details.upstream_inference_cost for OpenAI Responses via OpenRouter", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - // `convertOpenAIResponsesUsage` copies the verbatim provider usage onto `raw`. - raw: { - input_tokens: 622051, - input_tokens_details: { cached_tokens: 594944 }, - output_tokens: 304, - output_tokens_details: { reasoning_tokens: 0 }, - total_tokens: 622355, - cost: 0.0439847, - is_byok: true, - cost_details: { - upstream_inference_cost: 0.879694, - upstream_inference_input_cost: 0.866014, - upstream_inference_output_cost: 0.01368, - }, - }, - }, - }) - - expect(result.cost).toBe(0.879694) - }) - - test("ignores raw `cost` when no upstream_inference_cost is reported", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - raw: { - cost: 0.5, - // cost_details missing - }, - }, - }) - - // Falls back to calculated cost: 1M input * $3 + 100k output * $15 = 3 + 1.5 - expect(result.cost).toBe(3 + 1.5) - }) - - test("uses Vercel AI Gateway marketCost", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - }, - metadata: { - gateway: { - // Strings, exactly as emitted by the AI Gateway message_delta event. - // `cost` (gateway fee, often 0 for BYOK) must be ignored. - cost: "0", - marketCost: "0.35349075", - }, - }, - }) - - expect(result.cost).toBe(0.35349075) - }) - - test("ignores Vercel AI Gateway `cost` when marketCost is missing", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - }, - metadata: { - gateway: { - cost: "0.123", - }, - }, - }) - - // Falls back to calculated cost: 1M input * $3 + 100k output * $15 = 3 + 1.5 - expect(result.cost).toBe(3 + 1.5) - }) - - test("falls back to calculated cost when no provider cost is reported", () => { - const model = createModel({ - context: 100_000, - output: 32_000, - cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } }, - }) - const provider = { id: "kilo" } as Provider.Info - const result = SessionNs.getUsage({ - model, - provider, - usage: { - inputTokens: 1_000_000, - outputTokens: 100_000, - totalTokens: 1_100_000, - inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, - outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, - }, - // No metadata, no usage.raw — should fall back - }) - - // 1M input * $3 + 100k output * $15 = 3 + 1.5 - expect(result.cost).toBe(3 + 1.5) - }) + // Tests for Anthropic Messages / OpenAI Responses / Vercel AI Gateway cost extraction + // live in test/kilocode/provider-cost.test.ts (kilocode_change). // kilocode_change end test.each(["@ai-sdk/anthropic", "@ai-sdk/amazon-bedrock", "@ai-sdk/google-vertex/anthropic"])( From c8446f52318309900b8dc1a75e6fea561b166e09 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 08:19:44 +0000 Subject: [PATCH 5/7] docs(cli): clarify why Kilo prefers upstream cost over gateway cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kilo isn't always BYOK; what matters here is that Kilo doesn't charge a per-request fee on top of the upstream inference cost. So for the Kilo provider, the gateway/marketplace `cost` would understate the user's true spend — always prefer the upstream/market cost when present. --- .../opencode/src/kilocode/session/index.ts | 31 +++++++++++-------- .../test/kilocode/provider-cost.test.ts | 4 +-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 64f36f41917..79884353e32 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -105,20 +105,20 @@ export namespace KiloSession { * * Supports the following internal transports: * 1. OpenRouter chat completions -> `metadata.openrouter.usage.cost` - * (`costDetails.upstreamInferenceCost` for Kilo BYOK) + * (`costDetails.upstreamInferenceCost` for Kilo) * 2. Anthropic Messages or OpenAI Responses via OpenRouter * -> `usage.raw.cost_details.upstream_inference_cost` * (the `@ai-sdk/anthropic` and `@ai-sdk/openai` providers both surface the verbatim - * provider usage object on `LanguageModelUsage.raw`, so OpenRouter's BYOK cost lands - * there with snake_case preserved) + * provider usage object on `LanguageModelUsage.raw`, so OpenRouter's upstream + * inference cost lands there with snake_case preserved) * 3. Anthropic Messages or OpenAI Responses via Vercel AI Gateway * -> `metadata.gateway.marketCost` (defensive: the * gateway emits this in the SSE `provider_metadata` field, which the current AI SDK * providers drop before they reach this layer) * - * The top-level `cost` field for non-OpenRouter-chat-completions paths represents the - * gateway fee paid by Kilo (typically 0 for BYOK) and would understate the user's true - * spend, so it is deliberately never used. + * Kilo does not charge end users a per-request fee, so for the Kilo provider the + * top-level `cost` field (the gateway/marketplace fee) would understate the user's + * actual upstream spend. Always prefer the upstream/market cost when present. * * Returns `undefined` when no provider cost is available, so the caller * should fall back to the standard token-based calculation. @@ -146,7 +146,9 @@ export namespace KiloSession { if (orUsage) { const upstream = num(orUsage.costDetails?.upstreamInferenceCost) const regular = num(orUsage.cost) - // Kilo is always BYOK, so prefer upstream cost. For OpenRouter, use regular cost. + // Kilo doesn't charge a fee on top of the upstream inference cost, so for Kilo + // prefer the upstream cost (the user's true spend). For the OpenRouter provider + // itself, the regular `cost` field is what the user is billed. const cost = isKilo && upstream !== undefined ? upstream : regular if (cost !== undefined) return cost } @@ -154,16 +156,19 @@ export namespace KiloSession { // 2. Anthropic Messages or OpenAI Responses via OpenRouter. The `@ai-sdk/anthropic` // (`convertAnthropicUsage`) and `@ai-sdk/openai` (`convertOpenAIResponsesUsage`) // providers both copy the verbatim provider usage object onto `usage.raw`, so - // OpenRouter's BYOK upstream cost lands at `usage.raw.cost_details.upstream_inference_cost` - // with snake_case preserved. Only the upstream BYOK cost is meaningful here; the - // top-level `cost` is the OpenRouter fee. + // OpenRouter's upstream inference cost lands at + // `usage.raw.cost_details.upstream_inference_cost` with snake_case preserved. + // Kilo doesn't charge end users a per-request fee, so the top-level `cost` field + // (the OpenRouter fee) would understate the user's true spend; only the upstream + // cost is meaningful here. const raw = input.usage?.raw as { cost_details?: { upstream_inference_cost?: number } } | undefined const upstream = num(raw?.cost_details?.upstream_inference_cost) if (upstream !== undefined) return upstream - // 3. Anthropic Messages or OpenAI Responses via Vercel AI Gateway. `cost` is what Kilo - // paid the gateway (typically 0 for BYOK), so we always use `marketCost`. Values are - // emitted as strings on the wire. + // 3. Anthropic Messages or OpenAI Responses via Vercel AI Gateway. `cost` is the + // gateway fee that Kilo would pass through, but Kilo doesn't charge end users a + // per-request fee, so always use `marketCost` (the upstream provider's price). + // Values are emitted as strings on the wire. // // NOTE: this branch is currently dormant because neither `@ai-sdk/anthropic` nor // `@ai-sdk/openai` (responses) forwards the SSE-level `provider_metadata.gateway` diff --git a/packages/opencode/test/kilocode/provider-cost.test.ts b/packages/opencode/test/kilocode/provider-cost.test.ts index 293eaca533b..fbace3f1c73 100644 --- a/packages/opencode/test/kilocode/provider-cost.test.ts +++ b/packages/opencode/test/kilocode/provider-cost.test.ts @@ -130,8 +130,8 @@ describe("KiloSession.providerCost — Vercel AI Gateway", () => { usage: baseUsage, metadata: { gateway: { - // Strings, exactly as emitted by the AI Gateway. `cost` (gateway fee, often 0 - // for BYOK) must be ignored. + // Strings, exactly as emitted by the AI Gateway. `cost` is the gateway fee, + // which Kilo doesn't pass on to end users — must be ignored. cost: "0", marketCost: "0.35349075", }, From 4b883792fb8219cf5c4d811ce23b930f6a597ddf Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 08:22:28 +0000 Subject: [PATCH 6/7] chore: simplify cost-reporting changeset message --- .changeset/kilo-anthropic-messages-cost.md | 5 ----- .changeset/kilo-gateway-cost-accuracy.md | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 .changeset/kilo-anthropic-messages-cost.md create mode 100644 .changeset/kilo-gateway-cost-accuracy.md diff --git a/.changeset/kilo-anthropic-messages-cost.md b/.changeset/kilo-anthropic-messages-cost.md deleted file mode 100644 index f0c123a6cb2..00000000000 --- a/.changeset/kilo-anthropic-messages-cost.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Report accurate Kilo provider costs for models served through the Anthropic Messages API or the OpenAI Responses API (via OpenRouter). diff --git a/.changeset/kilo-gateway-cost-accuracy.md b/.changeset/kilo-gateway-cost-accuracy.md new file mode 100644 index 00000000000..35cd2cd89d6 --- /dev/null +++ b/.changeset/kilo-gateway-cost-accuracy.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Improved accuracy of Kilo Gateway cost reporting. From 45f12a6a70197901f0663dbed08b98d60cd8680d Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 08:26:39 +0000 Subject: [PATCH 7/7] docs(cli): update providerCost section heading for new transports --- packages/opencode/src/kilocode/session/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 79884353e32..9e56cab0fbf 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -97,7 +97,7 @@ export namespace KiloSession { } // --------------------------------------------------------------------------- - // Provider-reported cost (OpenRouter / Kilo) + // Provider-reported cost (Kilo / OpenRouter / Vercel AI Gateway) // --------------------------------------------------------------------------- /**