@x12i/openrouter-runtime 1.0.5 → 1.1.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/README.md +54 -25
- package/dist/index.cjs +409 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -3
- package/dist/index.d.ts +62 -3
- package/dist/index.js +406 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,33 @@ const response = await runtime.run({
|
|
|
32
32
|
console.log(response.text);
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Console logging
|
|
36
|
+
|
|
37
|
+
Turn on runtime console logs with:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
OPENROUTER_RUNTIME_LOGS=true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
When that env var is `true` / `1` / `yes` / `on`, and you do not pass a custom `logger`, the runtime logs to the console:
|
|
44
|
+
|
|
45
|
+
- `runtime.request.started` — includes `streaming: false` and `entrypoint: "run"`
|
|
46
|
+
- `runtime.request.compiled` — includes `bodyStream: false` and any streaming-related warning codes
|
|
47
|
+
- `runtime.response.normalized`
|
|
48
|
+
- `runtime.executeStreamingChat.called` — if someone hits the reserved streaming method
|
|
49
|
+
- provider retry / function-tool events
|
|
50
|
+
|
|
51
|
+
This package does **not** auto-load `.env`. Put the vars in your environment (shell export, process manager, or host `dotenv`) before calling `createOpenRouterRuntime()`. See `.env.example`.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
OPEN_ROUTER_KEY=sk-or-...
|
|
57
|
+
OPENROUTER_RUNTIME_LOGS=true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can still pass an explicit `logger` to override the console logger.
|
|
61
|
+
|
|
35
62
|
## Server Tools
|
|
36
63
|
|
|
37
64
|
Enable OpenRouter server tools through `serverTools`:
|
|
@@ -73,7 +100,9 @@ Function calls are executed locally and looped back to OpenRouter until a final
|
|
|
73
100
|
|
|
74
101
|
## Streaming
|
|
75
102
|
|
|
76
|
-
|
|
103
|
+
Streaming is a **separate API**. It is never an argument on `run()`, never the default, and never available through the removed `stream()` name.
|
|
104
|
+
|
|
105
|
+
### `run()` — non-streaming only
|
|
77
106
|
|
|
78
107
|
```ts
|
|
79
108
|
const response = await runtime.run({
|
|
@@ -82,35 +111,35 @@ const response = await runtime.run({
|
|
|
82
111
|
});
|
|
83
112
|
```
|
|
84
113
|
|
|
85
|
-
|
|
114
|
+
- Always sends `stream: false`
|
|
115
|
+
- Returns a completed `RuntimeResponse`
|
|
116
|
+
- Use for tools, research, extraction, patches, automation
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
Any truthy `rawOpenRouterOverrides.stream` is overwritten (`STREAMING_OVERRIDE_IGNORED_FOR_RUN`). Nested `advisor.stream` is forced off (`ADVISOR_STREAMING_IGNORED_FOR_RUN`).
|
|
88
119
|
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
Most runtime calls are background operations: graph nodes, tool loops, extraction, classification, research, patch generation, and structured output. For those workloads, streaming adds connection complexity without improving the final result.
|
|
92
|
-
|
|
93
|
-
When streaming support is added, it will be available through `runtime.stream(request)`, not through `runtime.run(request)`.
|
|
120
|
+
### `executeStreamingChat()` — streaming only
|
|
94
121
|
|
|
95
122
|
```ts
|
|
96
|
-
|
|
97
|
-
for await (const event of runtime.stream(request)) {
|
|
98
|
-
// send token deltas to browser/client
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Today, `stream()` is reserved and throws `STREAMING_NOT_IMPLEMENTED`. The package should be treated as a non-streaming runtime.
|
|
103
|
-
|
|
104
|
-
Do not pass streaming through raw overrides:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
await runtime.run({
|
|
123
|
+
for await (const event of runtime.executeStreamingChat({
|
|
108
124
|
model: "openai/gpt-5.2",
|
|
109
|
-
prompt: "
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
prompt: "Say hello"
|
|
126
|
+
})) {
|
|
127
|
+
if (event.type === "stream.text.delta") {
|
|
128
|
+
process.stdout.write(event.data.text);
|
|
112
129
|
}
|
|
113
|
-
|
|
130
|
+
if (event.type === "stream.done") {
|
|
131
|
+
console.log("\nfinal:", event.data.text);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
114
134
|
```
|
|
115
135
|
|
|
116
|
-
|
|
136
|
+
- Always sends `stream: true` (Chat Completions SSE)
|
|
137
|
+
- Yields typed events: `stream.start`, `stream.text.delta`, `stream.tool_call.delta`, `stream.usage`, `stream.warning`, `stream.error`, `stream.done`
|
|
138
|
+
- Chat Completions only — Responses / `applyPatch` must use `run()`
|
|
139
|
+
- Local function-tool loops are not run here; use `run()` for tool iteration
|
|
140
|
+
|
|
141
|
+
| Method | Streaming? | Notes |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `runtime.run(request)` | No | Default execution path |
|
|
144
|
+
| `runtime.executeStreamingChat(request)` | Yes | Explicit streaming API |
|
|
145
|
+
| `runtime.stream(request)` | — | **Removed** — breaks by design |
|
package/dist/index.cjs
CHANGED
|
@@ -24,18 +24,45 @@ __export(index_exports, {
|
|
|
24
24
|
RuntimeConfigError: () => RuntimeConfigError,
|
|
25
25
|
buildOpenRouterServerTools: () => buildOpenRouterServerTools,
|
|
26
26
|
compileRuntimeRequest: () => compileRuntimeRequest,
|
|
27
|
-
|
|
27
|
+
createConsoleLogger: () => createConsoleLogger,
|
|
28
|
+
createOpenRouterRuntime: () => createOpenRouterRuntime,
|
|
29
|
+
isRuntimeLoggingEnabled: () => isRuntimeLoggingEnabled
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(index_exports);
|
|
30
32
|
|
|
33
|
+
// src/utils/console-logger.ts
|
|
34
|
+
var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
35
|
+
function isRuntimeLoggingEnabled(env = typeof process === "undefined" ? void 0 : process.env) {
|
|
36
|
+
const value = env?.OPENROUTER_RUNTIME_LOGS?.trim().toLowerCase();
|
|
37
|
+
return value !== void 0 && TRUTHY.has(value);
|
|
38
|
+
}
|
|
39
|
+
function createConsoleLogger() {
|
|
40
|
+
return {
|
|
41
|
+
debug(event, data) {
|
|
42
|
+
console.debug(`[openrouter-runtime] ${event}`, data ?? "");
|
|
43
|
+
},
|
|
44
|
+
info(event, data) {
|
|
45
|
+
console.info(`[openrouter-runtime] ${event}`, data ?? "");
|
|
46
|
+
},
|
|
47
|
+
warn(event, data) {
|
|
48
|
+
console.warn(`[openrouter-runtime] ${event}`, data ?? "");
|
|
49
|
+
},
|
|
50
|
+
error(event, data) {
|
|
51
|
+
console.error(`[openrouter-runtime] ${event}`, data ?? "");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// src/config/defaults.ts
|
|
32
57
|
function resolveRuntimeOptions(options) {
|
|
58
|
+
const logger = options.logger ?? (isRuntimeLoggingEnabled() ? createConsoleLogger() : void 0);
|
|
33
59
|
return {
|
|
34
60
|
...options,
|
|
35
61
|
apiKey: options.apiKey ?? readOpenRouterApiKeyFromEnv() ?? "",
|
|
36
62
|
baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1",
|
|
37
63
|
defaultHeaders: options.defaultHeaders ?? {},
|
|
38
|
-
fetch: options.fetch ?? globalThis.fetch
|
|
64
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
65
|
+
...logger ? { logger } : {}
|
|
39
66
|
};
|
|
40
67
|
}
|
|
41
68
|
function resolveDefaults(options) {
|
|
@@ -923,14 +950,42 @@ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, cont
|
|
|
923
950
|
};
|
|
924
951
|
}
|
|
925
952
|
function enforceStreamingPolicyForRun(body, warnings, context) {
|
|
926
|
-
if (context.streaming)
|
|
927
|
-
|
|
953
|
+
if (context.streaming) {
|
|
954
|
+
body.stream = true;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (body.stream !== void 0 && body.stream !== false) {
|
|
928
958
|
warnings.push({
|
|
929
959
|
code: "STREAMING_OVERRIDE_IGNORED_FOR_RUN",
|
|
930
|
-
message: "runtime.run() is non-streaming. Use runtime.
|
|
960
|
+
message: "runtime.run() is non-streaming. Use runtime.executeStreamingChat() for streaming.",
|
|
961
|
+
details: { ignoredStreamValue: body.stream }
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
body.stream = false;
|
|
965
|
+
stripAdvisorStreaming(body, warnings);
|
|
966
|
+
}
|
|
967
|
+
function stripAdvisorStreaming(body, warnings) {
|
|
968
|
+
const tools = body.tools;
|
|
969
|
+
if (!Array.isArray(tools)) return;
|
|
970
|
+
let stripped = 0;
|
|
971
|
+
for (const tool of tools) {
|
|
972
|
+
if (!tool || typeof tool !== "object") continue;
|
|
973
|
+
const record = tool;
|
|
974
|
+
if (record.type !== "openrouter:advisor") continue;
|
|
975
|
+
const parameters = record.parameters;
|
|
976
|
+
if (!parameters || typeof parameters !== "object") continue;
|
|
977
|
+
const params = parameters;
|
|
978
|
+
if (params.stream === void 0 || params.stream === false) continue;
|
|
979
|
+
params.stream = false;
|
|
980
|
+
stripped += 1;
|
|
981
|
+
}
|
|
982
|
+
if (stripped > 0) {
|
|
983
|
+
warnings.push({
|
|
984
|
+
code: "ADVISOR_STREAMING_IGNORED_FOR_RUN",
|
|
985
|
+
message: "runtime.run() disables advisor.stream. Nested advisor streaming is not supported on the non-streaming entrypoint.",
|
|
986
|
+
details: { advisorsStripped: stripped }
|
|
931
987
|
});
|
|
932
988
|
}
|
|
933
|
-
delete body.stream;
|
|
934
989
|
}
|
|
935
990
|
function resolveApiMode(request, defaultMode = "auto", serverTools) {
|
|
936
991
|
const mode = request.apiMode ?? defaultMode;
|
|
@@ -964,7 +1019,7 @@ function buildChatBody(request, baseBody) {
|
|
|
964
1019
|
return clean({
|
|
965
1020
|
...baseBody,
|
|
966
1021
|
messages,
|
|
967
|
-
|
|
1022
|
+
max_completion_tokens: request.maxTokens,
|
|
968
1023
|
response_format: request.responseFormat
|
|
969
1024
|
});
|
|
970
1025
|
}
|
|
@@ -1305,12 +1360,333 @@ function optional6(key, value) {
|
|
|
1305
1360
|
return value === void 0 ? {} : { [key]: value };
|
|
1306
1361
|
}
|
|
1307
1362
|
|
|
1363
|
+
// src/openrouter/stream-client.ts
|
|
1364
|
+
async function openOpenRouterStream(compiled, options, timeoutMs) {
|
|
1365
|
+
const controller = new AbortController();
|
|
1366
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1367
|
+
const cleanup = () => clearTimeout(timeout);
|
|
1368
|
+
try {
|
|
1369
|
+
const response = await options.fetch(compiled.url, {
|
|
1370
|
+
method: "POST",
|
|
1371
|
+
headers: {
|
|
1372
|
+
...compiled.headers,
|
|
1373
|
+
Accept: "text/event-stream"
|
|
1374
|
+
},
|
|
1375
|
+
body: JSON.stringify(compiled.body),
|
|
1376
|
+
signal: controller.signal
|
|
1377
|
+
});
|
|
1378
|
+
if (!response.ok) {
|
|
1379
|
+
const text = await response.text();
|
|
1380
|
+
cleanup();
|
|
1381
|
+
const body = text ? parseBody2(text) : void 0;
|
|
1382
|
+
const message = extractMessage2(body) ?? `OpenRouter stream failed with status ${response.status}.`;
|
|
1383
|
+
throw new OpenRouterHttpError(
|
|
1384
|
+
response.status,
|
|
1385
|
+
classifyProviderFailure2(response.status, message, body),
|
|
1386
|
+
message,
|
|
1387
|
+
body,
|
|
1388
|
+
isRetryableStatus2(response.status)
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
return { response, cleanup };
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
cleanup();
|
|
1394
|
+
if (error instanceof OpenRouterHttpError) throw error;
|
|
1395
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1396
|
+
throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter stream timed out.", void 0, true);
|
|
1397
|
+
}
|
|
1398
|
+
throw new OpenRouterHttpError(
|
|
1399
|
+
503,
|
|
1400
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1401
|
+
error instanceof Error ? error.message : "OpenRouter stream failed before receiving a response.",
|
|
1402
|
+
void 0,
|
|
1403
|
+
true
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
async function* parseSseStream(response) {
|
|
1408
|
+
const body = response.body;
|
|
1409
|
+
if (!body) {
|
|
1410
|
+
throw new OpenRouterHttpError(
|
|
1411
|
+
503,
|
|
1412
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1413
|
+
"OpenRouter stream response had no body.",
|
|
1414
|
+
void 0,
|
|
1415
|
+
true
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
const reader = body.getReader();
|
|
1419
|
+
const decoder = new TextDecoder();
|
|
1420
|
+
let buffer = "";
|
|
1421
|
+
while (true) {
|
|
1422
|
+
const { done, value } = await reader.read();
|
|
1423
|
+
if (done) break;
|
|
1424
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1425
|
+
const parts = buffer.split(/\r?\n/);
|
|
1426
|
+
buffer = parts.pop() ?? "";
|
|
1427
|
+
for (const part of parts) {
|
|
1428
|
+
const line = part.trim();
|
|
1429
|
+
if (!line || line.startsWith(":")) continue;
|
|
1430
|
+
if (!line.startsWith("data:")) continue;
|
|
1431
|
+
const data = line.slice(5).trim();
|
|
1432
|
+
if (!data) continue;
|
|
1433
|
+
if (data === "[DONE]") return;
|
|
1434
|
+
try {
|
|
1435
|
+
yield JSON.parse(data);
|
|
1436
|
+
} catch {
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
const trailing = buffer.trim();
|
|
1441
|
+
if (trailing.startsWith("data:")) {
|
|
1442
|
+
const data = trailing.slice(5).trim();
|
|
1443
|
+
if (data && data !== "[DONE]") {
|
|
1444
|
+
try {
|
|
1445
|
+
yield JSON.parse(data);
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
function parseBody2(text) {
|
|
1452
|
+
try {
|
|
1453
|
+
return JSON.parse(text);
|
|
1454
|
+
} catch {
|
|
1455
|
+
return text;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
function extractMessage2(body) {
|
|
1459
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1460
|
+
const record = body;
|
|
1461
|
+
const error = record.error;
|
|
1462
|
+
if (typeof error === "object" && error !== null && typeof error.message === "string") {
|
|
1463
|
+
return error.message;
|
|
1464
|
+
}
|
|
1465
|
+
return typeof record.message === "string" ? record.message : void 0;
|
|
1466
|
+
}
|
|
1467
|
+
function classifyProviderFailure2(status, message, body) {
|
|
1468
|
+
const haystack = `${message} ${JSON.stringify(body ?? "")}`.toLowerCase();
|
|
1469
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1470
|
+
if (status === 402 || /\b(quota|credit|credits|billing|balance|insufficient funds|insufficient credit)\b/.test(haystack)) {
|
|
1471
|
+
return "PROVIDER_QUOTA_EXCEEDED";
|
|
1472
|
+
}
|
|
1473
|
+
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)) {
|
|
1474
|
+
return "PROVIDER_MODEL_NOT_FOUND";
|
|
1475
|
+
}
|
|
1476
|
+
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
1477
|
+
if (status === 408 || status >= 500) return "PROVIDER_GATEWAY_UNREACHABLE";
|
|
1478
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
1479
|
+
}
|
|
1480
|
+
function isRetryableStatus2(status) {
|
|
1481
|
+
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1308
1484
|
// src/runtime/streaming.ts
|
|
1309
|
-
async function*
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1485
|
+
async function* executeStreamingChat(request, context) {
|
|
1486
|
+
const requestId = request.id ?? "unknown";
|
|
1487
|
+
const options = context.options;
|
|
1488
|
+
const defaults = context.defaults;
|
|
1489
|
+
options.logger?.info?.("runtime.executeStreamingChat.started", {
|
|
1490
|
+
requestId,
|
|
1491
|
+
streaming: true,
|
|
1492
|
+
entrypoint: "executeStreamingChat"
|
|
1493
|
+
});
|
|
1494
|
+
try {
|
|
1495
|
+
assertStreamingChatRequest(request, defaults);
|
|
1496
|
+
const streamingRequest = {
|
|
1497
|
+
...request,
|
|
1498
|
+
apiMode: "chat"
|
|
1499
|
+
};
|
|
1500
|
+
const compiled = compileRuntimeRequest(streamingRequest, defaults, options, { streaming: true });
|
|
1501
|
+
if (compiled.body.stream !== true) {
|
|
1502
|
+
throw new RuntimeConfigError(
|
|
1503
|
+
"STREAMING_CHAT_MISCONFIGURED",
|
|
1504
|
+
"executeStreamingChat() requires stream=true on the compiled OpenRouter body."
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
for (const warning of compiled.warnings) {
|
|
1508
|
+
yield warningEvent(requestId, warning);
|
|
1509
|
+
}
|
|
1510
|
+
const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1511
|
+
yield startEvent(requestId, model);
|
|
1512
|
+
await options.hooks?.beforeOpenRouterRequest?.(compiled);
|
|
1513
|
+
const timeoutMs = request.execution?.timeoutMs ?? defaults.timeoutMs;
|
|
1514
|
+
const session = await openOpenRouterStream(compiled, options, timeoutMs);
|
|
1515
|
+
let text = "";
|
|
1516
|
+
let finishReason;
|
|
1517
|
+
let resolvedModel = model;
|
|
1518
|
+
try {
|
|
1519
|
+
for await (const chunk of parseSseStream(session.response)) {
|
|
1520
|
+
if (!isRecord(chunk)) continue;
|
|
1521
|
+
if (chunk.error) {
|
|
1522
|
+
const message = isRecord(chunk.error) && typeof chunk.error.message === "string" ? chunk.error.message : "OpenRouter stream returned an error chunk.";
|
|
1523
|
+
const error = {
|
|
1524
|
+
code: "PROVIDER_REQUEST_FAILED",
|
|
1525
|
+
message,
|
|
1526
|
+
source: "openrouter",
|
|
1527
|
+
details: chunk.error
|
|
1528
|
+
};
|
|
1529
|
+
yield errorEvent(requestId, error);
|
|
1530
|
+
throw new OpenRouterHttpError(500, "PROVIDER_REQUEST_FAILED", message, chunk, false);
|
|
1531
|
+
}
|
|
1532
|
+
if (typeof chunk.model === "string") resolvedModel = chunk.model;
|
|
1533
|
+
const choice = Array.isArray(chunk.choices) && isRecord(chunk.choices[0]) ? chunk.choices[0] : void 0;
|
|
1534
|
+
const delta = choice && isRecord(choice.delta) ? choice.delta : void 0;
|
|
1535
|
+
if (delta && typeof delta.content === "string" && delta.content.length > 0) {
|
|
1536
|
+
text += delta.content;
|
|
1537
|
+
yield textDeltaEvent(requestId, delta.content);
|
|
1538
|
+
}
|
|
1539
|
+
if (delta && Array.isArray(delta.tool_calls)) {
|
|
1540
|
+
for (const toolCall of delta.tool_calls) {
|
|
1541
|
+
if (!isRecord(toolCall)) continue;
|
|
1542
|
+
const index = typeof toolCall.index === "number" ? toolCall.index : 0;
|
|
1543
|
+
const fn = isRecord(toolCall.function) ? toolCall.function : {};
|
|
1544
|
+
yield toolCallDeltaEvent(requestId, {
|
|
1545
|
+
index,
|
|
1546
|
+
...typeof toolCall.id === "string" ? { id: toolCall.id } : {},
|
|
1547
|
+
...typeof fn.name === "string" ? { name: fn.name } : {},
|
|
1548
|
+
...typeof fn.arguments === "string" ? { argumentsDelta: fn.arguments } : {}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
if (choice && typeof choice.finish_reason === "string") {
|
|
1553
|
+
finishReason = choice.finish_reason;
|
|
1554
|
+
}
|
|
1555
|
+
if (chunk.usage) {
|
|
1556
|
+
const usage = normalizeUsage(chunk.usage);
|
|
1557
|
+
if (usage) yield usageEvent(requestId, usage);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} finally {
|
|
1561
|
+
session.cleanup();
|
|
1562
|
+
}
|
|
1563
|
+
await options.hooks?.afterOpenRouterResponse?.({ streamed: true, text, finishReason, model: resolvedModel });
|
|
1564
|
+
yield doneEvent(requestId, {
|
|
1565
|
+
text,
|
|
1566
|
+
model: resolvedModel,
|
|
1567
|
+
...finishReason ? { finishReason } : {}
|
|
1568
|
+
});
|
|
1569
|
+
options.logger?.info?.("runtime.executeStreamingChat.completed", {
|
|
1570
|
+
requestId,
|
|
1571
|
+
streaming: true,
|
|
1572
|
+
entrypoint: "executeStreamingChat",
|
|
1573
|
+
finishReason,
|
|
1574
|
+
textLength: text.length
|
|
1575
|
+
});
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
const runtimeError = toRuntimeError(error);
|
|
1578
|
+
yield errorEvent(requestId, runtimeError);
|
|
1579
|
+
options.logger?.error?.("runtime.executeStreamingChat.failed", {
|
|
1580
|
+
requestId,
|
|
1581
|
+
streaming: true,
|
|
1582
|
+
code: runtimeError.code,
|
|
1583
|
+
message: runtimeError.message
|
|
1584
|
+
});
|
|
1585
|
+
throw error instanceof Error ? error : new Error(runtimeError.message);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
function assertStreamingChatRequest(request, defaults) {
|
|
1589
|
+
if (request.apiMode === "responses") {
|
|
1590
|
+
throw new RuntimeConfigError(
|
|
1591
|
+
"STREAMING_CHAT_REQUIRES_CHAT_API",
|
|
1592
|
+
"executeStreamingChat() only supports Chat Completions. Use runtime.run() for Responses API requests."
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
if (request.serverTools?.applyPatch?.mode && request.serverTools.applyPatch.mode !== "disabled") {
|
|
1596
|
+
throw new RuntimeConfigError(
|
|
1597
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1598
|
+
"executeStreamingChat() does not support applyPatch. Use runtime.run() instead."
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
if (defaults.serverTools?.applyPatch?.mode && defaults.serverTools.applyPatch.mode !== "disabled") {
|
|
1602
|
+
const requestPatch = request.serverTools?.applyPatch;
|
|
1603
|
+
if (!requestPatch || requestPatch.mode !== "disabled") {
|
|
1604
|
+
throw new RuntimeConfigError(
|
|
1605
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1606
|
+
"executeStreamingChat() does not support applyPatch defaults. Use runtime.run() instead."
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function startEvent(requestId, model) {
|
|
1612
|
+
return {
|
|
1613
|
+
type: "stream.start",
|
|
1614
|
+
requestId,
|
|
1615
|
+
data: {
|
|
1616
|
+
model,
|
|
1617
|
+
apiMode: "chat",
|
|
1618
|
+
entrypoint: "executeStreamingChat"
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function textDeltaEvent(requestId, text) {
|
|
1623
|
+
return {
|
|
1624
|
+
type: "stream.text.delta",
|
|
1625
|
+
requestId,
|
|
1626
|
+
data: { text }
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function toolCallDeltaEvent(requestId, data) {
|
|
1630
|
+
return {
|
|
1631
|
+
type: "stream.tool_call.delta",
|
|
1632
|
+
requestId,
|
|
1633
|
+
data
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function usageEvent(requestId, usage) {
|
|
1637
|
+
return {
|
|
1638
|
+
type: "stream.usage",
|
|
1639
|
+
requestId,
|
|
1640
|
+
data: { usage }
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function warningEvent(requestId, warning) {
|
|
1644
|
+
return {
|
|
1645
|
+
type: "stream.warning",
|
|
1646
|
+
requestId,
|
|
1647
|
+
data: { warning }
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
function errorEvent(requestId, error) {
|
|
1651
|
+
return {
|
|
1652
|
+
type: "stream.error",
|
|
1653
|
+
requestId,
|
|
1654
|
+
data: { error }
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
function doneEvent(requestId, data) {
|
|
1658
|
+
return {
|
|
1659
|
+
type: "stream.done",
|
|
1660
|
+
requestId,
|
|
1661
|
+
data
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function toRuntimeError(error) {
|
|
1665
|
+
if (error instanceof RuntimeConfigError) {
|
|
1666
|
+
return {
|
|
1667
|
+
code: error.code,
|
|
1668
|
+
message: error.message,
|
|
1669
|
+
source: "validation",
|
|
1670
|
+
details: error.details
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
if (error instanceof OpenRouterHttpError) {
|
|
1674
|
+
return {
|
|
1675
|
+
code: error.code,
|
|
1676
|
+
message: error.message,
|
|
1677
|
+
source: "openrouter",
|
|
1678
|
+
retryable: error.retryable,
|
|
1679
|
+
details: {
|
|
1680
|
+
providerAttempts: error.providerAttempts,
|
|
1681
|
+
...error.body === void 0 ? {} : { response: error.body }
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
return {
|
|
1686
|
+
code: "RUNTIME_ERROR",
|
|
1687
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1688
|
+
source: "runtime"
|
|
1689
|
+
};
|
|
1314
1690
|
}
|
|
1315
1691
|
|
|
1316
1692
|
// src/runtime/create-runtime.ts
|
|
@@ -1322,24 +1698,38 @@ function createOpenRouterRuntime(options) {
|
|
|
1322
1698
|
async run(request) {
|
|
1323
1699
|
const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
|
|
1324
1700
|
try {
|
|
1325
|
-
resolvedOptions.logger?.info?.("runtime.request.started", {
|
|
1701
|
+
resolvedOptions.logger?.info?.("runtime.request.started", {
|
|
1702
|
+
requestId: requestWithId.id,
|
|
1703
|
+
streaming: false,
|
|
1704
|
+
entrypoint: "run"
|
|
1705
|
+
});
|
|
1326
1706
|
await resolvedOptions.hooks?.beforeCompile?.(requestWithId);
|
|
1327
1707
|
const compiled = compileRuntimeRequest(requestWithId, defaults, resolvedOptions, { streaming: false });
|
|
1328
1708
|
await resolvedOptions.hooks?.afterCompile?.(compiled);
|
|
1329
1709
|
resolvedOptions.logger?.debug?.("runtime.request.compiled", {
|
|
1330
1710
|
requestId: requestWithId.id,
|
|
1331
|
-
apiMode: compiled.apiMode
|
|
1711
|
+
apiMode: compiled.apiMode,
|
|
1712
|
+
streaming: false,
|
|
1713
|
+
bodyStream: compiled.body.stream,
|
|
1714
|
+
streamingWarnings: compiled.warnings.filter((warning) => warning.code.includes("STREAMING") || warning.code.includes("ADVISOR_STREAMING")).map((warning) => warning.code)
|
|
1332
1715
|
});
|
|
1333
1716
|
await resolvedOptions.hooks?.beforeOpenRouterRequest?.(compiled);
|
|
1334
1717
|
const response = compiled.apiMode === "chat" ? await runChat(requestWithId, compiled, context) : await runResponses(requestWithId, compiled, context);
|
|
1335
1718
|
await resolvedOptions.hooks?.afterOpenRouterResponse?.(response.raw.response);
|
|
1336
|
-
resolvedOptions.logger?.info?.("runtime.response.normalized", {
|
|
1719
|
+
resolvedOptions.logger?.info?.("runtime.response.normalized", {
|
|
1720
|
+
requestId: requestWithId.id,
|
|
1721
|
+
status: response.status,
|
|
1722
|
+
streaming: false
|
|
1723
|
+
});
|
|
1337
1724
|
return response;
|
|
1338
1725
|
} catch (error) {
|
|
1339
1726
|
return errorResponse(requestWithId, error);
|
|
1340
1727
|
}
|
|
1341
1728
|
},
|
|
1342
|
-
|
|
1729
|
+
executeStreamingChat(request) {
|
|
1730
|
+
const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
|
|
1731
|
+
return executeStreamingChat(requestWithId, context);
|
|
1732
|
+
}
|
|
1343
1733
|
};
|
|
1344
1734
|
function errorResponse(request, error) {
|
|
1345
1735
|
const runtimeError = normalizeError(error);
|
|
@@ -1432,6 +1822,8 @@ function operatorHintFor(code) {
|
|
|
1432
1822
|
RuntimeConfigError,
|
|
1433
1823
|
buildOpenRouterServerTools,
|
|
1434
1824
|
compileRuntimeRequest,
|
|
1435
|
-
|
|
1825
|
+
createConsoleLogger,
|
|
1826
|
+
createOpenRouterRuntime,
|
|
1827
|
+
isRuntimeLoggingEnabled
|
|
1436
1828
|
});
|
|
1437
1829
|
//# sourceMappingURL=index.cjs.map
|