@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.d.ts +42 -3
- package/dist/index.js +712 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
|
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:
|
|
1299
|
-
messagesPath: "
|
|
1993
|
+
body: truncateChatBase64(params),
|
|
1994
|
+
messagesPath: "messages",
|
|
1300
1995
|
metadata: {
|
|
1301
|
-
endpoint: "https://openrouter.ai/api/v1/
|
|
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
|