@openrouter/ai-sdk-provider 2.3.2 → 2.4.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/dist/index.d.mts CHANGED
@@ -431,6 +431,7 @@ type OpenRouterCompletionSettings = {
431
431
  declare enum ReasoningFormat {
432
432
  Unknown = "unknown",
433
433
  OpenAIResponsesV1 = "openai-responses-v1",
434
+ AzureOpenAIResponsesV1 = "azure-openai-responses-v1",
434
435
  XAIResponsesV1 = "xai-responses-v1",
435
436
  AnthropicClaudeV1 = "anthropic-claude-v1",
436
437
  GoogleGeminiV1 = "google-gemini-v1"
@@ -676,6 +677,16 @@ interface OpenRouterProviderSettings {
676
677
  * Maps provider slugs (e.g. "anthropic", "openai") to their respective API keys.
677
678
  */
678
679
  api_keys?: Record<string, string>;
680
+ /**
681
+ * Your app's display name. Sets the `X-OpenRouter-Title` header on
682
+ * every request for app attribution on the openrouter.ai dashboard.
683
+ */
684
+ appName?: string;
685
+ /**
686
+ * Your app's URL or identifier. Sets the `HTTP-Referer` header on every request,
687
+ * used to identify your app on the openrouter.ai dashboard.
688
+ */
689
+ appUrl?: string;
679
690
  }
680
691
  /**
681
692
  Create an OpenRouter provider instance.
@@ -708,6 +719,14 @@ declare class OpenRouter {
708
719
  * Record of provider slugs to API keys for injecting into provider routing.
709
720
  */
710
721
  readonly api_keys?: Record<string, string>;
722
+ /**
723
+ * App display name for the `X-OpenRouter-Title` header.
724
+ */
725
+ readonly appName?: string;
726
+ /**
727
+ * App URL for the `HTTP-Referer` header.
728
+ */
729
+ readonly appUrl?: string;
711
730
  /**
712
731
  * Creates a new OpenRouter provider instance.
713
732
  */
package/dist/index.d.ts CHANGED
@@ -431,6 +431,7 @@ type OpenRouterCompletionSettings = {
431
431
  declare enum ReasoningFormat {
432
432
  Unknown = "unknown",
433
433
  OpenAIResponsesV1 = "openai-responses-v1",
434
+ AzureOpenAIResponsesV1 = "azure-openai-responses-v1",
434
435
  XAIResponsesV1 = "xai-responses-v1",
435
436
  AnthropicClaudeV1 = "anthropic-claude-v1",
436
437
  GoogleGeminiV1 = "google-gemini-v1"
@@ -676,6 +677,16 @@ interface OpenRouterProviderSettings {
676
677
  * Maps provider slugs (e.g. "anthropic", "openai") to their respective API keys.
677
678
  */
678
679
  api_keys?: Record<string, string>;
680
+ /**
681
+ * Your app's display name. Sets the `X-OpenRouter-Title` header on
682
+ * every request for app attribution on the openrouter.ai dashboard.
683
+ */
684
+ appName?: string;
685
+ /**
686
+ * Your app's URL or identifier. Sets the `HTTP-Referer` header on every request,
687
+ * used to identify your app on the openrouter.ai dashboard.
688
+ */
689
+ appUrl?: string;
679
690
  }
680
691
  /**
681
692
  Create an OpenRouter provider instance.
@@ -708,6 +719,14 @@ declare class OpenRouter {
708
719
  * Record of provider slugs to API keys for injecting into provider routing.
709
720
  */
710
721
  readonly api_keys?: Record<string, string>;
722
+ /**
723
+ * App display name for the `X-OpenRouter-Title` header.
724
+ */
725
+ readonly appName?: string;
726
+ /**
727
+ * App URL for the `HTTP-Referer` header.
728
+ */
729
+ readonly appUrl?: string;
711
730
  /**
712
731
  * Creates a new OpenRouter provider instance.
713
732
  */
package/dist/index.js CHANGED
@@ -2216,11 +2216,13 @@ function isDefinedOrNotNull(value) {
2216
2216
  var ReasoningFormat = /* @__PURE__ */ ((ReasoningFormat2) => {
2217
2217
  ReasoningFormat2["Unknown"] = "unknown";
2218
2218
  ReasoningFormat2["OpenAIResponsesV1"] = "openai-responses-v1";
2219
+ ReasoningFormat2["AzureOpenAIResponsesV1"] = "azure-openai-responses-v1";
2219
2220
  ReasoningFormat2["XAIResponsesV1"] = "xai-responses-v1";
2220
2221
  ReasoningFormat2["AnthropicClaudeV1"] = "anthropic-claude-v1";
2221
2222
  ReasoningFormat2["GoogleGeminiV1"] = "google-gemini-v1";
2222
2223
  return ReasoningFormat2;
2223
2224
  })(ReasoningFormat || {});
2225
+ var DEFAULT_REASONING_FORMAT = "anthropic-claude-v1" /* AnthropicClaudeV1 */;
2224
2226
 
2225
2227
  // src/schemas/reasoning-details.ts
2226
2228
  var CommonReasoningDetailSchema = import_v4.z.object({
@@ -2373,7 +2375,11 @@ var OpenRouterProviderMetadataSchema = import_v43.z.object({
2373
2375
  }).catchall(import_v43.z.any());
2374
2376
  var OpenRouterProviderOptionsSchema = import_v43.z.object({
2375
2377
  openrouter: import_v43.z.object({
2376
- reasoning_details: import_v43.z.array(ReasoningDetailUnionSchema).optional(),
2378
+ // Use ReasoningDetailArraySchema (with unknown fallback) instead of
2379
+ // z.array(ReasoningDetailUnionSchema) so that a single malformed entry
2380
+ // (e.g., a future format not yet in the enum) is individually dropped
2381
+ // rather than causing the entire array to fail parsing.
2382
+ reasoning_details: ReasoningDetailArraySchema.optional(),
2377
2383
  annotations: import_v43.z.array(FileAnnotationSchema).optional()
2378
2384
  }).optional()
2379
2385
  }).optional();
@@ -2444,6 +2450,31 @@ function createFinishReason(unified, raw) {
2444
2450
  return { unified, raw };
2445
2451
  }
2446
2452
 
2453
+ // src/utils/with-stream-error-handling.ts
2454
+ function withStreamErrorHandling(source, onError) {
2455
+ const reader = source.getReader();
2456
+ return new ReadableStream({
2457
+ async pull(controller) {
2458
+ try {
2459
+ const { done, value } = await reader.read();
2460
+ if (done) {
2461
+ controller.close();
2462
+ } else {
2463
+ controller.enqueue(value);
2464
+ }
2465
+ } catch (err) {
2466
+ onError(err);
2467
+ reader.cancel().catch(() => {
2468
+ });
2469
+ controller.close();
2470
+ }
2471
+ },
2472
+ cancel(reason) {
2473
+ reader.cancel(reason);
2474
+ }
2475
+ });
2476
+ }
2477
+
2447
2478
  // src/utils/reasoning-details-duplicate-tracker.ts
2448
2479
  var _seenKeys;
2449
2480
  var ReasoningDetailsDuplicateTracker = class {
@@ -2785,8 +2816,24 @@ function convertToOpenRouterChatMessages(prompt) {
2785
2816
  const candidateReasoningDetails = messageReasoningDetails && Array.isArray(messageReasoningDetails) && messageReasoningDetails.length > 0 ? messageReasoningDetails : findFirstReasoningDetails(content);
2786
2817
  let finalReasoningDetails;
2787
2818
  if (candidateReasoningDetails && candidateReasoningDetails.length > 0) {
2819
+ const validDetails = candidateReasoningDetails.filter((detail) => {
2820
+ var _a17;
2821
+ if (detail.type !== "reasoning.text" /* Text */) {
2822
+ return true;
2823
+ }
2824
+ const format = (_a17 = detail.format) != null ? _a17 : DEFAULT_REASONING_FORMAT;
2825
+ if (format !== "anthropic-claude-v1" /* AnthropicClaudeV1 */) {
2826
+ return true;
2827
+ }
2828
+ return !!detail.signature;
2829
+ });
2830
+ if (validDetails.length < candidateReasoningDetails.length) {
2831
+ console.warn(
2832
+ "[openrouter] Some reasoning_details entries were removed because they were missing signatures. See https://github.com/OpenRouterTeam/ai-sdk-provider/issues/423 for more details."
2833
+ );
2834
+ }
2788
2835
  const uniqueDetails = [];
2789
- for (const detail of candidateReasoningDetails) {
2836
+ for (const detail of validDetails) {
2790
2837
  if (reasoningDetailsTracker.upsert(detail)) {
2791
2838
  uniqueDetails.push(detail);
2792
2839
  }
@@ -2815,6 +2862,7 @@ function convertToOpenRouterChatMessages(prompt) {
2815
2862
  role: "tool",
2816
2863
  tool_call_id: toolResponse.toolCallId,
2817
2864
  content: content2,
2865
+ name: toolResponse.toolName,
2818
2866
  cache_control: (_h = getCacheControl(providerOptions)) != null ? _h : getCacheControl(toolResponse.providerOptions)
2819
2867
  });
2820
2868
  }
@@ -2956,13 +3004,14 @@ function filenameFromUrl(url) {
2956
3004
  }
2957
3005
  }
2958
3006
  function findFirstReasoningDetails(content) {
2959
- var _a16, _b16, _c;
3007
+ var _a16, _b16, _c, _d;
2960
3008
  for (const part of content) {
2961
3009
  if (part.type === "tool-call") {
2962
- const openrouter2 = (_a16 = part.providerOptions) == null ? void 0 : _a16.openrouter;
2963
- const details = openrouter2 == null ? void 0 : openrouter2.reasoning_details;
2964
- if (Array.isArray(details) && details.length > 0) {
2965
- return details;
3010
+ const parsed = OpenRouterProviderOptionsSchema.safeParse(
3011
+ part.providerOptions
3012
+ );
3013
+ if (parsed.success && ((_b16 = (_a16 = parsed.data) == null ? void 0 : _a16.openrouter) == null ? void 0 : _b16.reasoning_details) && parsed.data.openrouter.reasoning_details.length > 0) {
3014
+ return parsed.data.openrouter.reasoning_details;
2966
3015
  }
2967
3016
  }
2968
3017
  }
@@ -2971,7 +3020,7 @@ function findFirstReasoningDetails(content) {
2971
3020
  const parsed = OpenRouterProviderOptionsSchema.safeParse(
2972
3021
  part.providerOptions
2973
3022
  );
2974
- if (parsed.success && ((_c = (_b16 = parsed.data) == null ? void 0 : _b16.openrouter) == null ? void 0 : _c.reasoning_details) && parsed.data.openrouter.reasoning_details.length > 0) {
3023
+ if (parsed.success && ((_d = (_c = parsed.data) == null ? void 0 : _c.openrouter) == null ? void 0 : _d.reasoning_details) && parsed.data.openrouter.reasoning_details.length > 0) {
2975
3024
  return parsed.data.openrouter.reasoning_details;
2976
3025
  }
2977
3026
  }
@@ -3476,7 +3525,8 @@ var OpenRouterChatLanguageModel = class {
3476
3525
  (d) => d.type === "reasoning.encrypted" /* Encrypted */ && d.data
3477
3526
  );
3478
3527
  const shouldOverrideFinishReason = hasToolCalls && hasEncryptedReasoning && choice.finish_reason === "stop";
3479
- const effectiveFinishReason = shouldOverrideFinishReason ? createFinishReason("tool-calls", (_j = choice.finish_reason) != null ? _j : void 0) : mapOpenRouterFinishReason(choice.finish_reason);
3528
+ const mappedFinishReason = shouldOverrideFinishReason ? createFinishReason("tool-calls", (_j = choice.finish_reason) != null ? _j : void 0) : mapOpenRouterFinishReason(choice.finish_reason);
3529
+ const effectiveFinishReason = hasToolCalls && mappedFinishReason.unified === "other" ? createFinishReason("tool-calls", mappedFinishReason.raw) : mappedFinishReason;
3480
3530
  return {
3481
3531
  content,
3482
3532
  finishReason: effectiveFinishReason,
@@ -3540,6 +3590,10 @@ var OpenRouterChatLanguageModel = class {
3540
3590
  abortSignal: options.abortSignal,
3541
3591
  fetch: this.config.fetch
3542
3592
  });
3593
+ let streamError;
3594
+ const safeResponse = withStreamErrorHandling(response, (err) => {
3595
+ streamError = err;
3596
+ });
3543
3597
  const toolCalls = [];
3544
3598
  let finishReason = createFinishReason("other");
3545
3599
  const usage = {
@@ -3568,7 +3622,7 @@ var OpenRouterChatLanguageModel = class {
3568
3622
  let openrouterResponseId;
3569
3623
  let provider;
3570
3624
  return {
3571
- stream: response.pipeThrough(
3625
+ stream: safeResponse.pipeThrough(
3572
3626
  new TransformStream({
3573
3627
  transform(chunk, controller) {
3574
3628
  var _a17, _b17, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
@@ -3890,12 +3944,19 @@ var OpenRouterChatLanguageModel = class {
3890
3944
  flush(controller) {
3891
3945
  var _a17;
3892
3946
  const hasToolCalls = toolCalls.length > 0;
3947
+ if (streamError != null) {
3948
+ finishReason = createFinishReason("error");
3949
+ controller.enqueue({ type: "error", error: streamError });
3950
+ }
3893
3951
  const hasEncryptedReasoning = accumulatedReasoningDetails.some(
3894
3952
  (d) => d.type === "reasoning.encrypted" /* Encrypted */ && d.data
3895
3953
  );
3896
3954
  if (hasToolCalls && hasEncryptedReasoning && finishReason.unified === "stop") {
3897
3955
  finishReason = createFinishReason("tool-calls", finishReason.raw);
3898
3956
  }
3957
+ if (hasToolCalls && finishReason.unified === "other") {
3958
+ finishReason = createFinishReason("tool-calls", finishReason.raw);
3959
+ }
3899
3960
  if (finishReason.unified === "tool-calls") {
3900
3961
  for (const toolCall of toolCalls) {
3901
3962
  if (toolCall && !toolCall.sent) {
@@ -4281,6 +4342,10 @@ var OpenRouterCompletionLanguageModel = class {
4281
4342
  abortSignal: options.abortSignal,
4282
4343
  fetch: this.config.fetch
4283
4344
  });
4345
+ let streamError;
4346
+ const safeResponse = withStreamErrorHandling(response, (err) => {
4347
+ streamError = err;
4348
+ });
4284
4349
  let finishReason = createFinishReason("other");
4285
4350
  const usage = {
4286
4351
  inputTokens: {
@@ -4300,7 +4365,7 @@ var OpenRouterCompletionLanguageModel = class {
4300
4365
  let provider;
4301
4366
  let rawUsage;
4302
4367
  return {
4303
- stream: response.pipeThrough(
4368
+ stream: safeResponse.pipeThrough(
4304
4369
  new TransformStream({
4305
4370
  transform(chunk, controller) {
4306
4371
  var _a16, _b16, _c, _d, _e;
@@ -4364,6 +4429,10 @@ var OpenRouterCompletionLanguageModel = class {
4364
4429
  }
4365
4430
  },
4366
4431
  flush(controller) {
4432
+ if (streamError != null) {
4433
+ finishReason = createFinishReason("error");
4434
+ controller.enqueue({ type: "error", error: streamError });
4435
+ }
4367
4436
  usage.raw = rawUsage;
4368
4437
  const openrouterMetadata = {
4369
4438
  usage: openrouterUsage
@@ -4477,17 +4546,19 @@ var OpenRouter = class {
4477
4546
  this.apiKey = options.apiKey;
4478
4547
  this.headers = options.headers;
4479
4548
  this.api_keys = options.api_keys;
4549
+ this.appName = options.appName;
4550
+ this.appUrl = options.appUrl;
4480
4551
  }
4481
4552
  get baseConfig() {
4482
4553
  return {
4483
4554
  baseURL: this.baseURL,
4484
- headers: () => __spreadValues(__spreadValues({
4555
+ headers: () => __spreadValues(__spreadValues(__spreadValues(__spreadValues({
4485
4556
  Authorization: `Bearer ${loadApiKey({
4486
4557
  apiKey: this.apiKey,
4487
4558
  environmentVariableName: "OPENROUTER_API_KEY",
4488
4559
  description: "OpenRouter"
4489
4560
  })}`
4490
- }, this.headers), this.api_keys && Object.keys(this.api_keys).length > 0 && {
4561
+ }, this.appName && { "X-OpenRouter-Title": this.appName }), this.appUrl && { "HTTP-Referer": this.appUrl }), this.headers), this.api_keys && Object.keys(this.api_keys).length > 0 && {
4491
4562
  "X-Provider-API-Keys": JSON.stringify(this.api_keys)
4492
4563
  })
4493
4564
  };
@@ -4724,7 +4795,7 @@ function withUserAgentSuffix2(headers, ...userAgentSuffixParts) {
4724
4795
  }
4725
4796
 
4726
4797
  // src/version.ts
4727
- var VERSION2 = false ? "0.0.0-test" : "2.3.2";
4798
+ var VERSION2 = false ? "0.0.0-test" : "2.4.0";
4728
4799
 
4729
4800
  // src/provider.ts
4730
4801
  function createOpenRouter(options = {}) {
@@ -4732,13 +4803,13 @@ function createOpenRouter(options = {}) {
4732
4803
  const baseURL = (_b16 = withoutTrailingSlash((_a16 = options.baseURL) != null ? _a16 : options.baseUrl)) != null ? _b16 : "https://openrouter.ai/api/v1";
4733
4804
  const compatibility = (_c = options.compatibility) != null ? _c : "compatible";
4734
4805
  const getHeaders = () => withUserAgentSuffix2(
4735
- __spreadValues(__spreadValues({
4806
+ __spreadValues(__spreadValues(__spreadValues(__spreadValues({
4736
4807
  Authorization: `Bearer ${loadApiKey({
4737
4808
  apiKey: options.apiKey,
4738
4809
  environmentVariableName: "OPENROUTER_API_KEY",
4739
4810
  description: "OpenRouter"
4740
4811
  })}`
4741
- }, options.headers), options.api_keys && Object.keys(options.api_keys).length > 0 && {
4812
+ }, options.appName && { "X-OpenRouter-Title": options.appName }), options.appUrl && { "HTTP-Referer": options.appUrl }), options.headers), options.api_keys && Object.keys(options.api_keys).length > 0 && {
4742
4813
  "X-Provider-API-Keys": JSON.stringify(options.api_keys)
4743
4814
  }),
4744
4815
  `ai-sdk/openrouter/${VERSION2}`