@openrouter/ai-sdk-provider 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ const { text } = await generateText({
37
37
 
38
38
  ## Supported models
39
39
 
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.
41
41
  You can find the latest list of models supported by OpenRouter [here](https://openrouter.ai/models).
42
42
 
43
43
  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.)
@@ -126,21 +126,58 @@ await streamText({
126
126
  messages: [
127
127
  {
128
128
  role: 'system',
129
- content: 'You are a helpful assistant.',
130
- // Add provider options at the message level
131
- providerMetadata: {
132
- // openrouter or anthropic
133
- openrouter: {
134
- // cache_control also works
135
- // cache_control: { type: 'ephemeral' }
136
- cacheControl: { type: 'ephemeral' },
137
- },
138
- },
129
+ content:
130
+ 'You are a podcast summary assistant. You are detail oriented and critical about the content.',
139
131
  },
140
132
  {
141
133
  role: 'user',
142
- content: 'Hello, how are you?',
134
+ content: [
135
+ {
136
+ type: 'text',
137
+ text: 'Given the text body below:',
138
+ },
139
+ {
140
+ type: 'text',
141
+ text: `<LARGE BODY OF TEXT>`,
142
+ providerOptions: {
143
+ openrouter: {
144
+ cacheControl: { type: 'ephemeral' },
145
+ },
146
+ },
147
+ },
148
+ {
149
+ type: 'text',
150
+ text: 'List the speakers?',
151
+ },
152
+ ],
143
153
  },
144
154
  ],
145
155
  });
146
156
  ```
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
+ ```
package/dist/index.d.mts CHANGED
@@ -27,7 +27,33 @@ type OpenRouterSharedSettings = OpenRouterProviderOptions & {
27
27
  * @deprecated use `reasoning` instead
28
28
  */
29
29
  includeReasoning?: boolean;
30
- extraBody?: Record<string, any>;
30
+ extraBody?: Record<string, unknown>;
31
+ /**
32
+ * Enable usage accounting to get detailed token usage information.
33
+ * https://openrouter.ai/docs/use-cases/usage-accounting
34
+ */
35
+ usage?: {
36
+ /**
37
+ * When true, includes token usage information in the response.
38
+ */
39
+ include: boolean;
40
+ };
41
+ };
42
+ /**
43
+ * Usage accounting response
44
+ * @see https://openrouter.ai/docs/use-cases/usage-accounting
45
+ */
46
+ type OpenRouterUsageAccounting = {
47
+ promptTokens: number;
48
+ promptTokensDetails?: {
49
+ cachedTokens: number;
50
+ };
51
+ completionTokens: number;
52
+ completionTokensDetails?: {
53
+ reasoningTokens: number;
54
+ };
55
+ totalTokens: number;
56
+ cost?: number;
31
57
  };
32
58
 
33
59
  type OpenRouterChatModelId = string;
@@ -236,4 +262,4 @@ declare class OpenRouter {
236
262
  completion(modelId: OpenRouterCompletionModelId, settings?: OpenRouterCompletionSettings): OpenRouterCompletionLanguageModel;
237
263
  }
238
264
 
239
- export { OpenRouter, type OpenRouterCompletionSettings, type OpenRouterLanguageModel, type OpenRouterProvider, type OpenRouterProviderOptions, type OpenRouterProviderSettings, type OpenRouterSharedSettings, createOpenRouter, openrouter };
265
+ export { OpenRouter, type OpenRouterCompletionSettings, type OpenRouterLanguageModel, type OpenRouterProvider, type OpenRouterProviderOptions, type OpenRouterProviderSettings, type OpenRouterSharedSettings, type OpenRouterUsageAccounting, createOpenRouter, openrouter };
package/dist/index.d.ts CHANGED
@@ -27,7 +27,33 @@ type OpenRouterSharedSettings = OpenRouterProviderOptions & {
27
27
  * @deprecated use `reasoning` instead
28
28
  */
29
29
  includeReasoning?: boolean;
30
- extraBody?: Record<string, any>;
30
+ extraBody?: Record<string, unknown>;
31
+ /**
32
+ * Enable usage accounting to get detailed token usage information.
33
+ * https://openrouter.ai/docs/use-cases/usage-accounting
34
+ */
35
+ usage?: {
36
+ /**
37
+ * When true, includes token usage information in the response.
38
+ */
39
+ include: boolean;
40
+ };
41
+ };
42
+ /**
43
+ * Usage accounting response
44
+ * @see https://openrouter.ai/docs/use-cases/usage-accounting
45
+ */
46
+ type OpenRouterUsageAccounting = {
47
+ promptTokens: number;
48
+ promptTokensDetails?: {
49
+ cachedTokens: number;
50
+ };
51
+ completionTokens: number;
52
+ completionTokensDetails?: {
53
+ reasoningTokens: number;
54
+ };
55
+ totalTokens: number;
56
+ cost?: number;
31
57
  };
32
58
 
33
59
  type OpenRouterChatModelId = string;
@@ -236,4 +262,4 @@ declare class OpenRouter {
236
262
  completion(modelId: OpenRouterCompletionModelId, settings?: OpenRouterCompletionSettings): OpenRouterCompletionLanguageModel;
237
263
  }
238
264
 
239
- export { OpenRouter, type OpenRouterCompletionSettings, type OpenRouterLanguageModel, type OpenRouterProvider, type OpenRouterProviderOptions, type OpenRouterProviderSettings, type OpenRouterSharedSettings, createOpenRouter, openrouter };
265
+ export { OpenRouter, type OpenRouterCompletionSettings, type OpenRouterLanguageModel, type OpenRouterProvider, type OpenRouterProviderOptions, type OpenRouterProviderSettings, type OpenRouterSharedSettings, type OpenRouterUsageAccounting, createOpenRouter, openrouter };
package/dist/index.js CHANGED
@@ -96,7 +96,7 @@ function convertToOpenRouterChatMessages(prompt) {
96
96
  const messageCacheControl = getCacheControl(providerMetadata);
97
97
  const contentParts = content.map(
98
98
  (part) => {
99
- var _a2, _b2, _c2, _d;
99
+ var _a2, _b2, _c2, _d, _e, _f;
100
100
  switch (part.type) {
101
101
  case "text":
102
102
  return {
@@ -118,9 +118,14 @@ function convertToOpenRouterChatMessages(prompt) {
118
118
  };
119
119
  case "file":
120
120
  return {
121
- type: "text",
122
- text: part.data instanceof URL ? part.data.toString() : part.data,
123
- cache_control: (_d = getCacheControl(part.providerMetadata)) != null ? _d : messageCacheControl
121
+ type: "file",
122
+ file: {
123
+ filename: String(
124
+ (_e = (_d = part.providerMetadata) == null ? void 0 : _d.openrouter) == null ? void 0 : _e.filename
125
+ ),
126
+ file_data: part.data instanceof Uint8Array ? `data:${part.mimeType};base64,${(0, import_provider_utils.convertUint8ArrayToBase64)(part.data)}` : `data:${part.mimeType};base64,${part.data}`
127
+ },
128
+ cache_control: (_f = getCacheControl(part.providerMetadata)) != null ? _f : messageCacheControl
124
129
  };
125
130
  default: {
126
131
  const _exhaustiveCheck = part;
@@ -272,7 +277,7 @@ var OpenRouterChatLanguageModel = class {
272
277
  }) {
273
278
  var _a;
274
279
  const type = mode.type;
275
- const extraCallingBody = (_a = providerMetadata == null ? void 0 : providerMetadata["openrouter"]) != null ? _a : {};
280
+ const extraCallingBody = (_a = providerMetadata == null ? void 0 : providerMetadata.openrouter) != null ? _a : {};
276
281
  const baseArgs = __spreadValues(__spreadValues(__spreadValues({
277
282
  // model id:
278
283
  model: this.modelId,
@@ -297,7 +302,8 @@ var OpenRouterChatLanguageModel = class {
297
302
  messages: convertToOpenRouterChatMessages(prompt),
298
303
  // OpenRouter specific settings:
299
304
  include_reasoning: this.settings.includeReasoning,
300
- reasoning: this.settings.reasoning
305
+ reasoning: this.settings.reasoning,
306
+ usage: this.settings.usage
301
307
  }, this.config.extraBody), this.settings.extraBody), extraCallingBody);
302
308
  switch (type) {
303
309
  case "regular": {
@@ -333,7 +339,7 @@ var OpenRouterChatLanguageModel = class {
333
339
  }
334
340
  }
335
341
  async doGenerate(options) {
336
- var _b, _c, _d, _e, _f, _g, _h;
342
+ var _b, _c, _d, _e, _f, _g, _h, _i, _j;
337
343
  const args = this.getArgs(options);
338
344
  const { responseHeaders, value: response } = await (0, import_provider_utils3.postJsonToApi)({
339
345
  url: this.config.url({
@@ -354,14 +360,39 @@ var OpenRouterChatLanguageModel = class {
354
360
  if (!choice) {
355
361
  throw new Error("No choice in response");
356
362
  }
357
- return {
363
+ const usageInfo = response.usage ? {
364
+ promptTokens: (_b = response.usage.prompt_tokens) != null ? _b : 0,
365
+ completionTokens: (_c = response.usage.completion_tokens) != null ? _c : 0
366
+ } : {
367
+ promptTokens: 0,
368
+ completionTokens: 0
369
+ };
370
+ const providerMetadata = {};
371
+ if (response.usage && ((_d = this.settings.usage) == null ? void 0 : _d.include)) {
372
+ providerMetadata.openrouter = {
373
+ usage: {
374
+ promptTokens: response.usage.prompt_tokens,
375
+ promptTokensDetails: response.usage.prompt_tokens_details ? {
376
+ cachedTokens: (_e = response.usage.prompt_tokens_details.cached_tokens) != null ? _e : 0
377
+ } : void 0,
378
+ completionTokens: response.usage.completion_tokens,
379
+ completionTokensDetails: response.usage.completion_tokens_details ? {
380
+ reasoningTokens: (_f = response.usage.completion_tokens_details.reasoning_tokens) != null ? _f : 0
381
+ } : void 0,
382
+ cost: response.usage.cost,
383
+ totalTokens: (_g = response.usage.total_tokens) != null ? _g : 0
384
+ }
385
+ };
386
+ }
387
+ const hasProviderMetadata = Object.keys(providerMetadata).length > 0;
388
+ return __spreadValues({
358
389
  response: {
359
390
  id: response.id,
360
391
  modelId: response.model
361
392
  },
362
- text: (_b = choice.message.content) != null ? _b : void 0,
363
- reasoning: (_c = choice.message.reasoning) != null ? _c : void 0,
364
- toolCalls: (_d = choice.message.tool_calls) == null ? void 0 : _d.map((toolCall) => {
393
+ text: (_h = choice.message.content) != null ? _h : void 0,
394
+ reasoning: (_i = choice.message.reasoning) != null ? _i : void 0,
395
+ toolCalls: (_j = choice.message.tool_calls) == null ? void 0 : _j.map((toolCall) => {
365
396
  var _a2;
366
397
  return {
367
398
  toolCallType: "function",
@@ -371,17 +402,15 @@ var OpenRouterChatLanguageModel = class {
371
402
  };
372
403
  }),
373
404
  finishReason: mapOpenRouterFinishReason(choice.finish_reason),
374
- usage: {
375
- promptTokens: (_f = (_e = response.usage) == null ? void 0 : _e.prompt_tokens) != null ? _f : 0,
376
- completionTokens: (_h = (_g = response.usage) == null ? void 0 : _g.completion_tokens) != null ? _h : 0
377
- },
405
+ usage: usageInfo,
378
406
  rawCall: { rawPrompt, rawSettings },
379
407
  rawResponse: { headers: responseHeaders },
380
408
  warnings: [],
381
409
  logprobs: mapOpenRouterChatLogProbsOutput(choice.logprobs)
382
- };
410
+ }, hasProviderMetadata ? { providerMetadata } : {});
383
411
  }
384
412
  async doStream(options) {
413
+ var _a, _c;
385
414
  const args = this.getArgs(options);
386
415
  const { responseHeaders, value: response } = await (0, import_provider_utils3.postJsonToApi)({
387
416
  url: this.config.url({
@@ -392,7 +421,9 @@ var OpenRouterChatLanguageModel = class {
392
421
  body: __spreadProps(__spreadValues({}, args), {
393
422
  stream: true,
394
423
  // only include stream_options when in strict compatibility mode:
395
- stream_options: this.config.compatibility === "strict" ? { include_usage: true } : void 0
424
+ stream_options: this.config.compatibility === "strict" ? __spreadValues({
425
+ include_usage: true
426
+ }, ((_a = this.settings.usage) == null ? void 0 : _a.include) ? { include_usage: true } : {}) : void 0
396
427
  }),
397
428
  failedResponseHandler: openrouterFailedResponseHandler,
398
429
  successfulResponseHandler: (0, import_provider_utils3.createEventSourceResponseHandler)(
@@ -401,7 +432,7 @@ var OpenRouterChatLanguageModel = class {
401
432
  abortSignal: options.abortSignal,
402
433
  fetch: this.config.fetch
403
434
  });
404
- const _a = args, { messages: rawPrompt } = _a, rawSettings = __objRest(_a, ["messages"]);
435
+ const _b = args, { messages: rawPrompt } = _b, rawSettings = __objRest(_b, ["messages"]);
405
436
  const toolCalls = [];
406
437
  let finishReason = "other";
407
438
  let usage = {
@@ -409,11 +440,13 @@ var OpenRouterChatLanguageModel = class {
409
440
  completionTokens: Number.NaN
410
441
  };
411
442
  let logprobs;
443
+ const openrouterUsage = {};
444
+ const shouldIncludeUsageAccounting = !!((_c = this.settings.usage) == null ? void 0 : _c.include);
412
445
  return {
413
446
  stream: response.pipeThrough(
414
447
  new TransformStream({
415
448
  transform(chunk, controller) {
416
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
449
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
417
450
  if (!chunk.success) {
418
451
  finishReason = "error";
419
452
  controller.enqueue({ type: "error", error: chunk.error });
@@ -442,6 +475,20 @@ var OpenRouterChatLanguageModel = class {
442
475
  promptTokens: value.usage.prompt_tokens,
443
476
  completionTokens: value.usage.completion_tokens
444
477
  };
478
+ openrouterUsage.promptTokens = value.usage.prompt_tokens;
479
+ if (value.usage.prompt_tokens_details) {
480
+ openrouterUsage.promptTokensDetails = {
481
+ cachedTokens: (_a2 = value.usage.prompt_tokens_details.cached_tokens) != null ? _a2 : 0
482
+ };
483
+ }
484
+ openrouterUsage.completionTokens = value.usage.completion_tokens;
485
+ if (value.usage.completion_tokens_details) {
486
+ openrouterUsage.completionTokensDetails = {
487
+ reasoningTokens: (_b2 = value.usage.completion_tokens_details.reasoning_tokens) != null ? _b2 : 0
488
+ };
489
+ }
490
+ openrouterUsage.cost = value.usage.cost;
491
+ openrouterUsage.totalTokens = value.usage.total_tokens;
445
492
  }
446
493
  const choice = value.choices[0];
447
494
  if ((choice == null ? void 0 : choice.finish_reason) != null) {
@@ -486,7 +533,7 @@ var OpenRouterChatLanguageModel = class {
486
533
  message: `Expected 'id' to be a string.`
487
534
  });
488
535
  }
489
- if (((_a2 = toolCallDelta.function) == null ? void 0 : _a2.name) == null) {
536
+ if (((_c2 = toolCallDelta.function) == null ? void 0 : _c2.name) == null) {
490
537
  throw new import_provider.InvalidResponseDataError({
491
538
  data: toolCallDelta,
492
539
  message: `Expected 'function.name' to be a string.`
@@ -497,7 +544,7 @@ var OpenRouterChatLanguageModel = class {
497
544
  type: "function",
498
545
  function: {
499
546
  name: toolCallDelta.function.name,
500
- arguments: (_b = toolCallDelta.function.arguments) != null ? _b : ""
547
+ arguments: (_d = toolCallDelta.function.arguments) != null ? _d : ""
501
548
  },
502
549
  sent: false
503
550
  };
@@ -505,7 +552,7 @@ var OpenRouterChatLanguageModel = class {
505
552
  if (toolCall2 == null) {
506
553
  throw new Error("Tool call is missing");
507
554
  }
508
- if (((_c = toolCall2.function) == null ? void 0 : _c.name) != null && ((_d = toolCall2.function) == null ? void 0 : _d.arguments) != null && (0, import_provider_utils3.isParsableJson)(toolCall2.function.arguments)) {
555
+ if (((_e = toolCall2.function) == null ? void 0 : _e.name) != null && ((_f = toolCall2.function) == null ? void 0 : _f.arguments) != null && (0, import_provider_utils3.isParsableJson)(toolCall2.function.arguments)) {
509
556
  controller.enqueue({
510
557
  type: "tool-call-delta",
511
558
  toolCallType: "function",
@@ -516,7 +563,7 @@ var OpenRouterChatLanguageModel = class {
516
563
  controller.enqueue({
517
564
  type: "tool-call",
518
565
  toolCallType: "function",
519
- toolCallId: (_e = toolCall2.id) != null ? _e : (0, import_provider_utils3.generateId)(),
566
+ toolCallId: (_g = toolCall2.id) != null ? _g : (0, import_provider_utils3.generateId)(),
520
567
  toolName: toolCall2.function.name,
521
568
  args: toolCall2.function.arguments
522
569
  });
@@ -528,21 +575,21 @@ var OpenRouterChatLanguageModel = class {
528
575
  if (toolCall == null) {
529
576
  throw new Error("Tool call is missing");
530
577
  }
531
- if (((_f = toolCallDelta.function) == null ? void 0 : _f.arguments) != null) {
532
- toolCall.function.arguments += (_h = (_g = toolCallDelta.function) == null ? void 0 : _g.arguments) != null ? _h : "";
578
+ if (((_h = toolCallDelta.function) == null ? void 0 : _h.arguments) != null) {
579
+ toolCall.function.arguments += (_j = (_i = toolCallDelta.function) == null ? void 0 : _i.arguments) != null ? _j : "";
533
580
  }
534
581
  controller.enqueue({
535
582
  type: "tool-call-delta",
536
583
  toolCallType: "function",
537
584
  toolCallId: toolCall.id,
538
585
  toolName: toolCall.function.name,
539
- argsTextDelta: (_i = toolCallDelta.function.arguments) != null ? _i : ""
586
+ argsTextDelta: (_k = toolCallDelta.function.arguments) != null ? _k : ""
540
587
  });
541
- if (((_j = toolCall.function) == null ? void 0 : _j.name) != null && ((_k = toolCall.function) == null ? void 0 : _k.arguments) != null && (0, import_provider_utils3.isParsableJson)(toolCall.function.arguments)) {
588
+ if (((_l = toolCall.function) == null ? void 0 : _l.name) != null && ((_m = toolCall.function) == null ? void 0 : _m.arguments) != null && (0, import_provider_utils3.isParsableJson)(toolCall.function.arguments)) {
542
589
  controller.enqueue({
543
590
  type: "tool-call",
544
591
  toolCallType: "function",
545
- toolCallId: (_l = toolCall.id) != null ? _l : (0, import_provider_utils3.generateId)(),
592
+ toolCallId: (_n = toolCall.id) != null ? _n : (0, import_provider_utils3.generateId)(),
546
593
  toolName: toolCall.function.name,
547
594
  args: toolCall.function.arguments
548
595
  });
@@ -568,12 +615,19 @@ var OpenRouterChatLanguageModel = class {
568
615
  }
569
616
  }
570
617
  }
571
- controller.enqueue({
618
+ const providerMetadata = {};
619
+ if (shouldIncludeUsageAccounting && (openrouterUsage.totalTokens !== void 0 || openrouterUsage.cost !== void 0 || openrouterUsage.promptTokensDetails !== void 0 || openrouterUsage.completionTokensDetails !== void 0)) {
620
+ providerMetadata.openrouter = {
621
+ usage: openrouterUsage
622
+ };
623
+ }
624
+ const hasProviderMetadata = Object.keys(providerMetadata).length > 0 && shouldIncludeUsageAccounting;
625
+ controller.enqueue(__spreadValues({
572
626
  type: "finish",
573
627
  finishReason,
574
628
  logprobs,
575
629
  usage
576
- });
630
+ }, hasProviderMetadata ? { providerMetadata } : {}));
577
631
  }
578
632
  })
579
633
  ),
@@ -588,8 +642,15 @@ var OpenRouterChatCompletionBaseResponseSchema = import_zod2.z.object({
588
642
  model: import_zod2.z.string().optional(),
589
643
  usage: import_zod2.z.object({
590
644
  prompt_tokens: import_zod2.z.number(),
645
+ prompt_tokens_details: import_zod2.z.object({
646
+ cached_tokens: import_zod2.z.number()
647
+ }).optional(),
591
648
  completion_tokens: import_zod2.z.number(),
592
- total_tokens: import_zod2.z.number()
649
+ completion_tokens_details: import_zod2.z.object({
650
+ reasoning_tokens: import_zod2.z.number()
651
+ }).optional(),
652
+ total_tokens: import_zod2.z.number(),
653
+ cost: import_zod2.z.number().optional()
593
654
  }).nullish()
594
655
  });
595
656
  var OpenRouterNonStreamChatCompletionResponseSchema = OpenRouterChatCompletionBaseResponseSchema.extend({
@@ -686,14 +747,13 @@ function prepareToolsAndToolChoice(mode) {
686
747
  parameters: tool.parameters
687
748
  }
688
749
  };
689
- } else {
690
- return {
691
- type: "function",
692
- function: {
693
- name: tool.name
694
- }
695
- };
696
750
  }
751
+ return {
752
+ type: "function",
753
+ function: {
754
+ name: tool.name
755
+ }
756
+ };
697
757
  });
698
758
  const toolChoice = mode.toolChoice;
699
759
  if (toolChoice == null) {
@@ -882,7 +942,7 @@ var OpenRouterCompletionLanguageModel = class {
882
942
  }) {
883
943
  var _a, _b;
884
944
  const type = mode.type;
885
- const extraCallingBody = (_a = providerMetadata == null ? void 0 : providerMetadata["openrouter"]) != null ? _a : {};
945
+ const extraCallingBody = (_a = providerMetadata == null ? void 0 : providerMetadata.openrouter) != null ? _a : {};
886
946
  const { prompt: completionPrompt } = convertToOpenRouterCompletionPrompt({
887
947
  prompt,
888
948
  inputFormat
@@ -1179,9 +1239,7 @@ function createOpenRouter(options = {}) {
1179
1239
  }
1180
1240
  return createChatModel(modelId, settings);
1181
1241
  };
1182
- const provider = function(modelId, settings) {
1183
- return createLanguageModel(modelId, settings);
1184
- };
1242
+ const provider = (modelId, settings) => createLanguageModel(modelId, settings);
1185
1243
  provider.languageModel = createLanguageModel;
1186
1244
  provider.chat = createChatModel;
1187
1245
  provider.completion = createCompletionModel;