fix(zen): emit cost chunk in client-facing format, not upstream format (#16817)

This commit is contained in:
Kit Langton 2026-03-20 23:10:34 -04:00 committed by GitHub
parent 5dc47905a9
commit 6a64177589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 19 additions and 11 deletions

View File

@ -24,7 +24,7 @@ import {
FreeUsageLimitError, FreeUsageLimitError,
SubscriptionUsageLimitError, SubscriptionUsageLimitError,
} from "./error" } 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 { anthropicHelper } from "./provider/anthropic"
import { googleHelper } from "./provider/google" import { googleHelper } from "./provider/google"
import { openaiHelper } from "./provider/openai" import { openaiHelper } from "./provider/openai"
@ -90,7 +90,7 @@ export async function handler(
const projectId = input.request.headers.get("x-opencode-project") ?? "" const projectId = input.request.headers.get("x-opencode-project") ?? ""
const ocClient = input.request.headers.get("x-opencode-client") ?? "" const ocClient = input.request.headers.get("x-opencode-client") ?? ""
logger.metric({ logger.metric({
is_tream: isStream, is_stream: isStream,
session: sessionId, session: sessionId,
request: requestId, request: requestId,
client: ocClient, client: ocClient,
@ -230,7 +230,7 @@ export async function handler(
const body = JSON.stringify( const body = JSON.stringify(
responseConverter({ responseConverter({
...json, ...json,
cost: calculateOccuredCost(billingSource, costInfo), cost: calculateOccurredCost(billingSource, costInfo),
}), }),
) )
logger.metric({ response_length: body.length }) logger.metric({ response_length: body.length })
@ -274,8 +274,8 @@ export async function handler(
await trialLimiter?.track(usageInfo) await trialLimiter?.track(usageInfo)
await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
await reload(billingSource, authInfo, costInfo) await reload(billingSource, authInfo, costInfo)
const cost = calculateOccuredCost(billingSource, costInfo) const cost = calculateOccurredCost(billingSource, costInfo)
c.enqueue(encoder.encode(usageParser.buidlCostChunk(cost))) c.enqueue(encoder.encode(buildCostChunk(opts.format, cost)))
} }
c.close() c.close()
return 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" return billingSource === "balance" ? (costInfo.totalCostInCent / 100).toFixed(8) : "0"
} }

View File

@ -167,7 +167,6 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) =>
} }
}, },
retrieve: () => usage, retrieve: () => usage,
buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`,
} }
}, },
normalizeUsage: (usage: Usage) => ({ normalizeUsage: (usage: Usage) => ({

View File

@ -56,7 +56,6 @@ export const googleHelper: ProviderHelper = ({ providerModel }) => ({
usage = json.usageMetadata usage = json.usageMetadata
}, },
retrieve: () => usage, retrieve: () => usage,
buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ type: "ping", cost })}\n\n`,
} }
}, },
normalizeUsage: (usage: Usage) => { normalizeUsage: (usage: Usage) => {

View File

@ -54,7 +54,6 @@ export const oaCompatHelper: ProviderHelper = () => ({
usage = json.usage usage = json.usage
}, },
retrieve: () => usage, retrieve: () => usage,
buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ choices: [], cost })}\n\n`,
} }
}, },
normalizeUsage: (usage: Usage) => { normalizeUsage: (usage: Usage) => {

View File

@ -44,7 +44,6 @@ export const openaiHelper: ProviderHelper = () => ({
usage = json.response.usage usage = json.response.usage
}, },
retrieve: () => usage, retrieve: () => usage,
buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`,
} }
}, },
normalizeUsage: (usage: Usage) => { normalizeUsage: (usage: Usage) => {

View File

@ -43,7 +43,6 @@ export type ProviderHelper = (input: { reqModel: string; providerModel: string }
createUsageParser: () => { createUsageParser: () => {
parse: (chunk: string) => void parse: (chunk: string) => void
retrieve: () => any retrieve: () => any
buidlCostChunk: (cost: string) => string
} }
normalizeUsage: (usage: any) => UsageInfo 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) { export function createBodyConverter(from: ZenData.Format, to: ZenData.Format) {
return (body: any): any => { return (body: any): any => {
if (from === to) return body if (from === to) return body