Q65: every call path records real tokens, cache, reasoning and cost

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 11:24:54 -04:00
1 parent 1367a8900e
commit c2f8e656f6
15 files changed
+187 -37

No files matched your search

+6
View File
@@ -186,12 +186,14 @@ export const providerAdapters: ProviderAdapterRegistry = {
label: "xAI",
endpoint: "https://api.x.ai/v1/chat/completions",
healthUrl: "https://api.x.ai/v1/models",
requestStreamUsage: true,
}),
groq: openAiCompatibleAdapter({
providerKey: "groq",
label: "Groq",
endpoint: "https://api.groq.com/openai/v1/chat/completions",
healthUrl: "https://api.groq.com/openai/v1/models",
requestStreamUsage: true,
}),
mistral: openAiCompatibleAdapter({
providerKey: "mistral",
@@ -367,11 +369,14 @@ function openAiCompatibleAdapter({
label,
endpoint,
healthUrl,
requestStreamUsage = false,
}: {
providerKey: string;
label: string;
endpoint: string;
healthUrl: string;
/** Providers that report a stream's token usage only when asked (xAI, Groq). */
requestStreamUsage?: boolean;
}): ProviderAdapter {
return {
providerKey,
@@ -394,6 +399,7 @@ function openAiCompatibleAdapter({
model: input.model,
messages,
stream: true,
...(requestStreamUsage ? { stream_options: { include_usage: true } } : {}),
});
return { events: mapOpenAiChatCompletionStream(response.messages), rateLimitState: response.rateLimitState };
},
@@ -170,7 +170,10 @@ export class ProviderBackgroundService {
startedAt,
action: "background_response_completed",
});
this.requestStore.recordUsage(request.org_id, provider.id, request.input_char_count, responseText.length);
this.requestStore.settleUsage({
orgId: request.org_id, requestId: request.id, provider, rawResponseJson: result.rawResponseJson,
inputCharCount: request.input_char_count, outputCharCount: responseText.length,
});
return;
}
if (CANCELLED_BACKGROUND_STATUSES.has(result.status)) {
+36 -5
View File
@@ -1,7 +1,10 @@
import type { DatabaseSync } from "node:sqlite";
import { PROVIDER_EXECUTION_MODE, PROVIDER_REQUEST_STATE, type ProviderExecutionMode } from "../../shared/domain";
import { createId } from "../../shared/ids";
import type { TokenUsage } from "./spend";
import { providerPrices, readTokenUsage, requestCostCents, type TokenUsage } from "./spend";
/** Providers already logged as not reporting token usage. */
const providersWithoutUsage = new Set<string>();
import { currentProviderUsageWindow } from "../../shared/provider-runtime-domain";
export interface ProviderRequestOwner {
@@ -269,20 +272,48 @@ export class ProviderRequestStore {
return request;
}
recordUsage(orgId: string, providerId: string, inputCharCount: number, outputCharCount: number): void {
/** Settles a successful call: reads the provider's token usage, prices it, and adds it to the request and the month's totals. */
settleUsage(input: { orgId: string; requestId: string; provider: { id: string; provider_key: string }; rawResponseJson: string; inputCharCount: number; outputCharCount: number }): void {
const usage = readTokenUsage(input.rawResponseJson);
if (!usage) {
if (!providersWithoutUsage.has(input.provider.provider_key)) console.warn(`[spend] ${input.provider.provider_key} returned no token usage; its calls cost nothing on record.`);
providersWithoutUsage.add(input.provider.provider_key);
this.recordUsage(input.orgId, input.provider.id, input.inputCharCount, input.outputCharCount, null);
return;
}
const costCents = requestCostCents(usage, providerPrices(this.db, input.provider.id));
this.recordSpend(input.requestId, usage, costCents);
this.recordUsage(input.orgId, input.provider.id, input.inputCharCount, input.outputCharCount, { usage, costCents });
}
/** Adds one call to the provider's monthly totals; spend is null when the provider reported no token usage. */
recordUsage(orgId: string, providerId: string, inputCharCount: number, outputCharCount: number, spend: { usage: TokenUsage; costCents: number | null } | null): void {
const window = currentProviderUsageWindow();
const now = new Date().toISOString();
const usage = spend?.usage;
this.db.prepare(`
insert into provider_usage_windows(
id, org_id, provider_id, window_start, window_end, request_count,
input_char_count, output_char_count, created_at, updated_at
) values (?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
input_char_count, output_char_count, created_at, updated_at,
input_tokens, output_tokens, cached_input_tokens, reasoning_tokens, cost_cents, requests_without_usage, unpriced_requests
) values (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict(provider_id, window_start) do update set
request_count = request_count + 1,
input_char_count = input_char_count + excluded.input_char_count,
output_char_count = output_char_count + excluded.output_char_count,
input_tokens = input_tokens + excluded.input_tokens,
output_tokens = output_tokens + excluded.output_tokens,
cached_input_tokens = cached_input_tokens + excluded.cached_input_tokens,
reasoning_tokens = reasoning_tokens + excluded.reasoning_tokens,
cost_cents = cost_cents + excluded.cost_cents,
requests_without_usage = requests_without_usage + excluded.requests_without_usage,
unpriced_requests = unpriced_requests + excluded.unpriced_requests,
updated_at = excluded.updated_at
`).run(createId("puw"), orgId, providerId, window.start, window.end, inputCharCount, outputCharCount, now, now);
`).run(
createId("puw"), orgId, providerId, window.start, window.end, inputCharCount, outputCharCount, now, now,
usage?.inputTokens ?? 0, usage?.outputTokens ?? 0, usage?.cachedInputTokens ?? 0, usage?.reasoningTokens ?? 0,
spend?.costCents ?? 0, spend ? 0 : 1, spend && spend.costCents === null ? 1 : 0,
);
}
}
@@ -78,7 +78,7 @@ export class ProviderResponseObserver {
});
// A late error on a request already cancelled or finished changes nothing.
if (!failed) return;
this.requestStore.recordUsage(input.orgId, input.provider.id, input.inputCharCount, 0);
this.requestStore.recordUsage(input.orgId, input.provider.id, input.inputCharCount, 0, null);
this.recordProviderHealth(input.provider.id, input.orgId, HEALTH_STATE_FAILED, message);
this.operations.recordSignal({
orgId: input.orgId,
+14 -18
View File
@@ -17,7 +17,7 @@ import { type AgentTurnMessage, type AgentTurnResult, type ToolDefinition, type
import { selectToolTurn, supportsToolTurn } from "./provider-tool-turns/router";
import { resolveTurnCredential } from "./provider-tool-turns/credential-resolver";
import { currentRunId } from "./run-control";
import { budgetBlock, isPriced, providerPrices, readTokenUsage, requestCostCents } from "./spend";
import { budgetBlock, isPriced, providerPrices } from "./spend";
import {
HEALTH_STATE_HEALTHY,
WORKSPACE_SCOPES,
@@ -49,9 +49,6 @@ export interface ProviderResponseRunnerDeps {
}
export class ProviderResponseRunner {
/** Providers already logged as not reporting token usage. */
private readonly unreportedUsage = new Set<string>();
constructor(private readonly deps: ProviderResponseRunnerDeps) {}
async createResponse(input: ProviderResponseCreateInput): Promise<string> {
@@ -87,7 +84,10 @@ export class ProviderResponseRunner {
startedAt,
action: "response_created",
});
this.deps.requestStore.recordUsage(input.orgId, execution.provider.id, execution.prompt.length, result.text.length);
this.deps.requestStore.settleUsage({
orgId: input.orgId, requestId, provider: execution.provider, rawResponseJson: result.rawResponseJson,
inputCharCount: execution.prompt.length, outputCharCount: result.text.length,
});
return requestId;
} catch (error) {
this.deps.responseObserver.recordFailure({
@@ -163,8 +163,10 @@ export class ProviderResponseRunner {
startedAt,
action: "response_created",
});
this.deps.requestStore.recordUsage(input.orgId, provider.id, promptChars, result.text.length);
this.recordSpend(requestId, owner, result.rawResponseJson);
this.deps.requestStore.settleUsage({
orgId: input.orgId, requestId, provider: owner, rawResponseJson: result.rawResponseJson,
inputCharCount: promptChars, outputCharCount: result.text.length,
});
return { ...result, requestId };
} catch (error) {
this.deps.responseObserver.recordFailure({
@@ -180,16 +182,6 @@ export class ProviderResponseRunner {
}
}
private recordSpend(requestId: string, provider: { id: string; provider_key: string }, rawResponseJson: string): void {
const usage = readTokenUsage(rawResponseJson);
if (!usage) {
if (!this.unreportedUsage.has(provider.provider_key)) console.warn(`[spend] ${provider.provider_key} returned no token usage; its calls cost nothing on record.`);
this.unreportedUsage.add(provider.provider_key);
return;
}
this.deps.requestStore.recordSpend(requestId, usage, requestCostCents(usage, providerPrices(this.deps.db, provider.id)));
}
async streamResponse(input: StreamProviderResponseInput, onEvent: (event: ProviderStreamRendererEvent) => void): Promise<string> {
const execution = this.prepareExecution(input);
const requestId = this.createRequestRecord(execution);
@@ -209,6 +201,7 @@ export class ProviderResponseRunner {
for await (const event of result.events) {
if (event.type === "error") throw new Error(event.textDelta ?? "Provider stream failed.");
rawEvents.push(event.rawEventJson);
if (event.type === "usage") continue;
if (event.providerResponseId) providerResponseId = event.providerResponseId;
if (event.textDelta) textParts.push(event.textDelta);
const responseText = textParts.join("");
@@ -258,7 +251,10 @@ export class ProviderResponseRunner {
startedAt,
action: "response_streamed",
});
this.deps.requestStore.recordUsage(input.orgId, execution.provider.id, execution.prompt.length, responseText.length);
this.deps.requestStore.settleUsage({
orgId: input.orgId, requestId, provider: execution.provider, rawResponseJson: JSON.stringify(rawEvents),
inputCharCount: execution.prompt.length, outputCharCount: responseText.length,
});
return requestId;
} catch (error) {
this.deps.responseObserver.recordFailure({
+9 -1
View File
@@ -3,7 +3,8 @@ import type { JsonRecord, ProviderEventStreamMessage } from "./provider-http";
import { isRecord } from "./provider-http";
export interface ProviderStreamEvent {
type: "message_start" | "text_delta" | "message_stop" | "error";
/** usage: a chunk that only reports token usage; it is recorded, never shown. */
type: "message_start" | "text_delta" | "message_stop" | "error" | "usage";
textDelta: string | null;
providerResponseId: string | null;
rawEventJson: string;
@@ -71,6 +72,8 @@ export async function* mapOpenAiChatCompletionStream(messages: AsyncIterable<Pro
yield streamEvent("message_start", null, message);
}
yield streamEvent("message_stop", null, message);
} else if (!delta && isRecord(message.data.usage)) {
yield streamEvent("usage", null, message);
}
}
}
@@ -84,6 +87,11 @@ export async function* mapAnthropicMessageStream(messages: AsyncIterable<Provide
yield streamEvent("message_start", null, message);
continue;
}
// Anthropic reports output tokens on message_delta, input tokens on message_start.
if (type === "message_delta") {
yield streamEvent("usage", null, message);
continue;
}
if (type === "content_block_delta") {
const delta = readAnthropicTextDelta(message.data);
if (delta) {
+14 -9
View File
@@ -19,12 +19,15 @@ export interface TokenPrices {
/** Reads token usage from a provider's raw response: one JSON body, or the list of a stream's event payloads. */
export function readTokenUsage(rawResponseJson: string): TokenUsage | null {
const parsed = JSON.parse(rawResponseJson) as unknown;
const bodies = Array.isArray(parsed) ? parsed.map(parseEvent) : [parsed];
for (let index = bodies.length - 1; index >= 0; index -= 1) {
const usage = usageIn(bodies[index]);
if (usage) return usage;
}
return null;
const reports = (Array.isArray(parsed) ? parsed.map(parseEvent) : [parsed])
.map(usageIn)
.filter((usage): usage is TokenUsage => usage !== null);
if (reports.length === 0) return null;
// A stream may report input early and output late (Anthropic): the last report wins, input taken from the last one that has it.
const last = reports[reports.length - 1];
if (last.inputTokens > 0) return last;
const withInput = [...reports].reverse().find((usage) => usage.inputTokens > 0);
return withInput ? { ...last, inputTokens: withInput.inputTokens, cachedInputTokens: withInput.cachedInputTokens } : last;
}
/** True when calls to this provider cost money on record. */
@@ -94,7 +97,9 @@ function usageIn(body: unknown): TokenUsage | null {
if (!isRecord(body)) return null;
const nested = isRecord(body.response) ? body.response : null;
const firstChoice = Array.isArray(body.choices) && isRecord(body.choices[0]) ? body.choices[0] : null;
const openAiStyle = record(body.usage) ?? record(nested?.usage) ?? record(isRecord(body.x_groq) ? body.x_groq.usage : null) ?? record(firstChoice?.usage);
const message = isRecord(body.message) ? body.message : null;
const openAiStyle = record(body.usage) ?? record(nested?.usage) ?? record(message?.usage)
?? record(isRecord(body.x_groq) ? body.x_groq.usage : null) ?? record(firstChoice?.usage);
if (openAiStyle) return fromOpenAiOrAnthropic(openAiStyle);
const gemini = record(body.usageMetadata) ?? record(nested?.usageMetadata);
if (gemini) return fromGemini(gemini);
@@ -120,11 +125,11 @@ function fromOpenAiOrAnthropic(usage: Record<string, unknown>): TokenUsage | nul
reasoningTokens: count(record(usage.completion_tokens_details)?.reasoning_tokens),
};
}
if (typeof usage.input_tokens !== "number") return null;
if (typeof usage.input_tokens !== "number" && typeof usage.output_tokens !== "number") return null;
// Anthropic reports cache reads and writes beside input_tokens; Responses reports them inside it.
const anthropicCache = count(usage.cache_read_input_tokens) + count(usage.cache_creation_input_tokens);
return {
inputTokens: usage.input_tokens + anthropicCache,
inputTokens: count(usage.input_tokens) + anthropicCache,
outputTokens: count(usage.output_tokens),
cachedInputTokens: count(usage.cache_read_input_tokens) + count(record(usage.input_tokens_details)?.cached_tokens),
reasoningTokens: count(record(usage.output_tokens_details)?.reasoning_tokens),