From 6a6417758972db9eb08b8534e84f21471899e205 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 20 Mar 2026 23:10:34 -0400 Subject: [PATCH] fix(zen): emit cost chunk in client-facing format, not upstream format (#16817) --- .../console/app/src/routes/zen/util/handler.ts | 12 ++++++------ .../app/src/routes/zen/util/provider/anthropic.ts | 1 - .../app/src/routes/zen/util/provider/google.ts | 1 - .../routes/zen/util/provider/openai-compatible.ts | 1 - .../app/src/routes/zen/util/provider/openai.ts | 1 - .../app/src/routes/zen/util/provider/provider.ts | 14 +++++++++++++- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 1996d48c5..d51688c99 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -24,7 +24,7 @@ import { FreeUsageLimitError, SubscriptionUsageLimitError, } from "./error" -import { createBodyConverter, createStreamPartConverter, createResponseConverter, UsageInfo } from "./provider/provider" +import { buildCostChunk, createBodyConverter, createStreamPartConverter, createResponseConverter, UsageInfo } from "./provider/provider" import { anthropicHelper } from "./provider/anthropic" import { googleHelper } from "./provider/google" import { openaiHelper } from "./provider/openai" @@ -90,7 +90,7 @@ export async function handler( const projectId = input.request.headers.get("x-opencode-project") ?? "" const ocClient = input.request.headers.get("x-opencode-client") ?? "" logger.metric({ - is_tream: isStream, + is_stream: isStream, session: sessionId, request: requestId, client: ocClient, @@ -230,7 +230,7 @@ export async function handler( const body = JSON.stringify( responseConverter({ ...json, - cost: calculateOccuredCost(billingSource, costInfo), + cost: calculateOccurredCost(billingSource, costInfo), }), ) logger.metric({ response_length: body.length }) @@ -274,8 +274,8 @@ export async function handler( await trialLimiter?.track(usageInfo) await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) await reload(billingSource, authInfo, costInfo) - const cost = calculateOccuredCost(billingSource, costInfo) - c.enqueue(encoder.encode(usageParser.buidlCostChunk(cost))) + const cost = calculateOccurredCost(billingSource, costInfo) + c.enqueue(encoder.encode(buildCostChunk(opts.format, cost))) } c.close() return @@ -818,7 +818,7 @@ export async function handler( } } - function calculateOccuredCost(billingSource: BillingSource, costInfo: CostInfo) { + function calculateOccurredCost(billingSource: BillingSource, costInfo: CostInfo) { return billingSource === "balance" ? (costInfo.totalCostInCent / 100).toFixed(8) : "0" } diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index 15fe75b84..2b617bff6 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -167,7 +167,6 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => } }, retrieve: () => usage, - buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`, } }, normalizeUsage: (usage: Usage) => ({ diff --git a/packages/console/app/src/routes/zen/util/provider/google.ts b/packages/console/app/src/routes/zen/util/provider/google.ts index ecf3b2d4d..f6f7d6e19 100644 --- a/packages/console/app/src/routes/zen/util/provider/google.ts +++ b/packages/console/app/src/routes/zen/util/provider/google.ts @@ -56,7 +56,6 @@ export const googleHelper: ProviderHelper = ({ providerModel }) => ({ usage = json.usageMetadata }, retrieve: () => usage, - buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ type: "ping", cost })}\n\n`, } }, normalizeUsage: (usage: Usage) => { diff --git a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts index 046bf8f0c..ce97a34d9 100644 --- a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts +++ b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts @@ -54,7 +54,6 @@ export const oaCompatHelper: ProviderHelper = () => ({ usage = json.usage }, retrieve: () => usage, - buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ choices: [], cost })}\n\n`, } }, normalizeUsage: (usage: Usage) => { diff --git a/packages/console/app/src/routes/zen/util/provider/openai.ts b/packages/console/app/src/routes/zen/util/provider/openai.ts index 596b38cc5..e5649239e 100644 --- a/packages/console/app/src/routes/zen/util/provider/openai.ts +++ b/packages/console/app/src/routes/zen/util/provider/openai.ts @@ -44,7 +44,6 @@ export const openaiHelper: ProviderHelper = () => ({ usage = json.response.usage }, retrieve: () => usage, - buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`, } }, normalizeUsage: (usage: Usage) => { diff --git a/packages/console/app/src/routes/zen/util/provider/provider.ts b/packages/console/app/src/routes/zen/util/provider/provider.ts index 1f9492845..3afabdae9 100644 --- a/packages/console/app/src/routes/zen/util/provider/provider.ts +++ b/packages/console/app/src/routes/zen/util/provider/provider.ts @@ -43,7 +43,6 @@ export type ProviderHelper = (input: { reqModel: string; providerModel: string } createUsageParser: () => { parse: (chunk: string) => void retrieve: () => any - buidlCostChunk: (cost: string) => string } normalizeUsage: (usage: any) => UsageInfo } @@ -162,6 +161,19 @@ export interface CommonChunk { } } +export function buildCostChunk(format: ZenData.Format, cost: string): string { + switch (format) { + case "anthropic": + return `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n` + case "openai": + return `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n` + case "oa-compat": + return `data: ${JSON.stringify({ choices: [], cost })}\n\n` + default: + return `data: ${JSON.stringify({ type: "ping", cost })}\n\n` + } +} + export function createBodyConverter(from: ZenData.Format, to: ZenData.Format) { return (body: any): any => { if (from === to) return body