@x12i/openrouter-runtime 1.0.6 → 1.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/LICENSE +21 -21
- package/README.md +160 -140
- package/dist/index.cjs +483 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -6
- package/dist/index.d.ts +113 -6
- package/dist/index.js +479 -34
- package/dist/index.js.map +1 -1
- package/package.json +41 -41
package/dist/index.js
CHANGED
|
@@ -722,13 +722,22 @@ function endpointFor(baseUrl, apiMode) {
|
|
|
722
722
|
}
|
|
723
723
|
|
|
724
724
|
// src/openrouter/headers.ts
|
|
725
|
-
function buildHeaders(options) {
|
|
725
|
+
function buildHeaders(options, agentId) {
|
|
726
|
+
const requestAppName = agentId?.trim();
|
|
726
727
|
return {
|
|
727
728
|
"content-type": "application/json",
|
|
728
729
|
authorization: `Bearer ${options.apiKey}`,
|
|
729
730
|
...options.appAttribution?.siteUrl ? { "HTTP-Referer": options.appAttribution.siteUrl } : {},
|
|
730
|
-
...options.appAttribution?.appName
|
|
731
|
-
...options.defaultHeaders ?? {}
|
|
731
|
+
...appTitleHeaders(options.appAttribution?.appName),
|
|
732
|
+
...options.defaultHeaders ?? {},
|
|
733
|
+
...requestAppName ? appTitleHeaders(requestAppName) : {}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function appTitleHeaders(name) {
|
|
737
|
+
if (!name) return {};
|
|
738
|
+
return {
|
|
739
|
+
"X-Title": name,
|
|
740
|
+
"X-OpenRouter-Title": name
|
|
732
741
|
};
|
|
733
742
|
}
|
|
734
743
|
|
|
@@ -757,7 +766,7 @@ function cleanParameters(parameters) {
|
|
|
757
766
|
function enabled(policy) {
|
|
758
767
|
return !!policy && policy.mode !== "disabled";
|
|
759
768
|
}
|
|
760
|
-
function buildOpenRouterServerTools(policy) {
|
|
769
|
+
function buildOpenRouterServerTools(policy, fallbackReasoning) {
|
|
761
770
|
const tools = [];
|
|
762
771
|
const webSearch = buildWebSearchTool(policy?.webSearch);
|
|
763
772
|
if (webSearch) tools.push(webSearch);
|
|
@@ -769,10 +778,10 @@ function buildOpenRouterServerTools(policy) {
|
|
|
769
778
|
if (image) tools.push(image);
|
|
770
779
|
const patch = buildApplyPatchTool(policy?.applyPatch);
|
|
771
780
|
if (patch) tools.push(patch);
|
|
772
|
-
const fusion = buildFusionTool(policy?.fusion);
|
|
781
|
+
const fusion = buildFusionTool(policy?.fusion, fallbackReasoning);
|
|
773
782
|
if (fusion) tools.push(fusion);
|
|
774
|
-
tools.push(...buildAdvisorTools(policy?.advisor));
|
|
775
|
-
const subagent = buildSubagentTool(policy?.subagent);
|
|
783
|
+
tools.push(...buildAdvisorTools(policy?.advisor, fallbackReasoning));
|
|
784
|
+
const subagent = buildSubagentTool(policy?.subagent, fallbackReasoning);
|
|
776
785
|
if (subagent) tools.push(subagent);
|
|
777
786
|
return tools;
|
|
778
787
|
}
|
|
@@ -831,7 +840,7 @@ function buildApplyPatchTool(policy) {
|
|
|
831
840
|
if (!enabled(policy)) return void 0;
|
|
832
841
|
return { type: "openrouter:apply_patch" };
|
|
833
842
|
}
|
|
834
|
-
function buildFusionTool(policy) {
|
|
843
|
+
function buildFusionTool(policy, fallbackReasoning) {
|
|
835
844
|
if (!enabled(policy)) return void 0;
|
|
836
845
|
return {
|
|
837
846
|
type: "openrouter:fusion",
|
|
@@ -840,12 +849,12 @@ function buildFusionTool(policy) {
|
|
|
840
849
|
model: policy?.judgeModel,
|
|
841
850
|
max_tool_calls: policy?.maxToolCalls ?? 8,
|
|
842
851
|
max_completion_tokens: policy?.maxCompletionTokens,
|
|
843
|
-
reasoning: policy?.reasoning,
|
|
852
|
+
reasoning: policy?.reasoning ?? fallbackReasoning,
|
|
844
853
|
temperature: policy?.temperature
|
|
845
854
|
})
|
|
846
855
|
};
|
|
847
856
|
}
|
|
848
|
-
function buildAdvisorTools(policy) {
|
|
857
|
+
function buildAdvisorTools(policy, fallbackReasoning) {
|
|
849
858
|
const advisors = Array.isArray(policy) ? policy : policy ? [policy] : [];
|
|
850
859
|
return advisors.filter(enabled).map((advisor) => {
|
|
851
860
|
validateNestedTools(advisor.tools);
|
|
@@ -860,13 +869,13 @@ function buildAdvisorTools(policy) {
|
|
|
860
869
|
stream: advisor.stream,
|
|
861
870
|
max_tool_calls: advisor.maxToolCalls,
|
|
862
871
|
max_completion_tokens: advisor.maxCompletionTokens,
|
|
863
|
-
reasoning: advisor.reasoning,
|
|
872
|
+
reasoning: advisor.reasoning ?? fallbackReasoning,
|
|
864
873
|
temperature: advisor.temperature
|
|
865
874
|
})
|
|
866
875
|
};
|
|
867
876
|
});
|
|
868
877
|
}
|
|
869
|
-
function buildSubagentTool(policy) {
|
|
878
|
+
function buildSubagentTool(policy, fallbackReasoning) {
|
|
870
879
|
if (!policy || policy.mode === "disabled") return void 0;
|
|
871
880
|
const subagent = policy;
|
|
872
881
|
validateNestedTools(subagent.tools);
|
|
@@ -878,12 +887,86 @@ function buildSubagentTool(policy) {
|
|
|
878
887
|
tools: subagent.tools,
|
|
879
888
|
max_tool_calls: subagent.maxToolCalls,
|
|
880
889
|
max_completion_tokens: subagent.maxCompletionTokens,
|
|
881
|
-
reasoning: subagent.reasoning,
|
|
890
|
+
reasoning: subagent.reasoning ?? fallbackReasoning,
|
|
882
891
|
temperature: subagent.temperature
|
|
883
892
|
})
|
|
884
893
|
};
|
|
885
894
|
}
|
|
886
895
|
|
|
896
|
+
// src/utils/provider-metadata.ts
|
|
897
|
+
var OPENROUTER_METADATA_LIMITS = {
|
|
898
|
+
maxPairs: 16,
|
|
899
|
+
maxKeyLength: 64,
|
|
900
|
+
maxValueLength: 512
|
|
901
|
+
};
|
|
902
|
+
var PROVIDER_RECORD_CHUNK = 256;
|
|
903
|
+
var PROVIDER_RECORD_MAX_CHUNKS = 16;
|
|
904
|
+
function toProviderMetadata(metadata, agentId, _limits, base) {
|
|
905
|
+
return encodeProviderRecord(collectProviderStrings(metadata, agentId, base));
|
|
906
|
+
}
|
|
907
|
+
function decodeProviderMetadata(wire) {
|
|
908
|
+
if (!isRecord2(wire)) return {};
|
|
909
|
+
const parts = [];
|
|
910
|
+
for (let index = 0; ; index += 1) {
|
|
911
|
+
const chunk = wire[`m.${index}`];
|
|
912
|
+
if (typeof chunk !== "string") break;
|
|
913
|
+
parts.push(chunk);
|
|
914
|
+
}
|
|
915
|
+
if (!parts.length) return {};
|
|
916
|
+
const parsed = JSON.parse(Buffer.from(parts.join(""), "base64").toString("utf8"));
|
|
917
|
+
if (!isRecord2(parsed)) return {};
|
|
918
|
+
const record = {};
|
|
919
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
920
|
+
if (typeof value === "string") record[key] = value;
|
|
921
|
+
}
|
|
922
|
+
return record;
|
|
923
|
+
}
|
|
924
|
+
function encodeProviderRecord(map) {
|
|
925
|
+
const keys = Object.keys(map).sort();
|
|
926
|
+
if (!keys.length) return void 0;
|
|
927
|
+
const ordered = {};
|
|
928
|
+
for (const key of keys) {
|
|
929
|
+
const value = map[key];
|
|
930
|
+
if (value !== void 0) ordered[key] = value;
|
|
931
|
+
}
|
|
932
|
+
const encoded = Buffer.from(JSON.stringify(ordered), "utf8").toString("base64");
|
|
933
|
+
const chunks = Math.ceil(encoded.length / PROVIDER_RECORD_CHUNK);
|
|
934
|
+
if (chunks > PROVIDER_RECORD_MAX_CHUNKS) {
|
|
935
|
+
throw new Error(
|
|
936
|
+
`METADATA_RECORD_TOO_LARGE: provider metadata is ${encoded.length} base64 characters; the maximum is ${PROVIDER_RECORD_CHUNK * PROVIDER_RECORD_MAX_CHUNKS}.`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
const wire = {};
|
|
940
|
+
for (let index = 0; index < chunks; index += 1) {
|
|
941
|
+
wire[`m.${index}`] = encoded.slice(index * PROVIDER_RECORD_CHUNK, (index + 1) * PROVIDER_RECORD_CHUNK);
|
|
942
|
+
}
|
|
943
|
+
return wire;
|
|
944
|
+
}
|
|
945
|
+
function collectProviderStrings(metadata, agentId, base) {
|
|
946
|
+
const map = {};
|
|
947
|
+
appendStrings(map, base);
|
|
948
|
+
appendStrings(map, metadata);
|
|
949
|
+
const trimmedAgentId = agentId?.trim();
|
|
950
|
+
if (trimmedAgentId) map.agentId = trimmedAgentId;
|
|
951
|
+
return map;
|
|
952
|
+
}
|
|
953
|
+
function appendStrings(target, metadata) {
|
|
954
|
+
if (!metadata) return;
|
|
955
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
956
|
+
const asString = asMetadataString(value);
|
|
957
|
+
if (asString !== void 0) target[key] = asString;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function asMetadataString(value) {
|
|
961
|
+
if (typeof value === "string") return value;
|
|
962
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
963
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
964
|
+
return void 0;
|
|
965
|
+
}
|
|
966
|
+
function isRecord2(value) {
|
|
967
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
968
|
+
}
|
|
969
|
+
|
|
887
970
|
// src/runtime/runtime.ts
|
|
888
971
|
function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, context = {}) {
|
|
889
972
|
const warnings = [];
|
|
@@ -895,7 +978,10 @@ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, cont
|
|
|
895
978
|
const apiMode = resolveApiMode(request, defaults.apiMode, serverTools);
|
|
896
979
|
validateRuntimeRequest(request, options, apiMode, normalized.model, serverTools);
|
|
897
980
|
const tools = [
|
|
898
|
-
...buildOpenRouterServerTools(
|
|
981
|
+
...buildOpenRouterServerTools(
|
|
982
|
+
serverTools,
|
|
983
|
+
effectiveContext.suppressNestedReasoningFallback ? void 0 : request.reasoning
|
|
984
|
+
),
|
|
899
985
|
...(request.functionTools ?? []).map((tool) => ({
|
|
900
986
|
type: "function",
|
|
901
987
|
function: {
|
|
@@ -905,10 +991,12 @@ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, cont
|
|
|
905
991
|
}
|
|
906
992
|
}))
|
|
907
993
|
];
|
|
908
|
-
const headers = buildHeaders(options);
|
|
994
|
+
const headers = buildHeaders(options, request.agentId);
|
|
909
995
|
const baseBody = buildBaseBody(request, normalized.model, defaults, tools, serverTools);
|
|
910
|
-
const
|
|
996
|
+
const assembled = apiMode === "chat" ? buildChatBody(request, baseBody) : buildResponsesBody(request, baseBody);
|
|
997
|
+
let body = effectiveContext.finalizeBody ? effectiveContext.finalizeBody(assembled) : assembled;
|
|
911
998
|
enforceStreamingPolicyForRun(body, warnings, effectiveContext);
|
|
999
|
+
body = attachProviderMetadata(body, request);
|
|
912
1000
|
return {
|
|
913
1001
|
apiMode,
|
|
914
1002
|
url: endpointFor(options.baseUrl ?? "https://openrouter.ai/api/v1", apiMode),
|
|
@@ -917,12 +1005,24 @@ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, cont
|
|
|
917
1005
|
warnings
|
|
918
1006
|
};
|
|
919
1007
|
}
|
|
1008
|
+
function attachProviderMetadata(body, request) {
|
|
1009
|
+
const fromBody = isRecord(body.metadata) ? body.metadata : void 0;
|
|
1010
|
+
const metadata = toProviderMetadata(request.metadata, request.agentId, OPENROUTER_METADATA_LIMITS, fromBody);
|
|
1011
|
+
if (metadata) return { ...body, metadata };
|
|
1012
|
+
if (body.metadata === void 0) return body;
|
|
1013
|
+
const rest = { ...body };
|
|
1014
|
+
delete rest.metadata;
|
|
1015
|
+
return rest;
|
|
1016
|
+
}
|
|
920
1017
|
function enforceStreamingPolicyForRun(body, warnings, context) {
|
|
921
|
-
if (context.streaming)
|
|
1018
|
+
if (context.streaming) {
|
|
1019
|
+
body.stream = true;
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
922
1022
|
if (body.stream !== void 0 && body.stream !== false) {
|
|
923
1023
|
warnings.push({
|
|
924
1024
|
code: "STREAMING_OVERRIDE_IGNORED_FOR_RUN",
|
|
925
|
-
message: "runtime.run() is non-streaming. Use runtime.executeStreamingChat()
|
|
1025
|
+
message: "runtime.run() is non-streaming. Use runtime.executeStreamingChat() for streaming.",
|
|
926
1026
|
details: { ignoredStreamValue: body.stream }
|
|
927
1027
|
});
|
|
928
1028
|
}
|
|
@@ -1325,12 +1425,348 @@ function optional6(key, value) {
|
|
|
1325
1425
|
return value === void 0 ? {} : { [key]: value };
|
|
1326
1426
|
}
|
|
1327
1427
|
|
|
1428
|
+
// src/openrouter/stream-client.ts
|
|
1429
|
+
async function openOpenRouterStream(compiled, options, timeoutMs) {
|
|
1430
|
+
const controller = new AbortController();
|
|
1431
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1432
|
+
const cleanup = () => clearTimeout(timeout);
|
|
1433
|
+
try {
|
|
1434
|
+
const response = await options.fetch(compiled.url, {
|
|
1435
|
+
method: "POST",
|
|
1436
|
+
headers: {
|
|
1437
|
+
...compiled.headers,
|
|
1438
|
+
Accept: "text/event-stream"
|
|
1439
|
+
},
|
|
1440
|
+
body: JSON.stringify(compiled.body),
|
|
1441
|
+
signal: controller.signal
|
|
1442
|
+
});
|
|
1443
|
+
if (!response.ok) {
|
|
1444
|
+
const text = await response.text();
|
|
1445
|
+
cleanup();
|
|
1446
|
+
const body = text ? parseBody2(text) : void 0;
|
|
1447
|
+
const message = extractMessage2(body) ?? `OpenRouter stream failed with status ${response.status}.`;
|
|
1448
|
+
throw new OpenRouterHttpError(
|
|
1449
|
+
response.status,
|
|
1450
|
+
classifyProviderFailure2(response.status, message, body),
|
|
1451
|
+
message,
|
|
1452
|
+
body,
|
|
1453
|
+
isRetryableStatus2(response.status)
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
return { response, cleanup };
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
cleanup();
|
|
1459
|
+
if (error instanceof OpenRouterHttpError) throw error;
|
|
1460
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1461
|
+
throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter stream timed out.", void 0, true);
|
|
1462
|
+
}
|
|
1463
|
+
throw new OpenRouterHttpError(
|
|
1464
|
+
503,
|
|
1465
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1466
|
+
error instanceof Error ? error.message : "OpenRouter stream failed before receiving a response.",
|
|
1467
|
+
void 0,
|
|
1468
|
+
true
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
async function* parseSseStream(response) {
|
|
1473
|
+
const body = response.body;
|
|
1474
|
+
if (!body) {
|
|
1475
|
+
throw new OpenRouterHttpError(
|
|
1476
|
+
503,
|
|
1477
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1478
|
+
"OpenRouter stream response had no body.",
|
|
1479
|
+
void 0,
|
|
1480
|
+
true
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
const reader = body.getReader();
|
|
1484
|
+
const decoder = new TextDecoder();
|
|
1485
|
+
let buffer = "";
|
|
1486
|
+
while (true) {
|
|
1487
|
+
const { done, value } = await reader.read();
|
|
1488
|
+
if (done) break;
|
|
1489
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1490
|
+
const parts = buffer.split(/\r?\n/);
|
|
1491
|
+
buffer = parts.pop() ?? "";
|
|
1492
|
+
for (const part of parts) {
|
|
1493
|
+
const line = part.trim();
|
|
1494
|
+
if (!line || line.startsWith(":")) continue;
|
|
1495
|
+
if (!line.startsWith("data:")) continue;
|
|
1496
|
+
const data = line.slice(5).trim();
|
|
1497
|
+
if (!data) continue;
|
|
1498
|
+
if (data === "[DONE]") return;
|
|
1499
|
+
try {
|
|
1500
|
+
yield JSON.parse(data);
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
const trailing = buffer.trim();
|
|
1506
|
+
if (trailing.startsWith("data:")) {
|
|
1507
|
+
const data = trailing.slice(5).trim();
|
|
1508
|
+
if (data && data !== "[DONE]") {
|
|
1509
|
+
try {
|
|
1510
|
+
yield JSON.parse(data);
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function parseBody2(text) {
|
|
1517
|
+
try {
|
|
1518
|
+
return JSON.parse(text);
|
|
1519
|
+
} catch {
|
|
1520
|
+
return text;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function extractMessage2(body) {
|
|
1524
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1525
|
+
const record = body;
|
|
1526
|
+
const error = record.error;
|
|
1527
|
+
if (typeof error === "object" && error !== null && typeof error.message === "string") {
|
|
1528
|
+
return error.message;
|
|
1529
|
+
}
|
|
1530
|
+
return typeof record.message === "string" ? record.message : void 0;
|
|
1531
|
+
}
|
|
1532
|
+
function classifyProviderFailure2(status, message, body) {
|
|
1533
|
+
const haystack = `${message} ${JSON.stringify(body ?? "")}`.toLowerCase();
|
|
1534
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1535
|
+
if (status === 402 || /\b(quota|credit|credits|billing|balance|insufficient funds|insufficient credit)\b/.test(haystack)) {
|
|
1536
|
+
return "PROVIDER_QUOTA_EXCEEDED";
|
|
1537
|
+
}
|
|
1538
|
+
if (status === 404 || /\bmodel\b.*\b(not found|not a valid|invalid|unavailable)\b/.test(haystack) || /\b(no endpoints found|not a valid model id)\b/.test(haystack)) {
|
|
1539
|
+
return "PROVIDER_MODEL_NOT_FOUND";
|
|
1540
|
+
}
|
|
1541
|
+
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
1542
|
+
if (status === 408 || status >= 500) return "PROVIDER_GATEWAY_UNREACHABLE";
|
|
1543
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
1544
|
+
}
|
|
1545
|
+
function isRetryableStatus2(status) {
|
|
1546
|
+
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1328
1549
|
// src/runtime/streaming.ts
|
|
1329
|
-
async function*
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1550
|
+
async function* executeStreamingChat(request, context, call = {}) {
|
|
1551
|
+
const requestId = request.id ?? "unknown";
|
|
1552
|
+
const options = context.options;
|
|
1553
|
+
const defaults = context.defaults;
|
|
1554
|
+
options.logger?.info?.("runtime.executeStreamingChat.started", {
|
|
1555
|
+
requestId,
|
|
1556
|
+
streaming: true,
|
|
1557
|
+
entrypoint: "executeStreamingChat"
|
|
1558
|
+
});
|
|
1559
|
+
try {
|
|
1560
|
+
assertStreamingChatRequest(request, defaults);
|
|
1561
|
+
const streamingRequest = {
|
|
1562
|
+
...request,
|
|
1563
|
+
apiMode: "chat"
|
|
1564
|
+
};
|
|
1565
|
+
const compiled = compileRuntimeRequest(streamingRequest, defaults, options, {
|
|
1566
|
+
streaming: true,
|
|
1567
|
+
...call.finalizeBody ? { finalizeBody: call.finalizeBody } : {},
|
|
1568
|
+
...call.suppressNestedReasoningFallback ? { suppressNestedReasoningFallback: true } : {}
|
|
1569
|
+
});
|
|
1570
|
+
if (compiled.body.stream !== true) {
|
|
1571
|
+
throw new RuntimeConfigError(
|
|
1572
|
+
"STREAMING_CHAT_MISCONFIGURED",
|
|
1573
|
+
"executeStreamingChat() requires stream=true on the compiled OpenRouter body."
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
for (const warning of compiled.warnings) {
|
|
1577
|
+
yield warningEvent(requestId, warning);
|
|
1578
|
+
}
|
|
1579
|
+
const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1580
|
+
yield startEvent(requestId, model);
|
|
1581
|
+
await options.hooks?.beforeOpenRouterRequest?.(compiled);
|
|
1582
|
+
const timeoutMs = request.execution?.timeoutMs ?? defaults.timeoutMs;
|
|
1583
|
+
const session = await openOpenRouterStream(compiled, options, timeoutMs);
|
|
1584
|
+
let text = "";
|
|
1585
|
+
let finishReason;
|
|
1586
|
+
let resolvedModel = model;
|
|
1587
|
+
try {
|
|
1588
|
+
for await (const chunk of parseSseStream(session.response)) {
|
|
1589
|
+
if (!isRecord(chunk)) continue;
|
|
1590
|
+
if (chunk.error) {
|
|
1591
|
+
const message = isRecord(chunk.error) && typeof chunk.error.message === "string" ? chunk.error.message : "OpenRouter stream returned an error chunk.";
|
|
1592
|
+
const error = {
|
|
1593
|
+
code: "PROVIDER_REQUEST_FAILED",
|
|
1594
|
+
message,
|
|
1595
|
+
source: "openrouter",
|
|
1596
|
+
details: chunk.error
|
|
1597
|
+
};
|
|
1598
|
+
yield errorEvent(requestId, error);
|
|
1599
|
+
throw new OpenRouterHttpError(500, "PROVIDER_REQUEST_FAILED", message, chunk, false);
|
|
1600
|
+
}
|
|
1601
|
+
if (typeof chunk.model === "string") resolvedModel = chunk.model;
|
|
1602
|
+
const choice = Array.isArray(chunk.choices) && isRecord(chunk.choices[0]) ? chunk.choices[0] : void 0;
|
|
1603
|
+
const delta = choice && isRecord(choice.delta) ? choice.delta : void 0;
|
|
1604
|
+
if (delta && call.extractStreamReasoning) {
|
|
1605
|
+
const reasoningText = call.extractStreamReasoning(delta);
|
|
1606
|
+
if (reasoningText) yield reasoningDeltaEvent(requestId, reasoningText);
|
|
1607
|
+
}
|
|
1608
|
+
if (delta && typeof delta.content === "string" && delta.content.length > 0) {
|
|
1609
|
+
text += delta.content;
|
|
1610
|
+
yield textDeltaEvent(requestId, delta.content);
|
|
1611
|
+
}
|
|
1612
|
+
if (delta && Array.isArray(delta.tool_calls)) {
|
|
1613
|
+
for (const toolCall of delta.tool_calls) {
|
|
1614
|
+
if (!isRecord(toolCall)) continue;
|
|
1615
|
+
const index = typeof toolCall.index === "number" ? toolCall.index : 0;
|
|
1616
|
+
const fn = isRecord(toolCall.function) ? toolCall.function : {};
|
|
1617
|
+
yield toolCallDeltaEvent(requestId, {
|
|
1618
|
+
index,
|
|
1619
|
+
...typeof toolCall.id === "string" ? { id: toolCall.id } : {},
|
|
1620
|
+
...typeof fn.name === "string" ? { name: fn.name } : {},
|
|
1621
|
+
...typeof fn.arguments === "string" ? { argumentsDelta: fn.arguments } : {}
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (choice && typeof choice.finish_reason === "string") {
|
|
1626
|
+
finishReason = choice.finish_reason;
|
|
1627
|
+
}
|
|
1628
|
+
if (chunk.usage) {
|
|
1629
|
+
const usage = normalizeUsage(chunk.usage);
|
|
1630
|
+
if (usage) yield usageEvent(requestId, usage);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
} finally {
|
|
1634
|
+
session.cleanup();
|
|
1635
|
+
}
|
|
1636
|
+
await options.hooks?.afterOpenRouterResponse?.({ streamed: true, text, finishReason, model: resolvedModel });
|
|
1637
|
+
yield doneEvent(requestId, {
|
|
1638
|
+
text,
|
|
1639
|
+
model: resolvedModel,
|
|
1640
|
+
...finishReason ? { finishReason } : {}
|
|
1641
|
+
});
|
|
1642
|
+
options.logger?.info?.("runtime.executeStreamingChat.completed", {
|
|
1643
|
+
requestId,
|
|
1644
|
+
streaming: true,
|
|
1645
|
+
entrypoint: "executeStreamingChat",
|
|
1646
|
+
finishReason,
|
|
1647
|
+
textLength: text.length
|
|
1648
|
+
});
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
const runtimeError = toRuntimeError(error);
|
|
1651
|
+
yield errorEvent(requestId, runtimeError);
|
|
1652
|
+
options.logger?.error?.("runtime.executeStreamingChat.failed", {
|
|
1653
|
+
requestId,
|
|
1654
|
+
streaming: true,
|
|
1655
|
+
code: runtimeError.code,
|
|
1656
|
+
message: runtimeError.message
|
|
1657
|
+
});
|
|
1658
|
+
throw error instanceof Error ? error : new Error(runtimeError.message);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function assertStreamingChatRequest(request, defaults) {
|
|
1662
|
+
if (request.apiMode === "responses") {
|
|
1663
|
+
throw new RuntimeConfigError(
|
|
1664
|
+
"STREAMING_CHAT_REQUIRES_CHAT_API",
|
|
1665
|
+
"executeStreamingChat() only supports Chat Completions. Use runtime.run() for Responses API requests."
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
if (request.serverTools?.applyPatch?.mode && request.serverTools.applyPatch.mode !== "disabled") {
|
|
1669
|
+
throw new RuntimeConfigError(
|
|
1670
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1671
|
+
"executeStreamingChat() does not support applyPatch. Use runtime.run() instead."
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
if (defaults.serverTools?.applyPatch?.mode && defaults.serverTools.applyPatch.mode !== "disabled") {
|
|
1675
|
+
const requestPatch = request.serverTools?.applyPatch;
|
|
1676
|
+
if (!requestPatch || requestPatch.mode !== "disabled") {
|
|
1677
|
+
throw new RuntimeConfigError(
|
|
1678
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1679
|
+
"executeStreamingChat() does not support applyPatch defaults. Use runtime.run() instead."
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function startEvent(requestId, model) {
|
|
1685
|
+
return {
|
|
1686
|
+
type: "stream.start",
|
|
1687
|
+
requestId,
|
|
1688
|
+
data: {
|
|
1689
|
+
model,
|
|
1690
|
+
apiMode: "chat",
|
|
1691
|
+
entrypoint: "executeStreamingChat"
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
function textDeltaEvent(requestId, text) {
|
|
1696
|
+
return {
|
|
1697
|
+
type: "stream.text.delta",
|
|
1698
|
+
requestId,
|
|
1699
|
+
data: { text }
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function reasoningDeltaEvent(requestId, text) {
|
|
1703
|
+
return {
|
|
1704
|
+
type: "stream.reasoning.delta",
|
|
1705
|
+
requestId,
|
|
1706
|
+
data: { text }
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
function toolCallDeltaEvent(requestId, data) {
|
|
1710
|
+
return {
|
|
1711
|
+
type: "stream.tool_call.delta",
|
|
1712
|
+
requestId,
|
|
1713
|
+
data
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
function usageEvent(requestId, usage) {
|
|
1717
|
+
return {
|
|
1718
|
+
type: "stream.usage",
|
|
1719
|
+
requestId,
|
|
1720
|
+
data: { usage }
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function warningEvent(requestId, warning) {
|
|
1724
|
+
return {
|
|
1725
|
+
type: "stream.warning",
|
|
1726
|
+
requestId,
|
|
1727
|
+
data: { warning }
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function errorEvent(requestId, error) {
|
|
1731
|
+
return {
|
|
1732
|
+
type: "stream.error",
|
|
1733
|
+
requestId,
|
|
1734
|
+
data: { error }
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function doneEvent(requestId, data) {
|
|
1738
|
+
return {
|
|
1739
|
+
type: "stream.done",
|
|
1740
|
+
requestId,
|
|
1741
|
+
data
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function toRuntimeError(error) {
|
|
1745
|
+
if (error instanceof RuntimeConfigError) {
|
|
1746
|
+
return {
|
|
1747
|
+
code: error.code,
|
|
1748
|
+
message: error.message,
|
|
1749
|
+
source: "validation",
|
|
1750
|
+
details: error.details
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
if (error instanceof OpenRouterHttpError) {
|
|
1754
|
+
return {
|
|
1755
|
+
code: error.code,
|
|
1756
|
+
message: error.message,
|
|
1757
|
+
source: "openrouter",
|
|
1758
|
+
retryable: error.retryable,
|
|
1759
|
+
details: {
|
|
1760
|
+
providerAttempts: error.providerAttempts,
|
|
1761
|
+
...error.body === void 0 ? {} : { response: error.body }
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
return {
|
|
1766
|
+
code: "RUNTIME_ERROR",
|
|
1767
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1768
|
+
source: "runtime"
|
|
1769
|
+
};
|
|
1334
1770
|
}
|
|
1335
1771
|
|
|
1336
1772
|
// src/runtime/create-runtime.ts
|
|
@@ -1339,7 +1775,14 @@ function createOpenRouterRuntime(options) {
|
|
|
1339
1775
|
const defaults = resolveDefaults(options);
|
|
1340
1776
|
const context = { options: resolvedOptions, defaults };
|
|
1341
1777
|
return {
|
|
1342
|
-
|
|
1778
|
+
compile(request, call) {
|
|
1779
|
+
return compileRuntimeRequest(request, defaults, resolvedOptions, {
|
|
1780
|
+
streaming: false,
|
|
1781
|
+
...call?.finalizeBody ? { finalizeBody: call.finalizeBody } : {},
|
|
1782
|
+
...call?.suppressNestedReasoningFallback ? { suppressNestedReasoningFallback: true } : {}
|
|
1783
|
+
});
|
|
1784
|
+
},
|
|
1785
|
+
async run(request, call) {
|
|
1343
1786
|
const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
|
|
1344
1787
|
try {
|
|
1345
1788
|
resolvedOptions.logger?.info?.("runtime.request.started", {
|
|
@@ -1348,7 +1791,11 @@ function createOpenRouterRuntime(options) {
|
|
|
1348
1791
|
entrypoint: "run"
|
|
1349
1792
|
});
|
|
1350
1793
|
await resolvedOptions.hooks?.beforeCompile?.(requestWithId);
|
|
1351
|
-
const compiled = compileRuntimeRequest(requestWithId, defaults, resolvedOptions, {
|
|
1794
|
+
const compiled = compileRuntimeRequest(requestWithId, defaults, resolvedOptions, {
|
|
1795
|
+
streaming: false,
|
|
1796
|
+
...call?.finalizeBody ? { finalizeBody: call.finalizeBody } : {},
|
|
1797
|
+
...call?.suppressNestedReasoningFallback ? { suppressNestedReasoningFallback: true } : {}
|
|
1798
|
+
});
|
|
1352
1799
|
await resolvedOptions.hooks?.afterCompile?.(compiled);
|
|
1353
1800
|
resolvedOptions.logger?.debug?.("runtime.request.compiled", {
|
|
1354
1801
|
requestId: requestWithId.id,
|
|
@@ -1370,14 +1817,9 @@ function createOpenRouterRuntime(options) {
|
|
|
1370
1817
|
return errorResponse(requestWithId, error);
|
|
1371
1818
|
}
|
|
1372
1819
|
},
|
|
1373
|
-
executeStreamingChat(request) {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
streaming: true,
|
|
1377
|
-
entrypoint: "executeStreamingChat",
|
|
1378
|
-
implemented: false
|
|
1379
|
-
});
|
|
1380
|
-
return executeStreamingChatNotImplemented();
|
|
1820
|
+
executeStreamingChat(request, call) {
|
|
1821
|
+
const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
|
|
1822
|
+
return executeStreamingChat(requestWithId, context, call);
|
|
1381
1823
|
}
|
|
1382
1824
|
};
|
|
1383
1825
|
function errorResponse(request, error) {
|
|
@@ -1466,12 +1908,15 @@ function operatorHintFor(code) {
|
|
|
1466
1908
|
}
|
|
1467
1909
|
}
|
|
1468
1910
|
export {
|
|
1911
|
+
OPENROUTER_METADATA_LIMITS,
|
|
1469
1912
|
OpenRouterHttpError,
|
|
1470
1913
|
RuntimeConfigError,
|
|
1471
1914
|
buildOpenRouterServerTools,
|
|
1472
1915
|
compileRuntimeRequest,
|
|
1473
1916
|
createConsoleLogger,
|
|
1474
1917
|
createOpenRouterRuntime,
|
|
1475
|
-
|
|
1918
|
+
decodeProviderMetadata,
|
|
1919
|
+
isRuntimeLoggingEnabled,
|
|
1920
|
+
toProviderMetadata
|
|
1476
1921
|
};
|
|
1477
1922
|
//# sourceMappingURL=index.js.map
|