@x12i/openrouter-runtime 1.0.6 → 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 +24 -19
- package/dist/index.cjs +333 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -3
- package/dist/index.d.ts +55 -3
- package/dist/index.js +333 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -4,7 +4,8 @@ type ToolMode = "disabled" | "allowed" | "required";
|
|
|
4
4
|
interface OpenRouterRuntime {
|
|
5
5
|
run(request: RuntimeRequest): Promise<RuntimeResponse>;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Explicit streaming chat API. Separate from `run()` by design — never an argument, never the default.
|
|
8
|
+
* Always sends OpenRouter Chat Completions with `stream: true` and yields typed SSE events.
|
|
8
9
|
* The old `stream()` name was removed so mistaken/old clients break loudly.
|
|
9
10
|
*/
|
|
10
11
|
executeStreamingChat(request: RuntimeRequest): AsyncIterable<RuntimeStreamEvent>;
|
|
@@ -364,9 +365,60 @@ interface RuntimePolicyViolation {
|
|
|
364
365
|
details?: unknown;
|
|
365
366
|
}
|
|
366
367
|
interface RuntimeStreamEvent {
|
|
367
|
-
type:
|
|
368
|
+
type: RuntimeStreamEventType;
|
|
369
|
+
requestId: string;
|
|
368
370
|
data?: unknown;
|
|
369
371
|
}
|
|
372
|
+
type RuntimeStreamEventType = "stream.start" | "stream.text.delta" | "stream.tool_call.delta" | "stream.usage" | "stream.warning" | "stream.error" | "stream.done";
|
|
373
|
+
interface RuntimeStreamStartEvent extends RuntimeStreamEvent {
|
|
374
|
+
type: "stream.start";
|
|
375
|
+
data: {
|
|
376
|
+
model: string;
|
|
377
|
+
apiMode: "chat";
|
|
378
|
+
entrypoint: "executeStreamingChat";
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
interface RuntimeStreamTextDeltaEvent extends RuntimeStreamEvent {
|
|
382
|
+
type: "stream.text.delta";
|
|
383
|
+
data: {
|
|
384
|
+
text: string;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
interface RuntimeStreamToolCallDeltaEvent extends RuntimeStreamEvent {
|
|
388
|
+
type: "stream.tool_call.delta";
|
|
389
|
+
data: {
|
|
390
|
+
index: number;
|
|
391
|
+
id?: string;
|
|
392
|
+
name?: string;
|
|
393
|
+
argumentsDelta?: string;
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
interface RuntimeStreamUsageEvent extends RuntimeStreamEvent {
|
|
397
|
+
type: "stream.usage";
|
|
398
|
+
data: {
|
|
399
|
+
usage: RuntimeUsage;
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
interface RuntimeStreamWarningEvent extends RuntimeStreamEvent {
|
|
403
|
+
type: "stream.warning";
|
|
404
|
+
data: {
|
|
405
|
+
warning: RuntimeWarning;
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
interface RuntimeStreamErrorEvent extends RuntimeStreamEvent {
|
|
409
|
+
type: "stream.error";
|
|
410
|
+
data: {
|
|
411
|
+
error: RuntimeError;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
interface RuntimeStreamDoneEvent extends RuntimeStreamEvent {
|
|
415
|
+
type: "stream.done";
|
|
416
|
+
data: {
|
|
417
|
+
text: string;
|
|
418
|
+
finishReason?: string;
|
|
419
|
+
model: string;
|
|
420
|
+
};
|
|
421
|
+
}
|
|
370
422
|
type RuntimePatchApplier = (patch: RuntimePatchProposal) => Promise<unknown> | unknown;
|
|
371
423
|
|
|
372
424
|
declare function createOpenRouterRuntime(options: OpenRouterRuntimeOptions): OpenRouterRuntime;
|
|
@@ -414,4 +466,4 @@ declare function buildOpenRouterServerTools(policy?: RuntimeServerToolsPolicy):
|
|
|
414
466
|
declare function isRuntimeLoggingEnabled(env?: Record<string, string | undefined> | undefined): boolean;
|
|
415
467
|
declare function createConsoleLogger(): RuntimeLogger;
|
|
416
468
|
|
|
417
|
-
export { type AdvisorPolicy, type ApplyPatchPolicy, type CompiledOpenRouterRequest, type DatetimePolicy, type FusionPolicy, type ImageGenerationPolicy, OpenRouterHttpError, type OpenRouterRuntime, type OpenRouterRuntimeOptions, type RuntimeCitation, RuntimeConfigError, type RuntimeDefaults, type RuntimeError, type RuntimeFunctionToolDefinition, type RuntimeGeneratedImage, type RuntimeLogger, type RuntimeMessage, type RuntimePatchProposal, type RuntimePolicyViolation, type RuntimeRequest, type RuntimeResponse, type RuntimeServerToolsPolicy, type RuntimeStreamEvent, type RuntimeToolExecutor, type RuntimeToolUsage, type RuntimeUsage, type RuntimeWarning, type ServerToolKey, type ServerToolUsage, type SubagentPolicy, type WebFetchPolicy, type WebSearchPolicy, buildOpenRouterServerTools, compileRuntimeRequest, createConsoleLogger, createOpenRouterRuntime, isRuntimeLoggingEnabled };
|
|
469
|
+
export { type AdvisorPolicy, type ApplyPatchPolicy, type CompiledOpenRouterRequest, type DatetimePolicy, type FusionPolicy, type ImageGenerationPolicy, OpenRouterHttpError, type OpenRouterRuntime, type OpenRouterRuntimeOptions, type RuntimeCitation, RuntimeConfigError, type RuntimeDefaults, type RuntimeError, type RuntimeFunctionToolDefinition, type RuntimeGeneratedImage, type RuntimeLogger, type RuntimeMessage, type RuntimePatchProposal, type RuntimePolicyViolation, type RuntimeRequest, type RuntimeResponse, type RuntimeServerToolsPolicy, type RuntimeStreamDoneEvent, type RuntimeStreamErrorEvent, type RuntimeStreamEvent, type RuntimeStreamEventType, type RuntimeStreamStartEvent, type RuntimeStreamTextDeltaEvent, type RuntimeStreamToolCallDeltaEvent, type RuntimeStreamUsageEvent, type RuntimeStreamWarningEvent, type RuntimeToolExecutor, type RuntimeToolUsage, type RuntimeUsage, type RuntimeWarning, type ServerToolKey, type ServerToolUsage, type SubagentPolicy, type WebFetchPolicy, type WebSearchPolicy, buildOpenRouterServerTools, compileRuntimeRequest, createConsoleLogger, createOpenRouterRuntime, isRuntimeLoggingEnabled };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ type ToolMode = "disabled" | "allowed" | "required";
|
|
|
4
4
|
interface OpenRouterRuntime {
|
|
5
5
|
run(request: RuntimeRequest): Promise<RuntimeResponse>;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Explicit streaming chat API. Separate from `run()` by design — never an argument, never the default.
|
|
8
|
+
* Always sends OpenRouter Chat Completions with `stream: true` and yields typed SSE events.
|
|
8
9
|
* The old `stream()` name was removed so mistaken/old clients break loudly.
|
|
9
10
|
*/
|
|
10
11
|
executeStreamingChat(request: RuntimeRequest): AsyncIterable<RuntimeStreamEvent>;
|
|
@@ -364,9 +365,60 @@ interface RuntimePolicyViolation {
|
|
|
364
365
|
details?: unknown;
|
|
365
366
|
}
|
|
366
367
|
interface RuntimeStreamEvent {
|
|
367
|
-
type:
|
|
368
|
+
type: RuntimeStreamEventType;
|
|
369
|
+
requestId: string;
|
|
368
370
|
data?: unknown;
|
|
369
371
|
}
|
|
372
|
+
type RuntimeStreamEventType = "stream.start" | "stream.text.delta" | "stream.tool_call.delta" | "stream.usage" | "stream.warning" | "stream.error" | "stream.done";
|
|
373
|
+
interface RuntimeStreamStartEvent extends RuntimeStreamEvent {
|
|
374
|
+
type: "stream.start";
|
|
375
|
+
data: {
|
|
376
|
+
model: string;
|
|
377
|
+
apiMode: "chat";
|
|
378
|
+
entrypoint: "executeStreamingChat";
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
interface RuntimeStreamTextDeltaEvent extends RuntimeStreamEvent {
|
|
382
|
+
type: "stream.text.delta";
|
|
383
|
+
data: {
|
|
384
|
+
text: string;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
interface RuntimeStreamToolCallDeltaEvent extends RuntimeStreamEvent {
|
|
388
|
+
type: "stream.tool_call.delta";
|
|
389
|
+
data: {
|
|
390
|
+
index: number;
|
|
391
|
+
id?: string;
|
|
392
|
+
name?: string;
|
|
393
|
+
argumentsDelta?: string;
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
interface RuntimeStreamUsageEvent extends RuntimeStreamEvent {
|
|
397
|
+
type: "stream.usage";
|
|
398
|
+
data: {
|
|
399
|
+
usage: RuntimeUsage;
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
interface RuntimeStreamWarningEvent extends RuntimeStreamEvent {
|
|
403
|
+
type: "stream.warning";
|
|
404
|
+
data: {
|
|
405
|
+
warning: RuntimeWarning;
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
interface RuntimeStreamErrorEvent extends RuntimeStreamEvent {
|
|
409
|
+
type: "stream.error";
|
|
410
|
+
data: {
|
|
411
|
+
error: RuntimeError;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
interface RuntimeStreamDoneEvent extends RuntimeStreamEvent {
|
|
415
|
+
type: "stream.done";
|
|
416
|
+
data: {
|
|
417
|
+
text: string;
|
|
418
|
+
finishReason?: string;
|
|
419
|
+
model: string;
|
|
420
|
+
};
|
|
421
|
+
}
|
|
370
422
|
type RuntimePatchApplier = (patch: RuntimePatchProposal) => Promise<unknown> | unknown;
|
|
371
423
|
|
|
372
424
|
declare function createOpenRouterRuntime(options: OpenRouterRuntimeOptions): OpenRouterRuntime;
|
|
@@ -414,4 +466,4 @@ declare function buildOpenRouterServerTools(policy?: RuntimeServerToolsPolicy):
|
|
|
414
466
|
declare function isRuntimeLoggingEnabled(env?: Record<string, string | undefined> | undefined): boolean;
|
|
415
467
|
declare function createConsoleLogger(): RuntimeLogger;
|
|
416
468
|
|
|
417
|
-
export { type AdvisorPolicy, type ApplyPatchPolicy, type CompiledOpenRouterRequest, type DatetimePolicy, type FusionPolicy, type ImageGenerationPolicy, OpenRouterHttpError, type OpenRouterRuntime, type OpenRouterRuntimeOptions, type RuntimeCitation, RuntimeConfigError, type RuntimeDefaults, type RuntimeError, type RuntimeFunctionToolDefinition, type RuntimeGeneratedImage, type RuntimeLogger, type RuntimeMessage, type RuntimePatchProposal, type RuntimePolicyViolation, type RuntimeRequest, type RuntimeResponse, type RuntimeServerToolsPolicy, type RuntimeStreamEvent, type RuntimeToolExecutor, type RuntimeToolUsage, type RuntimeUsage, type RuntimeWarning, type ServerToolKey, type ServerToolUsage, type SubagentPolicy, type WebFetchPolicy, type WebSearchPolicy, buildOpenRouterServerTools, compileRuntimeRequest, createConsoleLogger, createOpenRouterRuntime, isRuntimeLoggingEnabled };
|
|
469
|
+
export { type AdvisorPolicy, type ApplyPatchPolicy, type CompiledOpenRouterRequest, type DatetimePolicy, type FusionPolicy, type ImageGenerationPolicy, OpenRouterHttpError, type OpenRouterRuntime, type OpenRouterRuntimeOptions, type RuntimeCitation, RuntimeConfigError, type RuntimeDefaults, type RuntimeError, type RuntimeFunctionToolDefinition, type RuntimeGeneratedImage, type RuntimeLogger, type RuntimeMessage, type RuntimePatchProposal, type RuntimePolicyViolation, type RuntimeRequest, type RuntimeResponse, type RuntimeServerToolsPolicy, type RuntimeStreamDoneEvent, type RuntimeStreamErrorEvent, type RuntimeStreamEvent, type RuntimeStreamEventType, type RuntimeStreamStartEvent, type RuntimeStreamTextDeltaEvent, type RuntimeStreamToolCallDeltaEvent, type RuntimeStreamUsageEvent, type RuntimeStreamWarningEvent, type RuntimeToolExecutor, type RuntimeToolUsage, type RuntimeUsage, type RuntimeWarning, type ServerToolKey, type ServerToolUsage, type SubagentPolicy, type WebFetchPolicy, type WebSearchPolicy, buildOpenRouterServerTools, compileRuntimeRequest, createConsoleLogger, createOpenRouterRuntime, isRuntimeLoggingEnabled };
|
package/dist/index.js
CHANGED
|
@@ -918,11 +918,14 @@ function compileRuntimeRequest(request, defaults, options = { apiKey: "" }, cont
|
|
|
918
918
|
};
|
|
919
919
|
}
|
|
920
920
|
function enforceStreamingPolicyForRun(body, warnings, context) {
|
|
921
|
-
if (context.streaming)
|
|
921
|
+
if (context.streaming) {
|
|
922
|
+
body.stream = true;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
922
925
|
if (body.stream !== void 0 && body.stream !== false) {
|
|
923
926
|
warnings.push({
|
|
924
927
|
code: "STREAMING_OVERRIDE_IGNORED_FOR_RUN",
|
|
925
|
-
message: "runtime.run() is non-streaming. Use runtime.executeStreamingChat()
|
|
928
|
+
message: "runtime.run() is non-streaming. Use runtime.executeStreamingChat() for streaming.",
|
|
926
929
|
details: { ignoredStreamValue: body.stream }
|
|
927
930
|
});
|
|
928
931
|
}
|
|
@@ -1325,12 +1328,333 @@ function optional6(key, value) {
|
|
|
1325
1328
|
return value === void 0 ? {} : { [key]: value };
|
|
1326
1329
|
}
|
|
1327
1330
|
|
|
1331
|
+
// src/openrouter/stream-client.ts
|
|
1332
|
+
async function openOpenRouterStream(compiled, options, timeoutMs) {
|
|
1333
|
+
const controller = new AbortController();
|
|
1334
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1335
|
+
const cleanup = () => clearTimeout(timeout);
|
|
1336
|
+
try {
|
|
1337
|
+
const response = await options.fetch(compiled.url, {
|
|
1338
|
+
method: "POST",
|
|
1339
|
+
headers: {
|
|
1340
|
+
...compiled.headers,
|
|
1341
|
+
Accept: "text/event-stream"
|
|
1342
|
+
},
|
|
1343
|
+
body: JSON.stringify(compiled.body),
|
|
1344
|
+
signal: controller.signal
|
|
1345
|
+
});
|
|
1346
|
+
if (!response.ok) {
|
|
1347
|
+
const text = await response.text();
|
|
1348
|
+
cleanup();
|
|
1349
|
+
const body = text ? parseBody2(text) : void 0;
|
|
1350
|
+
const message = extractMessage2(body) ?? `OpenRouter stream failed with status ${response.status}.`;
|
|
1351
|
+
throw new OpenRouterHttpError(
|
|
1352
|
+
response.status,
|
|
1353
|
+
classifyProviderFailure2(response.status, message, body),
|
|
1354
|
+
message,
|
|
1355
|
+
body,
|
|
1356
|
+
isRetryableStatus2(response.status)
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
return { response, cleanup };
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
cleanup();
|
|
1362
|
+
if (error instanceof OpenRouterHttpError) throw error;
|
|
1363
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1364
|
+
throw new OpenRouterHttpError(408, "PROVIDER_GATEWAY_UNREACHABLE", "OpenRouter stream timed out.", void 0, true);
|
|
1365
|
+
}
|
|
1366
|
+
throw new OpenRouterHttpError(
|
|
1367
|
+
503,
|
|
1368
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1369
|
+
error instanceof Error ? error.message : "OpenRouter stream failed before receiving a response.",
|
|
1370
|
+
void 0,
|
|
1371
|
+
true
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async function* parseSseStream(response) {
|
|
1376
|
+
const body = response.body;
|
|
1377
|
+
if (!body) {
|
|
1378
|
+
throw new OpenRouterHttpError(
|
|
1379
|
+
503,
|
|
1380
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1381
|
+
"OpenRouter stream response had no body.",
|
|
1382
|
+
void 0,
|
|
1383
|
+
true
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
const reader = body.getReader();
|
|
1387
|
+
const decoder = new TextDecoder();
|
|
1388
|
+
let buffer = "";
|
|
1389
|
+
while (true) {
|
|
1390
|
+
const { done, value } = await reader.read();
|
|
1391
|
+
if (done) break;
|
|
1392
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1393
|
+
const parts = buffer.split(/\r?\n/);
|
|
1394
|
+
buffer = parts.pop() ?? "";
|
|
1395
|
+
for (const part of parts) {
|
|
1396
|
+
const line = part.trim();
|
|
1397
|
+
if (!line || line.startsWith(":")) continue;
|
|
1398
|
+
if (!line.startsWith("data:")) continue;
|
|
1399
|
+
const data = line.slice(5).trim();
|
|
1400
|
+
if (!data) continue;
|
|
1401
|
+
if (data === "[DONE]") return;
|
|
1402
|
+
try {
|
|
1403
|
+
yield JSON.parse(data);
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const trailing = buffer.trim();
|
|
1409
|
+
if (trailing.startsWith("data:")) {
|
|
1410
|
+
const data = trailing.slice(5).trim();
|
|
1411
|
+
if (data && data !== "[DONE]") {
|
|
1412
|
+
try {
|
|
1413
|
+
yield JSON.parse(data);
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
function parseBody2(text) {
|
|
1420
|
+
try {
|
|
1421
|
+
return JSON.parse(text);
|
|
1422
|
+
} catch {
|
|
1423
|
+
return text;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function extractMessage2(body) {
|
|
1427
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1428
|
+
const record = body;
|
|
1429
|
+
const error = record.error;
|
|
1430
|
+
if (typeof error === "object" && error !== null && typeof error.message === "string") {
|
|
1431
|
+
return error.message;
|
|
1432
|
+
}
|
|
1433
|
+
return typeof record.message === "string" ? record.message : void 0;
|
|
1434
|
+
}
|
|
1435
|
+
function classifyProviderFailure2(status, message, body) {
|
|
1436
|
+
const haystack = `${message} ${JSON.stringify(body ?? "")}`.toLowerCase();
|
|
1437
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1438
|
+
if (status === 402 || /\b(quota|credit|credits|billing|balance|insufficient funds|insufficient credit)\b/.test(haystack)) {
|
|
1439
|
+
return "PROVIDER_QUOTA_EXCEEDED";
|
|
1440
|
+
}
|
|
1441
|
+
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)) {
|
|
1442
|
+
return "PROVIDER_MODEL_NOT_FOUND";
|
|
1443
|
+
}
|
|
1444
|
+
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
1445
|
+
if (status === 408 || status >= 500) return "PROVIDER_GATEWAY_UNREACHABLE";
|
|
1446
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
1447
|
+
}
|
|
1448
|
+
function isRetryableStatus2(status) {
|
|
1449
|
+
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1328
1452
|
// src/runtime/streaming.ts
|
|
1329
|
-
async function*
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1453
|
+
async function* executeStreamingChat(request, context) {
|
|
1454
|
+
const requestId = request.id ?? "unknown";
|
|
1455
|
+
const options = context.options;
|
|
1456
|
+
const defaults = context.defaults;
|
|
1457
|
+
options.logger?.info?.("runtime.executeStreamingChat.started", {
|
|
1458
|
+
requestId,
|
|
1459
|
+
streaming: true,
|
|
1460
|
+
entrypoint: "executeStreamingChat"
|
|
1461
|
+
});
|
|
1462
|
+
try {
|
|
1463
|
+
assertStreamingChatRequest(request, defaults);
|
|
1464
|
+
const streamingRequest = {
|
|
1465
|
+
...request,
|
|
1466
|
+
apiMode: "chat"
|
|
1467
|
+
};
|
|
1468
|
+
const compiled = compileRuntimeRequest(streamingRequest, defaults, options, { streaming: true });
|
|
1469
|
+
if (compiled.body.stream !== true) {
|
|
1470
|
+
throw new RuntimeConfigError(
|
|
1471
|
+
"STREAMING_CHAT_MISCONFIGURED",
|
|
1472
|
+
"executeStreamingChat() requires stream=true on the compiled OpenRouter body."
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
for (const warning of compiled.warnings) {
|
|
1476
|
+
yield warningEvent(requestId, warning);
|
|
1477
|
+
}
|
|
1478
|
+
const model = typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1479
|
+
yield startEvent(requestId, model);
|
|
1480
|
+
await options.hooks?.beforeOpenRouterRequest?.(compiled);
|
|
1481
|
+
const timeoutMs = request.execution?.timeoutMs ?? defaults.timeoutMs;
|
|
1482
|
+
const session = await openOpenRouterStream(compiled, options, timeoutMs);
|
|
1483
|
+
let text = "";
|
|
1484
|
+
let finishReason;
|
|
1485
|
+
let resolvedModel = model;
|
|
1486
|
+
try {
|
|
1487
|
+
for await (const chunk of parseSseStream(session.response)) {
|
|
1488
|
+
if (!isRecord(chunk)) continue;
|
|
1489
|
+
if (chunk.error) {
|
|
1490
|
+
const message = isRecord(chunk.error) && typeof chunk.error.message === "string" ? chunk.error.message : "OpenRouter stream returned an error chunk.";
|
|
1491
|
+
const error = {
|
|
1492
|
+
code: "PROVIDER_REQUEST_FAILED",
|
|
1493
|
+
message,
|
|
1494
|
+
source: "openrouter",
|
|
1495
|
+
details: chunk.error
|
|
1496
|
+
};
|
|
1497
|
+
yield errorEvent(requestId, error);
|
|
1498
|
+
throw new OpenRouterHttpError(500, "PROVIDER_REQUEST_FAILED", message, chunk, false);
|
|
1499
|
+
}
|
|
1500
|
+
if (typeof chunk.model === "string") resolvedModel = chunk.model;
|
|
1501
|
+
const choice = Array.isArray(chunk.choices) && isRecord(chunk.choices[0]) ? chunk.choices[0] : void 0;
|
|
1502
|
+
const delta = choice && isRecord(choice.delta) ? choice.delta : void 0;
|
|
1503
|
+
if (delta && typeof delta.content === "string" && delta.content.length > 0) {
|
|
1504
|
+
text += delta.content;
|
|
1505
|
+
yield textDeltaEvent(requestId, delta.content);
|
|
1506
|
+
}
|
|
1507
|
+
if (delta && Array.isArray(delta.tool_calls)) {
|
|
1508
|
+
for (const toolCall of delta.tool_calls) {
|
|
1509
|
+
if (!isRecord(toolCall)) continue;
|
|
1510
|
+
const index = typeof toolCall.index === "number" ? toolCall.index : 0;
|
|
1511
|
+
const fn = isRecord(toolCall.function) ? toolCall.function : {};
|
|
1512
|
+
yield toolCallDeltaEvent(requestId, {
|
|
1513
|
+
index,
|
|
1514
|
+
...typeof toolCall.id === "string" ? { id: toolCall.id } : {},
|
|
1515
|
+
...typeof fn.name === "string" ? { name: fn.name } : {},
|
|
1516
|
+
...typeof fn.arguments === "string" ? { argumentsDelta: fn.arguments } : {}
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (choice && typeof choice.finish_reason === "string") {
|
|
1521
|
+
finishReason = choice.finish_reason;
|
|
1522
|
+
}
|
|
1523
|
+
if (chunk.usage) {
|
|
1524
|
+
const usage = normalizeUsage(chunk.usage);
|
|
1525
|
+
if (usage) yield usageEvent(requestId, usage);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
} finally {
|
|
1529
|
+
session.cleanup();
|
|
1530
|
+
}
|
|
1531
|
+
await options.hooks?.afterOpenRouterResponse?.({ streamed: true, text, finishReason, model: resolvedModel });
|
|
1532
|
+
yield doneEvent(requestId, {
|
|
1533
|
+
text,
|
|
1534
|
+
model: resolvedModel,
|
|
1535
|
+
...finishReason ? { finishReason } : {}
|
|
1536
|
+
});
|
|
1537
|
+
options.logger?.info?.("runtime.executeStreamingChat.completed", {
|
|
1538
|
+
requestId,
|
|
1539
|
+
streaming: true,
|
|
1540
|
+
entrypoint: "executeStreamingChat",
|
|
1541
|
+
finishReason,
|
|
1542
|
+
textLength: text.length
|
|
1543
|
+
});
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
const runtimeError = toRuntimeError(error);
|
|
1546
|
+
yield errorEvent(requestId, runtimeError);
|
|
1547
|
+
options.logger?.error?.("runtime.executeStreamingChat.failed", {
|
|
1548
|
+
requestId,
|
|
1549
|
+
streaming: true,
|
|
1550
|
+
code: runtimeError.code,
|
|
1551
|
+
message: runtimeError.message
|
|
1552
|
+
});
|
|
1553
|
+
throw error instanceof Error ? error : new Error(runtimeError.message);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function assertStreamingChatRequest(request, defaults) {
|
|
1557
|
+
if (request.apiMode === "responses") {
|
|
1558
|
+
throw new RuntimeConfigError(
|
|
1559
|
+
"STREAMING_CHAT_REQUIRES_CHAT_API",
|
|
1560
|
+
"executeStreamingChat() only supports Chat Completions. Use runtime.run() for Responses API requests."
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
if (request.serverTools?.applyPatch?.mode && request.serverTools.applyPatch.mode !== "disabled") {
|
|
1564
|
+
throw new RuntimeConfigError(
|
|
1565
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1566
|
+
"executeStreamingChat() does not support applyPatch. Use runtime.run() instead."
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
if (defaults.serverTools?.applyPatch?.mode && defaults.serverTools.applyPatch.mode !== "disabled") {
|
|
1570
|
+
const requestPatch = request.serverTools?.applyPatch;
|
|
1571
|
+
if (!requestPatch || requestPatch.mode !== "disabled") {
|
|
1572
|
+
throw new RuntimeConfigError(
|
|
1573
|
+
"STREAMING_CHAT_DOES_NOT_SUPPORT_APPLY_PATCH",
|
|
1574
|
+
"executeStreamingChat() does not support applyPatch defaults. Use runtime.run() instead."
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function startEvent(requestId, model) {
|
|
1580
|
+
return {
|
|
1581
|
+
type: "stream.start",
|
|
1582
|
+
requestId,
|
|
1583
|
+
data: {
|
|
1584
|
+
model,
|
|
1585
|
+
apiMode: "chat",
|
|
1586
|
+
entrypoint: "executeStreamingChat"
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
function textDeltaEvent(requestId, text) {
|
|
1591
|
+
return {
|
|
1592
|
+
type: "stream.text.delta",
|
|
1593
|
+
requestId,
|
|
1594
|
+
data: { text }
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function toolCallDeltaEvent(requestId, data) {
|
|
1598
|
+
return {
|
|
1599
|
+
type: "stream.tool_call.delta",
|
|
1600
|
+
requestId,
|
|
1601
|
+
data
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
function usageEvent(requestId, usage) {
|
|
1605
|
+
return {
|
|
1606
|
+
type: "stream.usage",
|
|
1607
|
+
requestId,
|
|
1608
|
+
data: { usage }
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function warningEvent(requestId, warning) {
|
|
1612
|
+
return {
|
|
1613
|
+
type: "stream.warning",
|
|
1614
|
+
requestId,
|
|
1615
|
+
data: { warning }
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
function errorEvent(requestId, error) {
|
|
1619
|
+
return {
|
|
1620
|
+
type: "stream.error",
|
|
1621
|
+
requestId,
|
|
1622
|
+
data: { error }
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
function doneEvent(requestId, data) {
|
|
1626
|
+
return {
|
|
1627
|
+
type: "stream.done",
|
|
1628
|
+
requestId,
|
|
1629
|
+
data
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function toRuntimeError(error) {
|
|
1633
|
+
if (error instanceof RuntimeConfigError) {
|
|
1634
|
+
return {
|
|
1635
|
+
code: error.code,
|
|
1636
|
+
message: error.message,
|
|
1637
|
+
source: "validation",
|
|
1638
|
+
details: error.details
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
if (error instanceof OpenRouterHttpError) {
|
|
1642
|
+
return {
|
|
1643
|
+
code: error.code,
|
|
1644
|
+
message: error.message,
|
|
1645
|
+
source: "openrouter",
|
|
1646
|
+
retryable: error.retryable,
|
|
1647
|
+
details: {
|
|
1648
|
+
providerAttempts: error.providerAttempts,
|
|
1649
|
+
...error.body === void 0 ? {} : { response: error.body }
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
return {
|
|
1654
|
+
code: "RUNTIME_ERROR",
|
|
1655
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1656
|
+
source: "runtime"
|
|
1657
|
+
};
|
|
1334
1658
|
}
|
|
1335
1659
|
|
|
1336
1660
|
// src/runtime/create-runtime.ts
|
|
@@ -1371,13 +1695,8 @@ function createOpenRouterRuntime(options) {
|
|
|
1371
1695
|
}
|
|
1372
1696
|
},
|
|
1373
1697
|
executeStreamingChat(request) {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
streaming: true,
|
|
1377
|
-
entrypoint: "executeStreamingChat",
|
|
1378
|
-
implemented: false
|
|
1379
|
-
});
|
|
1380
|
-
return executeStreamingChatNotImplemented();
|
|
1698
|
+
const requestWithId = { ...request, id: request.id ?? createRuntimeId() };
|
|
1699
|
+
return executeStreamingChat(requestWithId, context);
|
|
1381
1700
|
}
|
|
1382
1701
|
};
|
|
1383
1702
|
function errorResponse(request, error) {
|