Skip to content

Commit 7cfcbb1

Browse files
ImBIOSlouisgv
andauthored
feat: add usage accounting support to OpenRouter (#64)
* feat: add usage accounting support to OpenRouter - Updated README.md to include usage accounting use cases and examples. - Enhanced OpenRouterChatLanguageModel to support usage accounting settings and metadata extraction. - Implemented detailed usage information tracking in responses. - Added tests for usage accounting functionality, ensuring correct metadata inclusion based on settings. Tested: - Verified that usage accounting data is included in responses when enabled. - Confirmed that provider-specific metadata is omitted when usage accounting is disabled. * fix: update OpenRouterSharedSettings type definition - Moved `OpenRouterSharedSettings` type definition to the correct location in `src/types.ts`. - Added `extraBody` property to `OpenRouterSharedSettings`. - Marked `includeReasoning` as deprecated, suggesting to use `reasoning` instead. Tested: - No functional changes, but type definitions are now correctly structured. * chore: release version 0.5.0 with usage accounting support - Added support for OpenRouter usage accounting feature, enabling tracking of token usage details in API responses. - Updated schema to accommodate the new usage accounting response format. - Documented changes in CHANGELOG.md. Tested: - Verified that usage accounting data is correctly included in both streaming and non-streaming API responses. * feat: refactor usage accounting schema in OpenRouter - Introduced `openRouterUsageAccountingSchema` to centralize usage accounting details. - Updated `OpenRouterChatCompletionBaseResponseSchema` to utilize the new schema for cleaner code. - Added TypeScript type inference for `OpenRouterUsageAccounting` in `src/types.ts`. Tested: - Ensured that the new schema correctly captures token usage details in API responses. * chore: update version to 0.5.0-canary.1 and adjust CHANGELOG.md - Changed package version from 0.5.0 to 0.5.0-canary.1 to indicate a pre-release. - Updated CHANGELOG.md to reflect the version as [Unreleased] for upcoming changes. No functional changes; this prepares the package for the next development phase. * chore: update version to 0.5.0-canary.2 and refactor usage accounting schema - Bumped package version to 0.5.0-canary.2. - Refactored `OpenRouterChatCompletionBaseResponseSchema` to directly define the usage accounting schema inline, improving clarity and maintainability. - Updated `OpenRouterUsageAccounting` type definition to match the new schema structure. No functional changes; this prepares the package for further development. * chore: update pnpm and tsconfig * chore: add e2e test * chore: add example env.e2e * use kebab case for key * chore: bump deps --------- Co-authored-by: lab <[email protected]>
1 parent f8e1583 commit 7cfcbb1

21 files changed

+774
-77
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
node_modules
22
dist
33
./internal
4+
5+
.env.e2e*

CHANGELOG.md

Lines changed: 0 additions & 13 deletions
This file was deleted.

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const { text } = await generateText({
3737

3838
## Supported models
3939

40-
This list is not a definitive list of models supported by OpenRouter, as it constantly changes as we add new models (and deprecate old ones) to our system.
40+
This list is not a definitive list of models supported by OpenRouter, as it constantly changes as we add new models (and deprecate old ones) to our system.
4141
You can find the latest list of models supported by OpenRouter [here](https://openrouter.ai/models).
4242

4343
You can find the latest list of tool-supported models supported by OpenRouter [here](https://openrouter.ai/models?order=newest&supported_parameters=tools). (Note: This list may contain models that are not compatible with the AI SDK.)
@@ -154,3 +154,30 @@ await streamText({
154154
],
155155
});
156156
```
157+
158+
## Use Cases
159+
160+
### Usage Accounting
161+
162+
The provider supports [OpenRouter usage accounting](https://openrouter.ai/docs/use-cases/usage-accounting), which allows you to track token usage details directly in your API responses, without making additional API calls.
163+
164+
```typescript
165+
// Enable usage accounting
166+
const model = openrouter('openai/gpt-3.5-turbo', {
167+
usage: {
168+
include: true,
169+
}
170+
});
171+
172+
// Access usage accounting data
173+
const result = await generateText({
174+
model,
175+
prompt: 'Hello, how are you today?',
176+
});
177+
178+
// Provider-specific usage details (available in providerMetadata)
179+
if (result.providerMetadata?.openrouter?.usage) {
180+
console.log('Cost:', result.providerMetadata.openrouter.usage.cost);
181+
console.log('Total Tokens:', result.providerMetadata.openrouter.usage.totalTokens);
182+
}
183+
```

e2e/usage-accounting.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { createOpenRouter } from '@/src';
2+
import { streamText } from 'ai';
3+
import { it } from 'vitest';
4+
5+
it('receive usage accounting', async () => {
6+
const openrouter = createOpenRouter({
7+
apiKey: process.env.OPENROUTER_API_KEY,
8+
baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
9+
});
10+
const model = openrouter('anthropic/claude-3.7-sonnet:thinking', {
11+
usage: {
12+
include: true,
13+
},
14+
});
15+
const response = streamText({
16+
model,
17+
messages: [
18+
{
19+
role: 'user',
20+
content: 'What is the capital of France?',
21+
},
22+
],
23+
onFinish(e) {
24+
expect(e.providerMetadata?.openrouter).toMatchObject({
25+
usage: expect.objectContaining({
26+
promptTokens: expect.any(Number),
27+
completionTokens: expect.any(Number),
28+
promptTokensDetails: expect.any(Object),
29+
completionTokensDetails: expect.any(Object),
30+
totalTokens: expect.any(Number),
31+
cost: expect.any(Number),
32+
}),
33+
});
34+
},
35+
});
36+
37+
await response.consumeStream();
38+
const providerMetadata = await response.providerMetadata;
39+
// You can use expect.any(Type) or expect.objectContaining for schema-like matching
40+
expect(providerMetadata?.openrouter).toMatchObject({
41+
usage: expect.objectContaining({
42+
promptTokens: expect.any(Number),
43+
completionTokens: expect.any(Number),
44+
promptTokensDetails: expect.any(Object),
45+
completionTokensDetails: expect.any(Object),
46+
totalTokens: expect.any(Number),
47+
cost: expect.any(Number),
48+
}),
49+
});
50+
});

example.env.e2e

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Rename to .env.e2e
2+
OPENROUTER_API_BASE=https://openrouter.ai
3+
OPENROUTER_API_KEY=you-api-key-here

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openrouter/ai-sdk-provider",
3-
"version": "0.4.6",
3+
"version": "0.5.0",
44
"license": "Apache-2.0",
55
"sideEffects": false,
66
"main": "./dist/index.js",
@@ -20,7 +20,8 @@
2020
"prepublish": "pnpm run build",
2121
"test": "pnpm test:node && pnpm test:edge",
2222
"test:edge": "vitest --config vitest.edge.config.ts --run",
23-
"test:node": "vitest --config vitest.node.config.ts --run"
23+
"test:node": "vitest --config vitest.node.config.ts --run",
24+
"test:e2e": "vitest --config vitest.e2e.config.ts --run"
2425
},
2526
"exports": {
2627
"./package.json": "./package.json",
@@ -41,6 +42,7 @@
4142
"@ai-sdk/provider-utils": "2.1.10"
4243
},
4344
"devDependencies": {
45+
"@biomejs/biome": "1.9.4",
4446
"@ianvs/prettier-plugin-sort-imports": "4.4.1",
4547
"@edge-runtime/vm": "5.0.0",
4648
"@types/jest": "29.5.14",
@@ -50,6 +52,8 @@
5052
"prettier": "3.5.2",
5153
"typescript": "5.7.3",
5254
"vitest": "3.0.7",
55+
"vite-tsconfig-paths": "5.1.4",
56+
"dotenv": "16.5.0",
5357
"zod": "3.24.2"
5458
},
5559
"peerDependencies": {
@@ -72,5 +76,5 @@
7276
"keywords": [
7377
"ai"
7478
],
75-
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
79+
"packageManager": "pnpm@10.11.0"
7680
}

pnpm-lock.yaml

Lines changed: 141 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
onlyBuiltDependencies:
2+
- '@biomejs/biome'

0 commit comments

Comments
 (0)