@standardagents/openrouter 0.43.1 → 0.44.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.js CHANGED
@@ -1,7 +1,37 @@
1
1
  // src/OpenRouterProvider.ts
2
- import { ProviderError, defineTool } from "@standardagents/spec";
2
+ import { ProviderError, defineTool, resolveRequestReasoningLevels } from "@standardagents/spec";
3
3
  import { z } from "zod";
4
4
 
5
+ // src/responses-transformers.ts
6
+ import { mapReasoningLevel as mapReasoningLevel2 } from "@standardagents/spec";
7
+
8
+ // src/reasoning.ts
9
+ function getOpenRouterReasoningLevels(model) {
10
+ if (/^openai\/o[134](?:[-:]|$)/.test(model)) {
11
+ return { 0: "low", 33: "low", 66: "medium", 100: "high" };
12
+ }
13
+ return { 0: null, 10: "minimal", 33: "low", 66: "medium", 100: "high" };
14
+ }
15
+ function mapOpenRouterReasoningLevels(model, mandatory, supportedEfforts) {
16
+ const efforts = ["minimal", "low", "medium", "high", "xhigh", "max"].filter((effort) => supportedEfforts?.includes(effort));
17
+ if (efforts.length === 0) {
18
+ const fallback = getOpenRouterReasoningLevels(model);
19
+ if (mandatory) return { 0: "low", 33: "low", 66: "medium", 100: "high" };
20
+ return mandatory === false ? { ...fallback, 0: null } : fallback;
21
+ }
22
+ const values = mandatory ? efforts : [null, ...efforts];
23
+ return Object.fromEntries(values.map((effort, index) => [
24
+ Math.round(index * 100 / Math.max(1, values.length - 1)),
25
+ effort
26
+ ]));
27
+ }
28
+
29
+ // src/responses-transformers.ts
30
+ import { unrecognized } from "@openrouter/sdk/types";
31
+
32
+ // src/chat-completions-transformers.ts
33
+ import { mapReasoningLevel } from "@standardagents/spec";
34
+
5
35
  // src/server-tools.ts
