-
Notifications
You must be signed in to change notification settings - Fork 135
Description
Description
When using the OpenRouter AI SDK provider with certain API endpoints (e.g., Kilo Gateway at https://api.kilo.ai/api/openrouter), the standard usage object in the finish-step event contains undefined/NaN values, while the actual usage data is correctly populated in providerMetadata.openrouter.usage.
Steps to Reproduce
- Configure the OpenRouter provider with a custom baseURL (e.g., Kilo Gateway)
- Make a streaming request to a model like
z-ai/glm-5:free - Observe the
finish-stepevent
Expected Behavior
The standard usage object should contain valid token counts:
{
"inputTokens": 12004,
"outputTokens": 40,
"cachedInputTokens": 10880,
"reasoningTokens": 20
}Actual Behavior
The standard usage object has undefined values:
{
"inputTokens": undefined,
"outputTokens": undefined,
"inputTokenDetails": {},
"outputTokenDetails": {}
}While providerMetadata.openrouter.usage correctly contains:
{
"promptTokens": 12004,
"completionTokens": 40,
"promptTokensDetails": { "cachedTokens": 10880 },
"completionTokensDetails": { "reasoningTokens": 20 },
"cost": 0.0001714,
"totalTokens": 12044
}Analysis
Looking at the SDK source code:
- In streaming mode,
usageis initialized withNumber.NaNvalues (around line 1923):
const usage = {
inputTokens: Number.NaN,
outputTokens: Number.NaN,
...
};-
The SDK expects streaming chunks to contain
value.usage.prompt_tokens(snake_case, line 1971-1999) -
The SDK also populates
openrouterUsageseparately and includes it inproviderMetadata -
On
flush(), both objects are emitted:
controller.enqueue({
type: "finish",
finishReason,
usage, // May still be NaN if never populated
providerMetadata: {
openrouter: { usage: openrouterUsage } // Correctly populated
}
});The issue appears to be that the Kilo Gateway API (or potentially other OpenRouter-compatible APIs) sends usage data in a format or timing that doesn't trigger the population of the standard usage object, but does populate openrouterUsage.
Suggested Fix
The SDK could copy data from openrouterUsage to the standard usage object in the flush() handler if the standard usage values are still NaN:
flush(controller) {
// Before emitting, ensure standard usage is populated from openrouterUsage if needed
if (Number.isNaN(usage.inputTokens) && openrouterUsage.promptTokens != null) {
usage.inputTokens = openrouterUsage.promptTokens;
usage.outputTokens = openrouterUsage.completionTokens ?? 0;
usage.cachedInputTokens = openrouterUsage.promptTokensDetails?.cachedTokens ?? 0;
usage.reasoningTokens = openrouterUsage.completionTokensDetails?.reasoningTokens ?? 0;
usage.totalTokens = openrouterUsage.totalTokens ?? (usage.inputTokens + usage.outputTokens);
}
// ... rest of flush
}Environment
- @openrouter/ai-sdk-provider version: 1.5.4
- Node.js/Bun runtime
- Custom baseURL:
https://api.kilo.ai/api/openrouter
Workaround
For now, consumers can fall back to extracting usage from providerMetadata.openrouter.usage when the standard usage is empty. See: link-assistant/agent#188
Related Issues
- No Reasoning tokens #22 (similar issue with reasoning tokens)