Skip to content

Commit 1047e40

Browse files
feat: cost for OpenRouter image models (#782)
* feat: cost for OpenRouter image models * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 18e5f4d commit 1047e40

3 files changed

Lines changed: 52 additions & 4 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@tanstack/ai-openrouter': patch
3+
---
4+
5+
Surface OpenRouter provider-reported cost on image generation usage, matching
6+
the existing text adapter behavior.

packages/ai-openrouter/src/adapters/image.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
generateId as utilGenerateId,
77
} from '../utils'
88
import { buildOpenRouterUsage } from '../usage'
9+
import { extractUsageCost } from './cost'
910
import type { OpenRouterClientConfig } from '../utils'
1011
import type {
1112
OpenRouterImageModelInputModalitiesByName,
@@ -203,10 +204,12 @@ export class OpenRouterImageAdapter<
203204

204205
// OpenRouter routes image generation through the chat surface, so the
205206
// response carries the same `usage` shape as text. Surface it (with any
206-
// detail breakdowns) when present.
207-
const usage = response.usage
208-
? buildOpenRouterUsage(response.usage)
209-
: undefined
207+
// detail breakdowns and provider-reported cost) when present.
208+
const baseUsage = buildOpenRouterUsage(response.usage)
209+
const usage = baseUsage && {
210+
...baseUsage,
211+
...extractUsageCost(response.usage),
212+
}
210213

211214
return {
212215
id: response.id || this.generateId(),

packages/ai-openrouter/tests/image-adapter.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,45 @@ describe('OpenRouter Image Adapter', () => {
111111
})
112112
})
113113

114+
it('surfaces provider-reported cost from OpenRouter image usage', async () => {
115+
const mockResponse = {
116+
...createMockImageResponse([{ url: 'https://example.com/image1.png' }]),
117+
usage: {
118+
completionTokens: 1291,
119+
cost: 0.0387076,
120+
cost_details: {
121+
upstream_inference_completions_cost: 0.0387025,
122+
upstream_inference_cost: 0.0387076,
123+
upstream_inference_prompt_cost: 0.0000051,
124+
},
125+
promptTokens: 17,
126+
totalTokens: 1308,
127+
},
128+
}
129+
130+
mockSend = vi.fn().mockResolvedValueOnce(mockResponse)
131+
132+
const adapter = createAdapter()
133+
134+
const result = await adapter.generateImages({
135+
model: 'google/gemini-2.5-flash-image',
136+
prompt: 'A futuristic city at sunset',
137+
logger: testLogger,
138+
})
139+
140+
expect(result.usage).toMatchObject({
141+
promptTokens: 17,
142+
completionTokens: 1291,
143+
totalTokens: 1308,
144+
cost: 0.0387076,
145+
costDetails: {
146+
upstreamOutputCost: 0.0387025,
147+
upstreamCost: 0.0387076,
148+
upstreamInputCost: 0.0000051,
149+
},
150+
})
151+
})
152+
114153
it('generates multiple images', async () => {
115154
const mockResponse = createMockImageResponse([
116155
{ url: 'https://example.com/image1.png' },

0 commit comments

Comments
 (0)