@x12i/openrouter-runtime 1.0.1 → 1.0.3
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 +9 -0
- package/dist/index.cjs +109 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -161,7 +161,7 @@ function isRecord(value) {
|
|
|
161
161
|
function resultToChatToolMessage(result) {
|
|
162
162
|
return {
|
|
163
163
|
role: "tool",
|
|
164
|
-
|
|
164
|
+
tool_call_id: result.callId,
|
|
165
165
|
name: result.name,
|
|
166
166
|
content: stringifyToolResult(result.status === "completed" ? result.result : { error: result.error })
|
|
167
167
|
};
|
|
@@ -177,6 +177,9 @@ function resultToResponsesInput(result) {
|
|
|
177
177
|
// src/normalize/citations.ts
|
|
178
178
|
function extractCitations(value) {
|
|
179
179
|
const citations = [];
|
|
180
|
+
if (typeof value === "string") {
|
|
181
|
+
return extractCitationsFromText(value);
|
|
182
|
+
}
|
|
180
183
|
visit(value, (item) => {
|
|
181
184
|
const url = pickString(item, ["url", "uri"]);
|
|
182
185
|
if (!url) return;
|
|
@@ -191,7 +194,37 @@ function extractCitations(value) {
|
|
|
191
194
|
...optional("endIndex", pickNumber(item, ["end_index", "endIndex"]))
|
|
192
195
|
});
|
|
193
196
|
});
|
|
194
|
-
return
|
|
197
|
+
return dedupeCitations(citations);
|
|
198
|
+
}
|
|
199
|
+
function extractCitationsFromText(text) {
|
|
200
|
+
const citations = [];
|
|
201
|
+
const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g;
|
|
202
|
+
for (const match of text.matchAll(markdownLinkPattern)) {
|
|
203
|
+
const title = match[1];
|
|
204
|
+
const url = cleanUrl(match[2]);
|
|
205
|
+
const startIndex = match.index;
|
|
206
|
+
citations.push({
|
|
207
|
+
type: "url",
|
|
208
|
+
url,
|
|
209
|
+
sourceProvider: "openrouter",
|
|
210
|
+
raw: match[0],
|
|
211
|
+
...title ? { title } : {},
|
|
212
|
+
...startIndex !== void 0 ? { startIndex, endIndex: startIndex + match[0].length } : {}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const urlPattern = /https?:\/\/[^\s)]+/g;
|
|
216
|
+
for (const match of text.matchAll(urlPattern)) {
|
|
217
|
+
const url = cleanUrl(match[0]);
|
|
218
|
+
const startIndex = match.index;
|
|
219
|
+
citations.push({
|
|
220
|
+
type: "url",
|
|
221
|
+
url,
|
|
222
|
+
sourceProvider: "openrouter",
|
|
223
|
+
raw: match[0],
|
|
224
|
+
...startIndex !== void 0 ? { startIndex, endIndex: startIndex + match[0].length } : {}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return dedupeCitations(citations);
|
|
195
228
|
}
|
|
196
229
|
function optional(key, value) {
|
|
197
230
|
return value === void 0 ? {} : { [key]: value };
|
|
@@ -213,15 +246,18 @@ function pickNumber(record, keys) {
|
|
|
213
246
|
for (const key of keys) if (typeof record[key] === "number") return record[key];
|
|
214
247
|
return void 0;
|
|
215
248
|
}
|
|
216
|
-
function
|
|
249
|
+
function dedupeCitations(citations) {
|
|
217
250
|
const seen = /* @__PURE__ */ new Set();
|
|
218
251
|
return citations.filter((citation) => {
|
|
219
|
-
const key =
|
|
252
|
+
const key = citation.url;
|
|
220
253
|
if (seen.has(key)) return false;
|
|
221
254
|
seen.add(key);
|
|
222
255
|
return true;
|
|
223
256
|
});
|
|
224
257
|
}
|
|
258
|
+
function cleanUrl(url) {
|
|
259
|
+
return (url ?? "").replace(/[.,;:!?]+$/, "");
|
|
260
|
+
}
|
|
225
261
|
|
|
226
262
|
// src/normalize/images.ts
|
|
227
263
|
function extractImages(value) {
|
|
@@ -403,7 +439,10 @@ function normalizeChatResponse(params) {
|
|
|
403
439
|
apiMode: params.apiMode,
|
|
404
440
|
model: typeof responseRecord.model === "string" ? responseRecord.model : params.model,
|
|
405
441
|
text,
|
|
406
|
-
citations:
|
|
442
|
+
citations: dedupeCitations([
|
|
443
|
+
...extractCitations([message.annotations, responseRecord.citations, params.response]),
|
|
444
|
+
...extractCitations(text)
|
|
445
|
+
]),
|
|
407
446
|
images: extractImages(params.response),
|
|
408
447
|
patches: extractPatches(params.response),
|
|
409
448
|
toolUsage: createToolUsage(params.serverTools, params.response, params.functionToolUsage),
|
|
@@ -433,9 +472,14 @@ function optional3(key, value) {
|
|
|
433
472
|
}
|
|
434
473
|
|
|
435
474
|
// src/policies/post-response-validation.ts
|
|
436
|
-
async function validatePostResponse(response, defaults) {
|
|
475
|
+
async function validatePostResponse(response, defaults, serverTools) {
|
|
437
476
|
const errors = [...response.errors];
|
|
438
477
|
const warnings = [...response.warnings];
|
|
478
|
+
const webSearchUsage = response.toolUsage.serverTools.webSearch;
|
|
479
|
+
if (webSearchUsage.requested && response.citations.length > 0 && !webSearchUsage.used) {
|
|
480
|
+
webSearchUsage.used = true;
|
|
481
|
+
webSearchUsage.callCount = Math.max(webSearchUsage.callCount ?? 0, 1);
|
|
482
|
+
}
|
|
439
483
|
for (const [key, usage] of Object.entries(response.toolUsage.serverTools)) {
|
|
440
484
|
if (usage.required && !usage.used) {
|
|
441
485
|
const code = `${toScreamingSnake(key)}_REQUIRED_BUT_NOT_USED`;
|
|
@@ -448,11 +492,18 @@ async function validatePostResponse(response, defaults) {
|
|
|
448
492
|
}
|
|
449
493
|
}
|
|
450
494
|
}
|
|
451
|
-
|
|
452
|
-
|
|
495
|
+
const citationsRequired = serverTools?.webSearch?.requireCitations ?? defaults.requireCitationsWhenSearchUsed;
|
|
496
|
+
if (citationsRequired && webSearchUsage.used && response.citations.length === 0) {
|
|
497
|
+
const violation = {
|
|
453
498
|
code: "CITATIONS_REQUIRED_BUT_MISSING",
|
|
454
|
-
message: "Web search was
|
|
455
|
-
}
|
|
499
|
+
message: "Web search was used but no citations were extracted."
|
|
500
|
+
};
|
|
501
|
+
webSearchUsage.policyViolation = webSearchUsage.policyViolation ?? violation.code;
|
|
502
|
+
if (defaults.onPolicyViolation === "throw") {
|
|
503
|
+
errors.push({ ...violation, source: "policy" });
|
|
504
|
+
} else {
|
|
505
|
+
warnings.push(violation);
|
|
506
|
+
}
|
|
456
507
|
}
|
|
457
508
|
return {
|
|
458
509
|
...response,
|
|
@@ -832,7 +883,7 @@ function compileMessages(request) {
|
|
|
832
883
|
if (request.system) messages.push({ role: "system", content: request.system });
|
|
833
884
|
if (request.messages) messages.push(...request.messages);
|
|
834
885
|
if (request.prompt) messages.push({ role: "user", content: request.prompt });
|
|
835
|
-
return messages;
|
|
886
|
+
return messages.map(compileMessage);
|
|
836
887
|
}
|
|
837
888
|
function compilePromptInput(request) {
|
|
838
889
|
if (request.prompt) return request.prompt;
|
|
@@ -841,6 +892,32 @@ function compilePromptInput(request) {
|
|
|
841
892
|
content: message.content
|
|
842
893
|
}));
|
|
843
894
|
}
|
|
895
|
+
function compileMessage(message) {
|
|
896
|
+
return clean({
|
|
897
|
+
role: message.role,
|
|
898
|
+
content: compileContent(message.content),
|
|
899
|
+
name: message.name,
|
|
900
|
+
tool_call_id: message.toolCallId
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function compileContent(content) {
|
|
904
|
+
if (typeof content === "string") return content;
|
|
905
|
+
return content.map((part) => {
|
|
906
|
+
if (part.type === "image_url") {
|
|
907
|
+
return { type: "image_url", image_url: { url: part.imageUrl } };
|
|
908
|
+
}
|
|
909
|
+
if (part.type === "file") {
|
|
910
|
+
return clean({
|
|
911
|
+
type: "file",
|
|
912
|
+
file_name: part.fileName,
|
|
913
|
+
mime_type: part.mimeType,
|
|
914
|
+
data: part.data,
|
|
915
|
+
url: part.url
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
return part;
|
|
919
|
+
});
|
|
920
|
+
}
|
|
844
921
|
function compileToolChoice(choice, serverTools) {
|
|
845
922
|
if (!choice || choice === "auto") return void 0;
|
|
846
923
|
if (choice === "none" || choice === "required") return choice;
|
|
@@ -968,7 +1045,7 @@ async function executeFunctionCalls(params) {
|
|
|
968
1045
|
name: result.name,
|
|
969
1046
|
callId: result.callId,
|
|
970
1047
|
status: result.status,
|
|
971
|
-
args: call.args,
|
|
1048
|
+
args: parseToolArgs(call.args),
|
|
972
1049
|
...result.result !== void 0 ? { result: result.result } : {},
|
|
973
1050
|
...result.error !== void 0 ? { error: result.error } : {},
|
|
974
1051
|
...result.durationMs !== void 0 ? { durationMs: result.durationMs } : {}
|
|
@@ -1004,10 +1081,13 @@ async function runChat(request, compiled, context) {
|
|
|
1004
1081
|
functionUsage.push(...results);
|
|
1005
1082
|
const body = activeCompiled.body;
|
|
1006
1083
|
const messages = Array.isArray(body.messages) ? [...body.messages] : [];
|
|
1084
|
+
const assistantMessage = extractAssistantMessage(response);
|
|
1085
|
+
if (assistantMessage) messages.push(assistantMessage);
|
|
1007
1086
|
messages.push(...results.map(resultToChatToolMessage));
|
|
1008
|
-
activeCompiled = { ...activeCompiled, body: { ...body, messages } };
|
|
1087
|
+
activeCompiled = { ...activeCompiled, body: { ...body, messages, tool_choice: "auto" } };
|
|
1009
1088
|
}
|
|
1010
1089
|
const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1090
|
+
const serverTools = mergeServerTools(context.defaults.serverTools, request.serverTools);
|
|
1011
1091
|
const normalized = normalizeChatResponse({
|
|
1012
1092
|
requestBody: activeCompiled.body,
|
|
1013
1093
|
response,
|
|
@@ -1015,25 +1095,33 @@ async function runChat(request, compiled, context) {
|
|
|
1015
1095
|
model,
|
|
1016
1096
|
warnings: compiled.warnings,
|
|
1017
1097
|
functionToolUsage: functionUsage,
|
|
1018
|
-
...optional4("serverTools",
|
|
1098
|
+
...optional4("serverTools", serverTools),
|
|
1019
1099
|
...optional4("metadata", request.metadata)
|
|
1020
1100
|
});
|
|
1021
|
-
return validatePostResponse(normalized, context.defaults);
|
|
1101
|
+
return validatePostResponse(normalized, context.defaults, serverTools);
|
|
1022
1102
|
}
|
|
1023
1103
|
function optional4(key, value) {
|
|
1024
1104
|
return value === void 0 ? {} : { [key]: value };
|
|
1025
1105
|
}
|
|
1106
|
+
function extractAssistantMessage(response) {
|
|
1107
|
+
const record = isRecord(response) ? response : {};
|
|
1108
|
+
const choice = Array.isArray(record.choices) && isRecord(record.choices[0]) ? record.choices[0] : {};
|
|
1109
|
+
const message = isRecord(choice.message) ? choice.message : void 0;
|
|
1110
|
+
if (!message) return void 0;
|
|
1111
|
+
return message;
|
|
1112
|
+
}
|
|
1026
1113
|
|
|
1027
1114
|
// src/normalize/normalize-responses-response.ts
|
|
1028
1115
|
function normalizeResponsesResponse(params) {
|
|
1029
1116
|
const record = isRecord(params.response) ? params.response : {};
|
|
1117
|
+
const text = extractOutputText(record);
|
|
1030
1118
|
return {
|
|
1031
1119
|
id: typeof record.id === "string" ? record.id : createRuntimeId("orresp"),
|
|
1032
1120
|
status: record.status === "requires_action" ? "requires_action" : "completed",
|
|
1033
1121
|
apiMode: "responses",
|
|
1034
1122
|
model: typeof record.model === "string" ? record.model : params.model,
|
|
1035
|
-
text
|
|
1036
|
-
citations: extractCitations(params.response),
|
|
1123
|
+
text,
|
|
1124
|
+
citations: dedupeCitations([...extractCitations(params.response), ...extractCitations(text)]),
|
|
1037
1125
|
images: extractImages(params.response),
|
|
1038
1126
|
patches: extractPatches(params.response),
|
|
1039
1127
|
toolUsage: createToolUsage(params.serverTools, params.response, params.functionToolUsage),
|
|
@@ -1095,22 +1183,24 @@ async function runResponses(request, compiled, context) {
|
|
|
1095
1183
|
...activeCompiled,
|
|
1096
1184
|
body: {
|
|
1097
1185
|
...activeCompiled.body,
|
|
1186
|
+
tool_choice: "auto",
|
|
1098
1187
|
previous_response_id: isRecord(response) && typeof response.id === "string" ? response.id : void 0,
|
|
1099
1188
|
input: results.map(resultToResponsesInput)
|
|
1100
1189
|
}
|
|
1101
1190
|
};
|
|
1102
1191
|
}
|
|
1103
1192
|
const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1193
|
+
const serverTools = mergeServerTools(context.defaults.serverTools, request.serverTools);
|
|
1104
1194
|
const normalized = normalizeResponsesResponse({
|
|
1105
1195
|
requestBody: activeCompiled.body,
|
|
1106
1196
|
response,
|
|
1107
1197
|
model,
|
|
1108
1198
|
warnings: compiled.warnings,
|
|
1109
1199
|
functionToolUsage: functionUsage,
|
|
1110
|
-
...optional6("serverTools",
|
|
1200
|
+
...optional6("serverTools", serverTools),
|
|
1111
1201
|
...optional6("metadata", request.metadata)
|
|
1112
1202
|
});
|
|
1113
|
-
return validatePostResponse(normalized, context.defaults);
|
|
1203
|
+
return validatePostResponse(normalized, context.defaults, serverTools);
|
|
1114
1204
|
}
|
|
1115
1205
|
function optional6(key, value) {
|
|
1116
1206
|
return value === void 0 ? {} : { [key]: value };
|