@standardagents/openrouter 0.11.4 → 0.11.6

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,7 @@
1
1
  // src/OpenRouterProvider.ts
2
2
  import { ProviderError } from "@standardagents/spec";
3
3
 
4
- // src/transformers.ts
4
+ // src/responses-transformers.ts
5
5
  function transformContentPart(part) {
6
6
  if (part.type === "text") {
7
7
  return { type: "input_text", text: part.text };
@@ -871,6 +871,548 @@ async function fetchGenerationMetadata(apiKey, generationId, baseUrl = "https://
871
871
  return null;
872
872
  }
873
873
 
874
+ // src/chat-completions-transformers.ts
875
+ function transformChatContentPart(part) {
876
+ if (part.type === "text") {
877
+ return { type: "text", text: part.text };
878
+ }
879
+ if (part.type === "image") {
880
+ const data = part.data || "";
881
+ const imageUrl = data.startsWith("data:") ? data : `data:${part.mediaType || "image/png"};base64,${data}`;
882
+ return {
883
+ type: "image_url",
884
+ image_url: {
885
+ url: imageUrl,
886
+ detail: part.detail || "auto"
887
+ }
888
+ };
889
+ }
890
+ if (part.type === "image_url") {
891
+ return {
892
+ type: "image_url",
893
+ image_url: {
894
+ url: part.image_url?.url || "",
895
+ detail: part.image_url?.detail || "auto"
896
+ }
897
+ };
898
+ }
899
+ return {
900
+ type: "text",
901
+ text: `[File: ${part.filename || "file"}]`
902
+ };
903
+ }
904
+ function transformChatMessageContent(content) {
905
+ if (typeof content === "string") {
906
+ return content;
907
+ }
908
+ return content.map(transformChatContentPart);
909
+ }
910
+ function transformChatSystemMessage(msg) {
911
+ return {
912
+ role: "system",
913
+ content: msg.content
914
+ };
915
+ }
916
+ function transformChatUserMessage(msg) {
917
+ return {
918
+ role: "user",
919
+ content: transformChatMessageContent(msg.content)
920
+ };
921
+ }
922
+ function transformChatAssistantMessage(msg) {
923
+ const message = {
924
+ role: "assistant",
925
+ content: msg.content || null
926
+ };
927
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
928
+ message.tool_calls = msg.toolCalls.map((tc) => ({
929
+ id: tc.id,
930
+ type: "function",
931
+ function: {
932
+ name: tc.name,
933
+ arguments: typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments)
934
+ }
935
+ }));
936
+ }
937
+ return message;
938
+ }
939
+ function transformChatToolMessage(msg) {
940
+ let output;
941
+ if (typeof msg.content === "string") {
942
+ output = msg.content;
943
+ } else if ("type" in msg.content) {
944
+ if (msg.content.type === "text") {
945
+ output = msg.content.text;
946
+ } else if (msg.content.type === "error") {
947
+ output = `Error: ${msg.content.error}`;
948
+ } else {
949
+ output = JSON.stringify(msg.content);
950
+ }
951
+ } else {
952
+ output = JSON.stringify(msg.content);
953
+ }
954
+ const imageAttachments = msg.attachments?.filter((a) => a.type === "image" && a.data) || [];
955
+ const toolMessage = {
956
+ role: "tool",
957
+ tool_call_id: msg.toolCallId,
958
+ content: output || (imageAttachments.length > 0 ? "Success" : "")
959
+ };
960
+ if (imageAttachments.length === 0) {
961
+ return { toolMessage };
962
+ }
963
+ const imageContent = [];
964
+ const toolName = msg.toolName || "the tool";
965
+ const isSingle = imageAttachments.length === 1;
966
+ const descriptor = isSingle ? "the file" : `${imageAttachments.length} files`;
967
+ const verb = isSingle ? "is" : "are";
968
+ imageContent.push({
969
+ type: "text",
970
+ text: `Here ${verb} ${descriptor} from ${toolName}:`
971
+ });
972
+ for (const attachment of imageAttachments) {
973
+ const attachmentData = attachment.data || "";
974
+ const imageData = attachmentData.startsWith("data:") ? attachmentData : `data:${attachment.mediaType || "image/png"};base64,${attachmentData}`;
975
+ imageContent.push({
976
+ type: "image_url",
977
+ image_url: {
978
+ url: imageData,
979
+ detail: "auto"
980
+ }
981
+ });
982
+ }
983
+ const syntheticUserMessage = {
984
+ role: "user",
985
+ content: imageContent
986
+ };
987
+ return { toolMessage, syntheticUserMessage };
988
+ }
989
+ function transformChatMessages(messages) {
990
+ const result = [];
991
+ for (const msg of messages) {
992
+ switch (msg.role) {
993
+ case "system":
994
+ result.push(transformChatSystemMessage(msg));
995
+ break;
996
+ case "user":
997
+ result.push(transformChatUserMessage(msg));
998
+ break;
999
+ case "assistant":
1000
+ result.push(transformChatAssistantMessage(msg));
1001
+ break;
1002
+ case "tool": {
1003
+ const { toolMessage, syntheticUserMessage } = transformChatToolMessage(msg);
1004
+ result.push(toolMessage);
1005
+ if (syntheticUserMessage) {
1006
+ result.push(syntheticUserMessage);
1007
+ }
1008
+ break;
1009
+ }
1010
+ }
1011
+ }
1012
+ return result;
1013
+ }
1014
+ function transformChatTool(tool) {
1015
+ const inputParams = tool.function.parameters;
1016
+ let parameters;
1017
+ if (inputParams && typeof inputParams === "object") {
1018
+ parameters = {
1019
+ ...inputParams,
1020
+ additionalProperties: false
1021
+ };
1022
+ } else {
1023
+ parameters = {
1024
+ type: "object",
1025
+ properties: {},
1026
+ required: [],
1027
+ additionalProperties: false
1028
+ };
1029
+ }
1030
+ return {
1031
+ type: "function",
1032
+ function: {
1033
+ name: tool.function.name,
1034
+ description: tool.function.description || void 0,
1035
+ parameters,
1036
+ strict: true
1037
+ }
1038
+ };
1039
+ }
1040
+ function transformChatTools(tools) {
1041
+ return tools.map(transformChatTool);
1042
+ }
1043
+ function transformChatToolChoice(choice) {
1044
+ if (choice === "auto") {
1045
+ return "auto";
1046
+ }
1047
+ if (choice === "none") {
1048
+ return "none";
1049
+ }
1050
+ if (choice === "required") {
1051
+ return "required";
1052
+ }
1053
+ if (typeof choice === "object" && "name" in choice) {
1054
+ return { type: "function", function: { name: choice.name } };
1055
+ }
1056
+ return void 0;
1057
+ }
1058
+ function mapChatFinishReason(finishReason) {
1059
+ switch (finishReason) {
1060
+ case "stop":
1061
+ return "stop";
1062
+ case "tool_calls":
1063
+ return "tool_calls";
1064
+ case "length":
1065
+ return "length";
1066
+ case "content_filter":
1067
+ return "content_filter";
1068
+ case "error":
1069
+ return "error";
1070
+ default:
1071
+ return "stop";
1072
+ }
1073
+ }
1074
+ function extractChatToolCalls(toolCalls) {
1075
+ if (!toolCalls || toolCalls.length === 0) {
1076
+ return void 0;
1077
+ }
1078
+ return toolCalls.map((tc) => {
1079
+ let parsedArgs = {};
1080
+ try {
1081
+ parsedArgs = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
1082
+ } catch {
1083
+ }
1084
+ return {
1085
+ id: tc.id,
1086
+ name: tc.function.name,
1087
+ arguments: parsedArgs
1088
+ };
1089
+ });
1090
+ }
1091
+ function transformChatUsage(usage, actualProvider) {
1092
+ if (!usage) {
1093
+ return {
1094
+ promptTokens: 0,
1095
+ completionTokens: 0,
1096
+ totalTokens: 0
1097
+ };
1098
+ }
1099
+ const promptTokens = usage.native_tokens_prompt || usage.prompt_tokens || 0;
1100
+ const completionTokens = usage.native_tokens_completion || usage.completion_tokens || 0;
1101
+ const totalTokens = usage.total_tokens || promptTokens + completionTokens;
1102
+ return {
1103
+ promptTokens,
1104
+ completionTokens,
1105
+ totalTokens,
1106
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
1107
+ cachedTokens: usage.prompt_tokens_details?.cached_tokens,
1108
+ cost: usage.cost,
1109
+ provider: actualProvider
1110
+ };
1111
+ }
1112
+ function extractImageFromUrl(url, index) {
1113
+ if (url.startsWith("data:")) {
1114
+ const match = url.match(/^data:([^;]+);base64,(.+)$/);
1115
+ if (match) {
1116
+ return {
1117
+ id: `image_${index}`,
1118
+ data: match[2],
1119
+ // base64 data without prefix
1120
+ mediaType: match[1]
1121
+ };
1122
+ }
1123
+ return {
1124
+ id: `image_${index}`,
1125
+ data: url,
1126
+ mediaType: "image/png"
1127
+ };
1128
+ }
1129
+ return {
1130
+ id: `image_${index}`,
1131
+ data: url,
1132
+ mediaType: "image/png"
1133
+ // Default; actual type unknown without fetching
1134
+ };
1135
+ }
1136
+ function extractChatImages(images) {
1137
+ if (!images || images.length === 0) {
1138
+ return void 0;
1139
+ }
1140
+ return images.map((img, index) => extractImageFromUrl(img.image_url.url, index));
1141
+ }
1142
+ function transformChatResponse(response) {
1143
+ const choice = response.choices?.[0];
1144
+ const message = choice?.message;
1145
+ const content = message?.content || null;
1146
+ const toolCalls = extractChatToolCalls(message?.tool_calls);
1147
+ const images = extractChatImages(message?.images);
1148
+ const actualProvider = response.model?.split("/")[0] || void 0;
1149
+ return {
1150
+ content,
1151
+ toolCalls,
1152
+ images,
1153
+ finishReason: mapChatFinishReason(choice?.finish_reason),
1154
+ usage: transformChatUsage(response.usage, actualProvider),
1155
+ metadata: {
1156
+ model: response.model,
1157
+ provider: "openrouter",
1158
+ actualProvider,
1159
+ requestId: response.id
1160
+ }
1161
+ };
1162
+ }
1163
+ function createChatStreamState() {
1164
+ return {
1165
+ content: "",
1166
+ toolCalls: /* @__PURE__ */ new Map(),
1167
+ images: [],
1168
+ reasoningContent: "",
1169
+ hasContent: false,
1170
+ hasReasoning: false,
1171
+ finishReason: null
1172
+ };
1173
+ }
1174
+ function processChatStreamChunk(chunk, state) {
1175
+ const chunks = [];
1176
+ const choice = chunk.choices?.[0];
1177
+ const delta = choice?.delta;
1178
+ if (!delta && !choice?.finish_reason && !chunk.usage) {
1179
+ return chunks;
1180
+ }
1181
+ if (delta?.content) {
1182
+ state.hasContent = true;
1183
+ state.content += delta.content;
1184
+ chunks.push({ type: "content-delta", delta: delta.content });
1185
+ }
1186
+ if (delta?.reasoning_content) {
1187
+ state.hasReasoning = true;
1188
+ state.reasoningContent += delta.reasoning_content;
1189
+ chunks.push({ type: "reasoning-delta", delta: delta.reasoning_content });
1190
+ }
1191
+ if (delta?.tool_calls) {
1192
+ for (const tc of delta.tool_calls) {
1193
+ const index = tc.index;
1194
+ const existing = state.toolCalls.get(index);
1195
+ if (tc.id && tc.function?.name) {
1196
+ state.toolCalls.set(index, {
1197
+ id: tc.id,
1198
+ name: tc.function.name,
1199
+ arguments: tc.function.arguments || ""
1200
+ });
1201
+ chunks.push({
1202
+ type: "tool-call-start",
1203
+ id: tc.id,
1204
+ name: tc.function.name
1205
+ });
1206
+ } else if (existing && tc.function?.arguments) {
1207
+ existing.arguments += tc.function.arguments;
1208
+ chunks.push({
1209
+ type: "tool-call-delta",
1210
+ id: existing.id,
1211
+ argumentsDelta: tc.function.arguments
1212
+ });
1213
+ }
1214
+ }
1215
+ }
1216
+ if (delta?.images && delta.images.length > 0) {
1217
+ for (const img of delta.images) {
1218
+ const index = state.images.length;
1219
+ const providerImage = extractImageFromUrl(img.image_url.url, index);
1220
+ state.images.push(providerImage);
1221
+ chunks.push({
1222
+ type: "image-done",
1223
+ index,
1224
+ image: providerImage
1225
+ });
1226
+ }
1227
+ }
1228
+ if (choice?.finish_reason) {
1229
+ state.finishReason = choice.finish_reason;
1230
+ if (state.hasContent) {
1231
+ chunks.push({ type: "content-done" });
1232
+ }
1233
+ if (state.hasReasoning) {
1234
+ chunks.push({ type: "reasoning-done" });
1235
+ }
1236
+ for (const tc of state.toolCalls.values()) {
1237
+ let parsedArgs = {};
1238
+ try {
1239
+ parsedArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
1240
+ } catch {
1241
+ }
1242
+ chunks.push({
1243
+ type: "tool-call-done",
1244
+ id: tc.id,
1245
+ arguments: parsedArgs
1246
+ });
1247
+ }
1248
+ }
1249
+ if (chunk.usage) {
1250
+ const actualProvider = chunk.model?.split("/")[0] || void 0;
1251
+ chunks.push({
1252
+ type: "finish",
1253
+ finishReason: mapChatFinishReason(state.finishReason),
1254
+ usage: transformChatUsage(chunk.usage, actualProvider),
1255
+ responseId: chunk.id
1256
+ });
1257
+ }
1258
+ return chunks;
1259
+ }
1260
+ function parseChatStreamEvent(jsonStr) {
1261
+ try {
1262
+ return JSON.parse(jsonStr);
1263
+ } catch {
1264
+ return null;
1265
+ }
1266
+ }
1267
+ async function* parseChatSSEStream(response, state) {
1268
+ const reader = response.body?.getReader();
1269
+ if (!reader) {
1270
+ throw new Error("No response body");
1271
+ }
1272
+ const decoder = new TextDecoder();
1273
+ let buffer = "";
1274
+ try {
1275
+ while (true) {
1276
+ const { done, value } = await reader.read();
1277
+ if (done) break;
1278
+ buffer += decoder.decode(value, { stream: true });
1279
+ const lines = buffer.split("\n");
1280
+ buffer = lines.pop() || "";
1281
+ for (const line of lines) {
1282
+ const trimmed = line.trim();
1283
+ if (!trimmed || trimmed.startsWith(":")) continue;
1284
+ if (trimmed.startsWith("data: ")) {
1285
+ const data = trimmed.slice(6);
1286
+ if (data === "[DONE]") continue;
1287
+ const chunk = parseChatStreamEvent(data);
1288
+ if (chunk) {
1289
+ const providerChunks = processChatStreamChunk(chunk, state);
1290
+ for (const c of providerChunks) {
1291
+ yield c;
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ }
1297
+ if (buffer.trim()) {
1298
+ const trimmed = buffer.trim();
1299
+ if (trimmed.startsWith("data: ")) {
1300
+ const data = trimmed.slice(6);
1301
+ if (data !== "[DONE]") {
1302
+ const chunk = parseChatStreamEvent(data);
1303
+ if (chunk) {
1304
+ const providerChunks = processChatStreamChunk(chunk, state);
1305
+ for (const c of providerChunks) {
1306
+ yield c;
1307
+ }
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ if (state.finishReason && !state.toolCalls.size) {
1313
+ }
1314
+ } finally {
1315
+ reader.releaseLock();
1316
+ }
1317
+ }
1318
+ function buildChatParams(request) {
1319
+ const messages = transformChatMessages(request.messages);
1320
+ const params = {
1321
+ model: request.model,
1322
+ messages
1323
+ };
1324
+ if (request.tools && request.tools.length > 0) {
1325
+ params.tools = transformChatTools(request.tools);
1326
+ const toolChoice = transformChatToolChoice(request.toolChoice);
1327
+ if (toolChoice !== void 0) {
1328
+ params.tool_choice = toolChoice;
1329
+ }
1330
+ if (request.parallelToolCalls !== void 0) {
1331
+ params.parallel_tool_calls = request.parallelToolCalls;
1332
+ }
1333
+ }
1334
+ if (request.maxOutputTokens !== void 0) {
1335
+ params.max_tokens = request.maxOutputTokens;
1336
+ }
1337
+ if (request.temperature !== void 0) {
1338
+ params.temperature = request.temperature;
1339
+ }
1340
+ if (request.topP !== void 0) {
1341
+ params.top_p = request.topP;
1342
+ }
1343
+ if (request.reasoning?.level !== void 0) {
1344
+ const effortMap = {
1345
+ 10: "minimal",
1346
+ 33: "low",
1347
+ 66: "medium",
1348
+ 100: "high"
1349
+ };
1350
+ const effort = effortMap[request.reasoning.level];
1351
+ if (effort) {
1352
+ params.reasoning = { effort };
1353
+ }
1354
+ }
1355
+ if (request.responseFormat) {
1356
+ if (request.responseFormat.type === "json") {
1357
+ if (request.responseFormat.schema) {
1358
+ params.response_format = {
1359
+ type: "json_schema",
1360
+ json_schema: {
1361
+ name: "response",
1362
+ schema: request.responseFormat.schema,
1363
+ strict: true
1364
+ }
1365
+ };
1366
+ } else {
1367
+ params.response_format = { type: "json_object" };
1368
+ }
1369
+ }
1370
+ }
1371
+ if (request.providerOptions) {
1372
+ const { _metadata, ...safeOptions } = request.providerOptions;
1373
+ Object.assign(params, safeOptions);
1374
+ }
1375
+ return params;
1376
+ }
1377
+ function createChatErrorChunk(error, code) {
1378
+ return { type: "error", error, code };
1379
+ }
1380
+ function isBase64Like2(str) {
1381
+ if (str.startsWith("data:")) return true;
1382
+ if (str.length > 200) {
1383
+ const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
1384
+ return base64Pattern.test(str.substring(0, 200));
1385
+ }
1386
+ return false;
1387
+ }
1388
+ function truncateBase64String2(str, maxLength = 50) {
1389
+ if (str.length <= maxLength) return str;
1390
+ const preview = str.substring(0, maxLength);
1391
+ return `${preview}...[truncated, ${str.length.toLocaleString()} chars]`;
1392
+ }
1393
+ function truncateChatBase64(obj, maxLength = 50) {
1394
+ if (obj === null || obj === void 0) {
1395
+ return obj;
1396
+ }
1397
+ if (typeof obj === "string") {
1398
+ if (isBase64Like2(obj)) {
1399
+ return truncateBase64String2(obj, maxLength);
1400
+ }
1401
+ return obj;
1402
+ }
1403
+ if (Array.isArray(obj)) {
1404
+ return obj.map((item) => truncateChatBase64(item, maxLength));
1405
+ }
1406
+ if (typeof obj === "object") {
1407
+ const result = {};
1408
+ for (const [key, value] of Object.entries(obj)) {
1409
+ result[key] = truncateChatBase64(value, maxLength);
1410
+ }
1411
+ return result;
1412
+ }
1413
+ return obj;
1414
+ }
1415
+
874
1416
  // src/icons.ts
875
1417
  var ANTHROPIC_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
876
1418
  <rect width="48" height="48" fill="#F0EFEA"/>
@@ -1002,6 +1544,17 @@ var OpenRouterProvider = class _OpenRouterProvider {
1002
1544
  constructor(config) {
1003
1545
  this.config = config;
1004
1546
  }
1547
+ /**
1548
+ * Determine which API to use for a request.
1549
+ * Checks request-level providerOptions first, then falls back to config.
1550
+ * Defaults to Chat Completions (stable API).
1551
+ */
1552
+ getApiMode(request) {
1553
+ if (request?.providerOptions?.useResponsesApi === true) {
1554
+ return "responses";
1555
+ }
1556
+ return "chat";
1557
+ }
1005
1558
  async getClient() {
1006
1559
  if (!this.client) {
1007
1560
  const { OpenRouter } = await import("@openrouter/sdk");
@@ -1138,6 +1691,53 @@ var OpenRouterProvider = class _OpenRouterProvider {
1138
1691
  // Generation Methods
1139
1692
  // ============================================================================
1140
1693
  async generate(request) {
1694
+ const apiMode = this.getApiMode(request);
1695
+ if (apiMode === "responses") {
1696
+ return this.generateWithResponses(request);
1697
+ }
1698
+ return this.generateWithChat(request);
1699
+ }
1700
+ /**
1701
+ * Generate using the Chat Completions API (default).
1702
+ */
1703
+ async generateWithChat(request) {
1704
+ const apiKey = this.config.apiKey;
1705
+ const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
1706
+ try {
1707
+ const params = buildChatParams(request);
1708
+ if (this.config.providers && this.config.providers.length > 0) {
1709
+ params.provider = { only: this.config.providers };
1710
+ }
1711
+ const response = await fetch(`${baseUrl}/chat/completions`, {
1712
+ method: "POST",
1713
+ headers: {
1714
+ "Content-Type": "application/json",
1715
+ "Authorization": `Bearer ${apiKey}`
1716
+ },
1717
+ body: JSON.stringify(params),
1718
+ signal: request.signal
1719
+ });
1720
+ if (!response.ok) {
1721
+ const errorText = await response.text();
1722
+ let errorMessage = `OpenRouter API error: ${response.status}`;
1723
+ try {
1724
+ const errorJson = JSON.parse(errorText);
1725
+ errorMessage = errorJson.error?.message || errorJson.message || errorMessage;
1726
+ } catch {
1727
+ errorMessage = errorText || errorMessage;
1728
+ }
1729
+ throw new ProviderError(errorMessage, "invalid_request", response.status);
1730
+ }
1731
+ const data = await response.json();
1732
+ return transformChatResponse(data);
1733
+ } catch (error) {
1734
+ throw this.toProviderError(error);
1735
+ }
1736
+ }
1737
+ /**
1738
+ * Generate using the Responses API (beta).
1739
+ */
1740
+ async generateWithResponses(request) {
1141
1741
  const client = await this.getClient();
1142
1742
  try {
1143
1743
  const params = buildCreateParams(request);
@@ -1154,6 +1754,87 @@ var OpenRouterProvider = class _OpenRouterProvider {
1154
1754
  }
1155
1755
  }
1156
1756
  async stream(request) {
1757
+ const apiMode = this.getApiMode(request);
1758
+ if (apiMode === "responses") {
1759
+ return this.streamWithResponses(request);
1760
+ }
1761
+ return this.streamWithChat(request);
1762
+ }
1763
+ /**
1764
+ * Stream using the Chat Completions API (default).
1765
+ */
1766
+ async streamWithChat(request) {
1767
+ const self = this;
1768
+ const apiKey = this.config.apiKey;
1769
+ const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
1770
+ try {
1771
+ const params = buildChatParams(request);
1772
+ if (this.config.providers && this.config.providers.length > 0) {
1773
+ params.provider = { only: this.config.providers };
1774
+ }
1775
+ const response = await fetch(`${baseUrl}/chat/completions`, {
1776
+ method: "POST",
1777
+ headers: {
1778
+ "Content-Type": "application/json",
1779
+ "Authorization": `Bearer ${apiKey}`
1780
+ },
1781
+ body: JSON.stringify({
1782
+ ...params,
1783
+ stream: true,
1784
+ stream_options: { include_usage: true }
1785
+ }),
1786
+ signal: request.signal
1787
+ });
1788
+ if (!response.ok) {
1789
+ const errorText = await response.text();
1790
+ let errorMessage = `OpenRouter API error: ${response.status}`;
1791
+ try {
1792
+ const errorJson = JSON.parse(errorText);
1793
+ errorMessage = errorJson.error?.message || errorJson.message || errorMessage;
1794
+ } catch {
1795
+ errorMessage = errorText || errorMessage;
1796
+ }
1797
+ throw new ProviderError(errorMessage, "invalid_request", response.status);
1798
+ }
1799
+ return {
1800
+ async *[Symbol.asyncIterator]() {
1801
+ const state = createChatStreamState();
1802
+ let finishChunk = null;
1803
+ let responseId = null;
1804
+ try {
1805
+ for await (const chunk of parseChatSSEStream(response, state)) {
1806
+ if (chunk.type === "finish") {
1807
+ finishChunk = chunk;
1808
+ responseId = chunk.responseId || null;
1809
+ } else {
1810
+ yield chunk;
1811
+ }
1812
+ }
1813
+ if (finishChunk) {
1814
+ const finishWithMeta = {
1815
+ ...finishChunk,
1816
+ _asyncMetadata: responseId ? {
1817
+ generationId: responseId,
1818
+ apiKey,
1819
+ baseUrl
1820
+ } : void 0
1821
+ };
1822
+ yield finishWithMeta;
1823
+ }
1824
+ } catch (error) {
1825
+ const providerError = self.toProviderError(error);
1826
+ yield createChatErrorChunk(providerError.message, providerError.code);
1827
+ }
1828
+ }
1829
+ };
1830
+ } catch (error) {
1831
+ throw this.toProviderError(error);
1832
+ }
1833
+ }
1834
+ /**
1835
+ * Stream using the Responses API (beta).
1836
+ */
1837
+ async streamWithResponses(request) {
1157
1838
  const self = this;
1158
1839
  const apiKey = this.config.apiKey;
1159
1840
  const baseUrl = this.config.baseUrl || "https://openrouter.ai/api/v1";
@@ -1286,19 +1967,33 @@ var OpenRouterProvider = class _OpenRouterProvider {
1286
1967
  // Inspection
1287
1968
  // ============================================================================
1288
1969
  /**
1289
- * Transform a ProviderRequest to OpenRouter Responses API format for inspection.
1970
+ * Transform a ProviderRequest to the appropriate API format for inspection.
1290
1971
  * Returns the exact request body that would be sent to OpenRouter, with base64 data truncated.
1291
1972
  */
1292
1973
  async inspectRequest(request) {
1293
- const params = buildCreateParams(request);
1974
+ const apiMode = this.getApiMode(request);
1975
+ if (apiMode === "responses") {
1976
+ const params2 = buildCreateParams(request);
1977
+ if (this.config.providers && this.config.providers.length > 0) {
1978
+ params2.provider = { only: this.config.providers };
1979
+ }
1980
+ return {
1981
+ body: truncateBase64(params2),
1982
+ messagesPath: "input",
1983
+ metadata: {
1984
+ endpoint: "https://openrouter.ai/api/v1/responses"
1985
+ }
1986
+ };
1987
+ }
1988
+ const params = buildChatParams(request);
1294
1989
  if (this.config.providers && this.config.providers.length > 0) {
1295
1990
  params.provider = { only: this.config.providers };
1296
1991
  }
1297
1992
  return {
1298
- body: truncateBase64(params),
1299
- messagesPath: "input",
1993
+ body: truncateChatBase64(params),
1994
+ messagesPath: "messages",
1300
1995
  metadata: {
1301
- endpoint: "https://openrouter.ai/api/v1/responses"
1996
+ endpoint: "https://openrouter.ai/api/v1/chat/completions"
1302
1997
  }
1303
1998
  };
1304
1999
  }
@@ -1370,7 +2065,17 @@ var providerRoutingSchema = z.object({
1370
2065
  }).passthrough();
1371
2066
  var openrouterProviderOptions = z.object({
1372
2067
  /** Provider routing configuration */
1373
- provider: providerRoutingSchema.optional()
2068
+ provider: providerRoutingSchema.optional(),
2069
+ /**
2070
+ * Use OpenRouter's Responses API (beta) instead of the Chat Completions API.
2071
+ *
2072
+ * By default, the provider uses the stable Chat Completions API.
2073
+ * Set this to true to use the Responses API which has some additional
2074
+ * features but may be less stable.
2075
+ *
2076
+ * @default false
2077
+ */
2078
+ useResponsesApi: z.boolean().optional()
1374
2079
  }).passthrough();
1375
2080
 
1376
2081
  // src/index.ts