6
36
  var OPENROUTER_SERVER_TOOL_NAMES = [
7
37
  "web_search",
@@ -235,21 +265,22 @@ function transformChatAssistantMessage(msg) {
235
265
  return message;
236
266
  }
237
267
  function transformChatToolMessage(msg) {
268
+ const content = Array.isArray(msg.content) ? msg.content.filter((part) => part.type === "text").map((part) => part.text).join("\n") : msg.content;
238
269
  let output;
239
- if (typeof msg.content === "string") {
240
- output = msg.content;
241
- } else if ("type" in msg.content) {
242
- if (msg.content.type === "text") {
243
- output = msg.content.text;
244
- } else if (msg.content.type === "error") {
245
- output = `Error: ${msg.content.error}`;
270
+ if (typeof content === "string") {
271
+ output = content;
272
+ } else if ("type" in content) {
273
+ if (content.type === "text") {
274
+ output = content.text;
275
+ } else if (content.type === "error") {
276
+ output = `Error: ${content.error}`;
246
277
  } else {
247
- output = JSON.stringify(msg.content);
278
+ output = JSON.stringify(content);
248
279
  }
249
280
  } else {
250
- output = JSON.stringify(msg.content);
281
+ output = JSON.stringify(content);
251
282
  }
252
- const imageAttachments = msg.attachments?.filter((a) => a.type === "image" && a.data) || [];
283
+ const imageAttachments = Array.isArray(msg.content) ? msg.content.filter((part) => part.type === "image" && Boolean(part.data) || part.type === "image_url") : [];
253
284
  const toolMessage = {
254
285
  role: "tool",
255
286
  tool_call_id: msg.toolCallId,
@@ -268,15 +299,7 @@ function transformChatToolMessage(msg) {
268
299
  text: `Here ${verb} ${descriptor} from ${toolName}:`
269
300
  });
270
301
  for (const attachment of imageAttachments) {
271
- const attachmentData = attachment.data || "";
272
- const imageData = attachmentData.startsWith("data:") ? attachmentData : `data:${attachment.mediaType || "image/png"};base64,${attachmentData}`;
273
- imageContent.push({
274
- type: "image_url",
275
- image_url: {
276
- url: imageData,
277
- detail: "auto"
278
- }
279
- });
302
+ imageContent.push(transformChatContentPart(attachment));
280
303
  }
281
304
  const syntheticUserMessage = {
282
305
  role: "user",
@@ -728,7 +751,7 @@ async function* parseChatSSEStream(response, state) {
728
751
  reader.releaseLock();
729
752
  }
730
753
  }
731
- function buildChatParams(request) {
754
+ function buildChatParams(request, reasoningLevels = getOpenRouterReasoningLevels(request.model)) {
732
755
  const messages = transformChatMessages(request.messages);
733
756
  const params = {
734
757
  model: request.model,
@@ -757,18 +780,6 @@ function buildChatParams(request) {
757
780
  if (request.topP !== void 0) {
758
781
  params.top_p = request.topP;
759
782
  }
760
- if (request.reasoning?.level !== void 0) {
761
- const effortMap = {
762
- 10: "minimal",
763
- 33: "low",
764
- 66: "medium",
765
- 100: "high"
766
- };
767
- const effort = effortMap[request.reasoning.level];
768
- if (effort) {
769
- params.reasoning = { effort };
770
- }
771
- }
772
783
  if (request.responseFormat) {
773
784
  if (request.responseFormat.type === "json") {
774
785
  if (request.responseFormat.schema) {
@@ -792,7 +803,27 @@ function buildChatParams(request) {
792
803
  request.providerOptions
793
804
  );
794
805
  if (safeOptions) {
795
- Object.assign(params, safeOptions);
806
+ if (request.reasoning?.level !== void 0) {
807
+ const { effort, reasoning_effort, reasoningEffort, ...otherOptions } = safeOptions;
808
+ Object.assign(params, otherOptions);
809
+ } else {
810
+ Object.assign(params, safeOptions);
811
+ }
812
+ }
813
+ if (request.reasoning?.level !== void 0) {
814
+ if (params.reasoning) {
815
+ params.reasoning = { ...params.reasoning };
816
+ delete params.reasoning.effort;
817
+ }
818
+ const effort = mapReasoningLevel(request.reasoning.level, request.reasoning.levels ?? reasoningLevels);
819
+ if (effort === null || params.reasoning?.enabled !== void 0) {
820
+ params.reasoning = { ...params.reasoning, enabled: effort !== null && effort !== "none" };
821
+ }
822
+ if (effort === "none" || effort === "minimal" || effort === "low" || effort === "medium" || effort === "high" || effort === "xhigh" || effort === "max") {
823
+ params.reasoning = { ...params.reasoning, effort };
824
+ } else if (effort === "enabled") {
825
+ params.reasoning = { ...params.reasoning, enabled: true };
826
+ }
796
827
  }
797
828
  return params;
798
829
  }
@@ -930,21 +961,22 @@ function transformAssistantMessage(msg) {
930
961
  return items;
931
962
  }
932
963
  function transformToolMessage(msg) {
964
+ const content = Array.isArray(msg.content) ? msg.content.filter((part) => part.type === "text").map((part) => part.text).join("\n") : msg.content;
933
965
  let output;
934
- if (typeof msg.content === "string") {
935
- output = msg.content;
936
- } else if ("type" in msg.content) {
937
- if (msg.content.type === "text") {
938
- output = msg.content.text;
939
- } else if (msg.content.type === "error") {
940
- output = `Error: ${msg.content.error}`;
966
+ if (typeof content === "string") {
967
+ output = content;
968
+ } else if ("type" in content) {
969
+ if (content.type === "text") {
970
+ output = content.text;
971
+ } else if (content.type === "error") {
972
+ output = `Error: ${content.error}`;
941
973
  } else {
942
- output = JSON.stringify(msg.content);
974
+ output = JSON.stringify(content);
943
975
  }
944
976
  } else {
945
- output = JSON.stringify(msg.content);
977
+ output = JSON.stringify(content);
946
978
  }
947
- const imageAttachments = msg.attachments?.filter((a) => a.type === "image" && a.data) || [];
979
+ const imageAttachments = Array.isArray(msg.content) ? msg.content.filter((part) => part.type === "image" && Boolean(part.data) || part.type === "image_url") : [];
948
980
  const toolOutput = {
949
981
  type: "function_call_output",
950
982
  call_id: msg.toolCallId,
@@ -963,13 +995,7 @@ function transformToolMessage(msg) {
963
995
  text: `Here ${verb} ${descriptor} from ${toolName}:`
964
996
  });
965
997
  for (const attachment of imageAttachments) {
966
- const attachmentData = attachment.data || "";
967
- const imageData = attachmentData.startsWith("data:") ? attachmentData : `data:${attachment.mediaType || "image/png"};base64,${attachmentData}`;
968
- imageContent.push({
969
- type: "input_image",
970
- image_url: imageData,
971
- detail: "auto"
972
- });
998
+ imageContent.push(transformContentPart(attachment));
973
999
  }
974
1000
  const syntheticUserMessage = {
975
1001
  role: "user",
@@ -1246,7 +1272,7 @@ function extractReasoningDetails(output, streamedReasoningContent) {
1246
1272
  }
1247
1273
  return details.length > 0 ? details : void 0;
1248
1274
  }
1249
- function buildCreateParams(request) {
1275
+ function buildCreateParams(request, reasoningLevels = getOpenRouterReasoningLevels(request.model)) {
1250
1276
  const { input, instructions } = transformMessages(request.messages);
1251
1277
  const params = {
1252
1278
  model: request.model,
@@ -1275,24 +1301,6 @@ function buildCreateParams(request) {
1275
1301
  if (request.topP !== void 0) {
1276
1302
  params.topP = request.topP;
1277
1303
  }
1278
- if (request.reasoning?.level !== void 0) {
1279
- const effortMap = {
1280
- 10: "minimal",
1281
- // Basic reasoning
1282
- 33: "low",
1283
- // Light reasoning
1284
- 66: "medium",
1285
- // Balanced reasoning
1286
- 100: "high"
1287
- // Deep reasoning
1288
- };
1289
- const effort = effortMap[request.reasoning.level];
1290
- if (effort) {
1291
- params.reasoning = {
1292
- effort
1293
- };
1294
- }
1295
- }
1296
1304
  if (request.responseFormat) {
1297
1305
  if (request.responseFormat.type === "json") {
1298
1306
  if (request.responseFormat.schema) {
@@ -1318,7 +1326,27 @@ function buildCreateParams(request) {
1318
1326
  request.providerOptions
1319
1327
  );
1320
1328
  if (providerOptions) {
1321
- Object.assign(params, providerOptions);
1329
+ if (request.reasoning?.level !== void 0) {
1330
+ const { effort, reasoning_effort, reasoningEffort, ...otherOptions } = providerOptions;
1331
+ Object.assign(params, otherOptions);
1332
+ } else {
1333
+ Object.assign(params, providerOptions);
1334
+ }
1335
+ }
1336
+ if (request.reasoning?.level !== void 0) {
1337
+ if (params.reasoning) {
1338
+ params.reasoning = { ...params.reasoning };
1339
+ delete params.reasoning.effort;
1340
+ }
1341
+ const effort = mapReasoningLevel2(request.reasoning.level, request.reasoning.levels ?? reasoningLevels);
1342
+ if (effort === null || params.reasoning?.enabled !== void 0) {
1343
+ params.reasoning = { ...params.reasoning, enabled: effort !== null && effort !== "none" };
1344
+ }
1345
+ if (effort === "none" || effort === "minimal" || effort === "low" || effort === "medium" || effort === "high" || effort === "xhigh" || effort === "max") {
1346
+ params.reasoning = { ...params.reasoning, effort: effort === "max" ? unrecognized(effort) : effort };
1347
+ } else if (effort === "enabled") {
1348
+ params.reasoning = { ...params.reasoning, enabled: true };
1349
+ }
1322
1350
  }
1323
1351
  return params;
1324
1352
  }
@@ -2205,8 +2233,10 @@ var OpenRouterProvider = class _OpenRouterProvider {
2205
2233
  }
2206
2234
  if (!_OpenRouterProvider.modelsFetchPromise) {
2207
2235
  _OpenRouterProvider.modelsFetchPromise = (async () => {
2236
+ const controller = new AbortController();
2237
+ const timer = setTimeout(() => controller.abort(), 2500);
2208
2238
  try {
2209
- const response = await fetch("https://openrouter.ai/api/v1/models");
2239
+ const response = await fetch("https://openrouter.ai/api/v1/models", { signal: controller.signal });
2210
2240
  if (!response.ok) {
2211
2241
  throw new Error(`OpenRouter API error: ${response.status}`);
2212
2242
  }
@@ -2218,6 +2248,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2218
2248
  _OpenRouterProvider.modelsFetchFailedAt = Date.now();
2219
2249
  throw error;
2220
2250
  } finally {
2251
+ clearTimeout(timer);
2221
2252
  _OpenRouterProvider.modelsFetchPromise = null;
2222
2253
  }
2223
2254
  })();
@@ -2243,7 +2274,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2243
2274
  capabilities.maxOutputTokens = model.top_provider.max_completion_tokens;
2244
2275
  }
2245
2276
  if (model.id.includes("o1") || model.id.includes("o3") || model.id.includes("o4")) {
2246
- capabilities.reasoningLevels = { 0: null, 33: "low", 66: "medium", 100: "high" };
2277
+ capabilities.reasoningLevels = { 0: "low", 33: "low", 66: "medium", 100: "high" };
2247
2278
  if (model.id.includes("o1-preview") || model.id.includes("o1-mini")) {
2248
2279
  capabilities.supportsToolCalls = false;
2249
2280
  capabilities.supportsJsonMode = false;
@@ -2251,6 +2282,13 @@ var OpenRouterProvider = class _OpenRouterProvider {
2251
2282
  } else if (model.id.includes("claude") && model.id.includes("thinking")) {
2252
2283
  capabilities.reasoningLevels = { 0: null, 100: "enabled" };
2253
2284
  }
2285
+ if (model.reasoning) {
2286
+ capabilities.reasoningLevels = mapOpenRouterReasoningLevels(
2287
+ model.id,
2288
+ model.reasoning.mandatory,
2289
+ model.reasoning.supported_efforts
2290
+ );
2291
+ }
2254
2292
  return capabilities;
2255
2293
  }
2256
2294
  /**
@@ -2303,7 +2341,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2303
2341
  const apiKey = this.config.apiKey;
2304
2342
  const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
2305
2343
  try {
2306
- const params = buildChatParams(request);
2344
+ const params = buildChatParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2307
2345
  if (this.config.providers && this.config.providers.length > 0) {
2308
2346
  params.provider = { only: this.config.providers };
2309
2347
  }
@@ -2341,7 +2379,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2341
2379
  const apiKey = this.config.apiKey;
2342
2380
  const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
2343
2381
  try {
2344
- const params = buildCreateParams(request);
2382
+ const params = buildCreateParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2345
2383
  this.applyConfiguredProvidersToResponses(params);
2346
2384
  const pricingPromise = this.getUsagePricing(params.model);
2347
2385
  if (this.hasOpenRouterServerTools(params)) {
@@ -2388,7 +2426,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2388
2426
  const apiKey = this.config.apiKey;
2389
2427
  const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
2390
2428
  try {
2391
- const params = buildChatParams(request);
2429
+ const params = buildChatParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2392
2430
  if (this.config.providers && this.config.providers.length > 0) {
2393
2431
  params.provider = { only: this.config.providers };
2394
2432
  }
@@ -2464,7 +2502,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2464
2502
  const apiKey = this.config.apiKey;
2465
2503
  const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
2466
2504
  try {
2467
- const params = buildCreateParams(request);
2505
+ const params = buildCreateParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2468
2506
  this.applyConfiguredProvidersToResponses(params);
2469
2507
  const pricingPromise = this.getUsagePricing(params.model);
2470
2508
  const response = await fetch(`${baseUrl}/responses`, {
@@ -2601,7 +2639,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2601
2639
  async inspectRequest(request) {
2602
2640
  const apiMode = this.getApiMode(request);
2603
2641
  if (apiMode === "responses") {
2604
- const params2 = buildCreateParams(request);
2642
+ const params2 = buildCreateParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2605
2643
  if (this.config.providers && this.config.providers.length > 0) {
2606
2644
  params2.provider = { only: this.config.providers };
2607
2645
  }
@@ -2613,7 +2651,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2613
2651
  }
2614
2652
  };
2615
2653
  }
2616
- const params = buildChatParams(request);
2654
+ const params = buildChatParams(request, await resolveRequestReasoningLevels(request, (model) => this.getModelCapabilities(model)));
2617
2655
  if (this.config.providers && this.config.providers.length > 0) {
2618
2656
  params.provider = { only: this.config.providers };
2619
2657
  }