Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions sdk/src/impl/__tests__/usage-receipts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,79 @@ const logger = {
},
}

describe('credit calculation from OpenRouter usage accounting', () => {
test('takes max of cost and upstream_inference_cost, never the sum', async () => {
// usage.cost is the total amount charged and already includes the
// upstream portion reported separately in cost_details
// (issue #1164): summing them roughly doubles credits on normal
// OpenRouter routes. BYOK routes carry the spend in upstream with
// cost = 0, which max handles and sum would zero out.
const costs: number[] = []
const chunks = [
{
id: 'chatcmpl-credits-1',
object: 'chat.completion.chunk',
created: 1,
model: 'test-model',
choices: [
{
index: 0,
delta: { content: 'hello' },
finish_reason: null,
},
],
},
{
id: 'chatcmpl-credits-1',
object: 'chat.completion.chunk',
created: 1,
model: 'test-model',
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
usage: {
prompt_tokens: 100,
completion_tokens: 20,
total_tokens: 120,
cost: 0.01,
cost_details: { upstream_inference_cost: 0.02 },
},
},
]
globalThis.fetch = (() =>
Promise.resolve(
new Response(
`${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join('')}data: [DONE]\n\n`,
{ headers: { 'Content-Type': 'text/event-stream' } },
),
)) as unknown as typeof fetch

const stream = promptAiSdkStream({
apiKey: 'test-key',
runId: 'run-credits-1',
messages: [{ role: 'user', content: 'hello' }],
clientSessionId: 'session-credits-1',
fingerprintId: 'fingerprint-credits-1',
model: 'openai/gpt-5.6-luna',
userId: 'user-1',
userInputId: 'input-credits-1',
onCostCalculated: async (credits: number) => {
costs.push(credits)
},
sendAction: async () => undefined,
logger,
trackEvent: async () => undefined,
signal: new AbortController().signal,
} as unknown as Parameters<typeof promptAiSdkStream>[0])

for await (const chunk of stream) {
void chunk
}

expect(costs).toHaveLength(1)
// max(0.01, 0.02) = 0.02 → * 1.055 margin * 100 credits-per-dollar.
expect(costs[0]).toBe(Math.round(0.02 * 1.055 * 100))
})
})

describe('stream usage receipts', () => {
test('reports final usage and cost before yielding an output-limit recovery', async () => {
const usage: Array<Record<string, number | undefined>> = []
Expand Down
24 changes: 16 additions & 8 deletions sdk/src/impl/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,15 @@ export async function* promptAiSdkStream(
const openrouterUsage = providerMetadata?.codebuff?.usage as
| OpenRouterUsageAccounting
| undefined
// usage.cost is the total charged and already contains the upstream
// portion reported separately in cost_details.upstream_inference_cost
// (see the Solar Pro 4 BYOK note in common/src/constants/freebuff-models.ts,
// where cost is 0 and upstream carries the real spend): max, never sum.
const costOverrideDollars = openrouterUsage
? (openrouterUsage.cost ?? 0) +
(openrouterUsage.costDetails?.upstreamInferenceCost ?? 0)
? Math.max(
openrouterUsage.cost ?? 0,
openrouterUsage.costDetails?.upstreamInferenceCost ?? 0,
)
: undefined
if (!params.onCostCalculated || !costOverrideDollars) return
costReported = true
Expand Down Expand Up @@ -732,9 +738,10 @@ export async function promptAiSdk(
const openrouterUsage = providerMetadata.codebuff
.usage as OpenRouterUsageAccounting

costOverrideDollars =
(openrouterUsage.cost ?? 0) +
(openrouterUsage.costDetails?.upstreamInferenceCost ?? 0)
costOverrideDollars = Math.max(
openrouterUsage.cost ?? 0,
openrouterUsage.costDetails?.upstreamInferenceCost ?? 0,
)
}
}

Expand Down Expand Up @@ -803,9 +810,10 @@ export async function promptAiSdkStructured<T>(
const openrouterUsage = providerMetadata.codebuff
.usage as OpenRouterUsageAccounting

costOverrideDollars =
(openrouterUsage.cost ?? 0) +
(openrouterUsage.costDetails?.upstreamInferenceCost ?? 0)
costOverrideDollars = Math.max(
openrouterUsage.cost ?? 0,
openrouterUsage.costDetails?.upstreamInferenceCost ?? 0,
)
}
}

Expand Down
Loading