190proof 1.0.109 → 1.0.111

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 CHANGED
@@ -278,6 +278,9 @@ Optional per-request knobs live on `payload` (`GenericPayload`):
278
278
  - `payload.requestTimeoutMs`: `number` - Per-attempt HTTP timeout in ms (default: 120000), honored by every adapter — except streaming OpenRouter attempts, which it deliberately does NOT bound (see below). For OpenRouter's non-streaming transport the default is 180000.
279
279
  - `payload.streaming`: `boolean` - OpenRouter-only (default: true). Streams the completion over SSE. A streaming attempt is bounded by two independent timers instead of `requestTimeoutMs`: `streamTimeoutMs` (total wall clock, default 600000) and the per-useful-chunk stall timeout (`chunkTimeoutMs` argument, default 15000). A chunk is "useful" only if it advances content, reasoning, tool-call fragments, finish_reason, or usage — SSE comment keep-alives (`: OPENROUTER PROCESSING`) and role-only deltas don't reset the stall timer, so a hung provider dies within one stall window while a healthy long generation can run to the total budget. Set `streaming: false` for the old single-JSON-body transport.
280
280
  - `payload.streamTimeoutMs`: `number` - OpenRouter-only: total wall-clock budget per streaming attempt (default: 600000).
281
+ - `payload.streamDeadlineAt`: `number` - OpenRouter-only: absolute deadline (epoch ms) for the whole call **including retries** — the caller's turn budget. Each attempt gets `min(streamTimeoutMs, deadline - now)`, and once under 10s remain the call fails fast instead of starting a generation that cannot be delivered. Use it whenever the caller has its own timeout: a per-attempt budget alone is re-granted on every retry and can outlive that timeout.
282
+
283
+ OpenRouter retries also perform **moderation eviction**: a provider content-moderation rejection (e.g. "Upstream error from Alibaba: Output data may contain inappropriate content.") is deterministic for a given payload, so on the first one the refusing provider is removed from the request's provider preferences (`ignore` += slug, `order` -= slug) and every remaining attempt reroutes to the next provider. Non-moderation errors retry with unchanged preferences, and `fallbackModel` still applies if the whole pool refuses.
281
284
  - `payload.signal`: `AbortSignal` - Caller-supplied cancellation. When it aborts, the in-flight provider request is cancelled and `callWithRetries` **rejects immediately — it does not retry or fall back** (both the retry loop and the fallback branch bail on `signal.aborted`). Threaded to the underlying fetch/axios/SDK call of each provider.
282
285
 
283
286
  #### Returns
package/dist/index.d.mts CHANGED
@@ -297,6 +297,19 @@ interface GenericPayload {
297
297
  * by chunks that advance the output, never by keep-alive bytes/comments).
298
298
  */
299
299
  streamTimeoutMs?: number;
300
+ /**
301
+ * OpenRouter-only: absolute wall-clock deadline (epoch ms) for the whole
302
+ * call INCLUDING retries — the caller's turn budget, not a per-attempt one.
303
+ * Each streaming attempt gets `min(streamTimeoutMs, deadline - now)`, and
304
+ * once too little time remains to be worth an attempt the call fails fast
305
+ * instead of starting a generation that cannot finish.
306
+ *
307
+ * Without it, a per-attempt budget is re-granted on every retry, so a slow
308
+ * generation can outlive the caller's own turn deadline and get killed with
309
+ * nothing to show (2026-07-28: a 538s completion finished just as the
310
+ * caller's 585s turn budget expired, and the reply was discarded).
311
+ */
312
+ streamDeadlineAt?: number;
300
313
  /**
301
314
  * Optional caller-supplied cancellation signal. When it aborts, the in-flight
302
315
  * provider request is cancelled and `callWithRetries` rejects immediately —
@@ -309,10 +322,16 @@ interface GenericPayload {
309
322
 
310
323
  declare const OPENROUTER_STREAM_TIMEOUT_MS = 600000;
311
324
  declare const OPENROUTER_NONSTREAM_TIMEOUT_MS = 180000;
325
+ /**
326
+ * Least time a streaming attempt is worth starting with. Below this the
327
+ * generation cannot plausibly finish before the caller's deadline, so burning
328
+ * a provider call (and paying for tokens that get discarded) is pure waste.
329
+ */
330
+ declare const MIN_STREAM_ATTEMPT_MS = 10000;
312
331
  declare function parseModelString(model: string): {
313
332
  provider: Provider;
314
333
  modelId: string;
315
334
  };
316
335
  declare function callWithRetries(id: string | string[], aiPayload: GenericPayload, aiConfig?: OpenAIConfig | AnthropicAIConfig, retries?: number, chunkTimeoutMs?: number): Promise<ParsedResponseMessage>;
317
336
 
318
- export { type AnyModel, ClaudeModel, type FunctionCall, type FunctionDefinition, GPTModel, GeminiModel, type GenericMessage, type GenericPayload, GroqModel, OPENROUTER_NONSTREAM_TIMEOUT_MS, OPENROUTER_STREAM_TIMEOUT_MS, type OpenAIConfig, OpenRouterModel, type OpenRouterProviderPreferences, type ParsedResponseMessage, type Provider, type ToolResult, callWithRetries, parseModelString };
337
+ export { type AnyModel, ClaudeModel, type FunctionCall, type FunctionDefinition, GPTModel, GeminiModel, type GenericMessage, type GenericPayload, GroqModel, MIN_STREAM_ATTEMPT_MS, OPENROUTER_NONSTREAM_TIMEOUT_MS, OPENROUTER_STREAM_TIMEOUT_MS, type OpenAIConfig, OpenRouterModel, type OpenRouterProviderPreferences, type ParsedResponseMessage, type Provider, type ToolResult, callWithRetries, parseModelString };
package/dist/index.d.ts CHANGED
@@ -297,6 +297,19 @@ interface GenericPayload {
297
297
  * by chunks that advance the output, never by keep-alive bytes/comments).
298
298
  */
299
299
  streamTimeoutMs?: number;
300
+ /**
301
+ * OpenRouter-only: absolute wall-clock deadline (epoch ms) for the whole
302
+ * call INCLUDING retries — the caller's turn budget, not a per-attempt one.
303
+ * Each streaming attempt gets `min(streamTimeoutMs, deadline - now)`, and
304
+ * once too little time remains to be worth an attempt the call fails fast
305
+ * instead of starting a generation that cannot finish.
306
+ *
307
+ * Without it, a per-attempt budget is re-granted on every retry, so a slow
308
+ * generation can outlive the caller's own turn deadline and get killed with
309
+ * nothing to show (2026-07-28: a 538s completion finished just as the
310
+ * caller's 585s turn budget expired, and the reply was discarded).
311
+ */
312
+ streamDeadlineAt?: number;
300
313
  /**
301
314
  * Optional caller-supplied cancellation signal. When it aborts, the in-flight
302
315
  * provider request is cancelled and `callWithRetries` rejects immediately —
@@ -309,10 +322,16 @@ interface GenericPayload {
309
322
 
310
323
  declare const OPENROUTER_STREAM_TIMEOUT_MS = 600000;
311
324
  declare const OPENROUTER_NONSTREAM_TIMEOUT_MS = 180000;
325
+ /**
326
+ * Least time a streaming attempt is worth starting with. Below this the
327
+ * generation cannot plausibly finish before the caller's deadline, so burning
328
+ * a provider call (and paying for tokens that get discarded) is pure waste.
329
+ */
330
+ declare const MIN_STREAM_ATTEMPT_MS = 10000;
312
331
  declare function parseModelString(model: string): {
313
332
  provider: Provider;
314
333
  modelId: string;
315
334
  };
316
335
  declare function callWithRetries(id: string | string[], aiPayload: GenericPayload, aiConfig?: OpenAIConfig | AnthropicAIConfig, retries?: number, chunkTimeoutMs?: number): Promise<ParsedResponseMessage>;
317
336
 
318
- export { type AnyModel, ClaudeModel, type FunctionCall, type FunctionDefinition, GPTModel, GeminiModel, type GenericMessage, type GenericPayload, GroqModel, OPENROUTER_NONSTREAM_TIMEOUT_MS, OPENROUTER_STREAM_TIMEOUT_MS, type OpenAIConfig, OpenRouterModel, type OpenRouterProviderPreferences, type ParsedResponseMessage, type Provider, type ToolResult, callWithRetries, parseModelString };
337
+ export { type AnyModel, ClaudeModel, type FunctionCall, type FunctionDefinition, GPTModel, GeminiModel, type GenericMessage, type GenericPayload, GroqModel, MIN_STREAM_ATTEMPT_MS, OPENROUTER_NONSTREAM_TIMEOUT_MS, OPENROUTER_STREAM_TIMEOUT_MS, type OpenAIConfig, OpenRouterModel, type OpenRouterProviderPreferences, type ParsedResponseMessage, type Provider, type ToolResult, callWithRetries, parseModelString };
package/dist/index.js CHANGED
@@ -28,19 +28,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
31
+ var proof_exports = {};
32
+ __export(proof_exports, {
33
33
  ClaudeModel: () => ClaudeModel,
34
34
  GPTModel: () => GPTModel,
35
35
  GeminiModel: () => GeminiModel,
36
36
  GroqModel: () => GroqModel,
37
+ MIN_STREAM_ATTEMPT_MS: () => MIN_STREAM_ATTEMPT_MS,
37
38
  OPENROUTER_NONSTREAM_TIMEOUT_MS: () => OPENROUTER_NONSTREAM_TIMEOUT_MS,
38
39
  OPENROUTER_STREAM_TIMEOUT_MS: () => OPENROUTER_STREAM_TIMEOUT_MS,
39
40
  OpenRouterModel: () => OpenRouterModel,
40
41
  callWithRetries: () => callWithRetries,
41
42
  parseModelString: () => parseModelString
42
43
  });
43
- module.exports = __toCommonJS(index_exports);
44
+ module.exports = __toCommonJS(proof_exports);
44
45
 
45
46
  // interfaces.ts
46
47
  var ClaudeModel = /* @__PURE__ */ ((ClaudeModel2) => {
@@ -171,7 +172,8 @@ async function withRetries(identifier, apiName, fn, options = {}) {
171
172
  return await fn();
172
173
  } catch (error3) {
173
174
  lastError = error3;
174
- if ((_a = options.signal) == null ? void 0 : _a.aborted) throw error3;
175
+ if ((_a = options.signal) == null ? void 0 : _a.aborted)
176
+ throw error3;
175
177
  if (onError) {
176
178
  onError(error3, attempt);
177
179
  } else {
@@ -195,7 +197,8 @@ function parseStreamedResponse(identifier, paragraph, toolCallAccumulators, allo
195
197
  const functionCalls = [];
196
198
  for (let i = 0; i < toolCallAccumulators.length; i++) {
197
199
  const acc = toolCallAccumulators[i];
198
- if (!acc.name || !acc.arguments) continue;
200
+ if (!acc.name || !acc.arguments)
201
+ continue;
199
202
  if (allowedFunctionNames && !allowedFunctionNames.has(acc.name)) {
200
203
  throw new Error(
201
204
  `Stream error: received function call with unknown name: ${acc.name}`
@@ -334,7 +337,8 @@ function buildOpenAIRequestConfig(identifier, model, config) {
334
337
  };
335
338
  }
336
339
  function filterOpenAICompatReasoningDetails(details) {
337
- if (!Array.isArray(details)) return details || void 0;
340
+ if (!Array.isArray(details))
341
+ return details || void 0;
338
342
  const blocks = details.filter(
339
343
  (block) => typeof (block == null ? void 0 : block.type) === "string" && block.type.startsWith("reasoning.")
340
344
  );
@@ -405,11 +409,13 @@ async function prepareOpenAIPayload(identifier, payload) {
405
409
  };
406
410
  });
407
411
  }
408
- if (message.reasoning) outMessage.reasoning = message.reasoning;
412
+ if (message.reasoning)
413
+ outMessage.reasoning = message.reasoning;
409
414
  const reasoningDetails = filterOpenAICompatReasoningDetails(
410
415
  message.reasoningDetails
411
416
  );
412
- if (reasoningDetails) outMessage.reasoning_details = reasoningDetails;
417
+ if (reasoningDetails)
418
+ outMessage.reasoning_details = reasoningDetails;
413
419
  preparedPayload.messages.push(outMessage);
414
420
  }
415
421
  return preparedPayload;
@@ -471,7 +477,8 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
471
477
  }
472
478
  const jsonStrings = chunk.split(/^data: /gm);
473
479
  for (const jsonString of jsonStrings) {
474
- if (!jsonString) continue;
480
+ if (!jsonString)
481
+ continue;
475
482
  if (jsonString.includes("[DONE]")) {
476
483
  clearTimeout(overallTimeout);
477
484
  return parseStreamedResponse(
@@ -509,7 +516,8 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
509
516
  while (toolCallAccumulators.length <= idx) {
510
517
  toolCallAccumulators.push({ name: "", arguments: "" });
511
518
  }
512
- if (toolCall.id) toolCallAccumulators[idx].id = toolCall.id;
519
+ if (toolCall.id)
520
+ toolCallAccumulators[idx].id = toolCall.id;
513
521
  if ((_e = toolCall.function) == null ? void 0 : _e.name)
514
522
  toolCallAccumulators[idx].name += toolCall.function.name;
515
523
  if ((_f = toolCall.function) == null ? void 0 : _f.arguments)
@@ -517,9 +525,11 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
517
525
  }
518
526
  }
519
527
  const text = (_h = (_g = json.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content;
520
- if (text) paragraph += text;
528
+ if (text)
529
+ paragraph += text;
521
530
  const reasoningDelta = (_j = (_i = json.choices[0]) == null ? void 0 : _i.delta) == null ? void 0 : _j.reasoning;
522
- if (reasoningDelta) reasoning += reasoningDelta;
531
+ if (reasoningDelta)
532
+ reasoning += reasoningDelta;
523
533
  }
524
534
  }
525
535
  }
@@ -664,7 +674,8 @@ function jigAnthropicMessages(messages) {
664
674
  ];
665
675
  }
666
676
  jiggedMessages = jiggedMessages.reduce((acc, message) => {
667
- if (acc.length === 0) return [message];
677
+ if (acc.length === 0)
678
+ return [message];
668
679
  const lastMessage = acc[acc.length - 1];
669
680
  if (lastMessage.role === message.role) {
670
681
  const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [{ type: "text", text: lastMessage.content }];
@@ -918,7 +929,8 @@ function jigGoogleMessages(messages) {
918
929
  ];
919
930
  }
920
931
  jiggedMessages = jiggedMessages.reduce((acc, message) => {
921
- if (acc.length === 0) return [message];
932
+ if (acc.length === 0)
933
+ return [message];
922
934
  const lastMessage = acc[acc.length - 1];
923
935
  if (lastMessage.role === message.role) {
924
936
  lastMessage.parts = [...lastMessage.parts, ...message.parts];
@@ -956,7 +968,8 @@ async function prepareGoogleAIPayload(_identifier, payload) {
956
968
  const toolNameById = /* @__PURE__ */ new Map();
957
969
  for (const m of payload.messages) {
958
970
  for (const fc of m.functionCalls || []) {
959
- if (fc.id) toolNameById.set(fc.id, fc.name);
971
+ if (fc.id)
972
+ toolNameById.set(fc.id, fc.name);
960
973
  }
961
974
  }
962
975
  for (const message of payload.messages) {
@@ -1033,7 +1046,8 @@ async function callGoogleAI(id, payload, requestTimeoutMs = 12e4, signal) {
1033
1046
  contents,
1034
1047
  generationConfig: { responseModalities: ["TEXT"] }
1035
1048
  };
1036
- if (payload.tools) requestBody.tools = [payload.tools];
1049
+ if (payload.tools)
1050
+ requestBody.tools = [payload.tools];
1037
1051
  if (payload.tools && payload.toolConfig) {
1038
1052
  requestBody.toolConfig = payload.toolConfig;
1039
1053
  }
@@ -1091,7 +1105,8 @@ async function callGoogleAI(id, payload, requestTimeoutMs = 12e4, signal) {
1091
1105
  });
1092
1106
  continue;
1093
1107
  }
1094
- if (part.text) text += part.text;
1108
+ if (part.text)
1109
+ text += part.text;
1095
1110
  if ((_m = part.inlineData) == null ? void 0 : _m.data) {
1096
1111
  files.push({ mimeType: "image/png", data: part.inlineData.data });
1097
1112
  }
@@ -1168,15 +1183,21 @@ async function callGoogleAIWithRetries(id, payload, retries = 5, requestTimeoutM
1168
1183
  finishReason: error2.finishReason,
1169
1184
  modelVersion: error2.modelVersion
1170
1185
  };
1171
- if (error2.safetyRatings) errorDetails.safetyRatings = error2.safetyRatings;
1172
- if (error2.usageMetadata) errorDetails.usageMetadata = error2.usageMetadata;
1186
+ if (error2.safetyRatings)
1187
+ errorDetails.safetyRatings = error2.safetyRatings;
1188
+ if (error2.usageMetadata)
1189
+ errorDetails.usageMetadata = error2.usageMetadata;
1173
1190
  if (error2.promptFeedback)
1174
1191
  errorDetails.promptFeedback = error2.promptFeedback;
1175
- if (error2.status) errorDetails.httpStatus = error2.status;
1176
- if (error2.code) errorDetails.errorCode = error2.code;
1177
- if (error2.details) errorDetails.errorDetails = error2.details;
1192
+ if (error2.status)
1193
+ errorDetails.httpStatus = error2.status;
1194
+ if (error2.code)
1195
+ errorDetails.errorCode = error2.code;
1196
+ if (error2.details)
1197
+ errorDetails.errorDetails = error2.details;
1178
1198
  const fileUris = payload.messages.flatMap((m) => m.parts).filter((p) => "fileData" in p).map((p) => p.fileData.fileUri);
1179
- if (fileUris.length) errorDetails.fileUris = fileUris;
1199
+ if (fileUris.length)
1200
+ errorDetails.fileUris = fileUris;
1180
1201
  logger_default.error(
1181
1202
  id,
1182
1203
  `Retry #${attempt} error: ${error2.message}`,
@@ -1256,13 +1277,16 @@ function prepareOpenAICompatMessages(messages) {
1256
1277
  }
1257
1278
  };
1258
1279
  });
1259
- if (!message.content) outMessage.content = null;
1280
+ if (!message.content)
1281
+ outMessage.content = null;
1260
1282
  }
1261
- if (message.reasoning) outMessage.reasoning = message.reasoning;
1283
+ if (message.reasoning)
1284
+ outMessage.reasoning = message.reasoning;
1262
1285
  const reasoningDetails = filterOpenAICompatReasoningDetails(
1263
1286
  message.reasoningDetails
1264
1287
  );
1265
- if (reasoningDetails) outMessage.reasoning_details = reasoningDetails;
1288
+ if (reasoningDetails)
1289
+ outMessage.reasoning_details = reasoningDetails;
1266
1290
  out.push(outMessage);
1267
1291
  }
1268
1292
  return out;
@@ -1405,7 +1429,8 @@ function parseDsmlToolCalls(content) {
1405
1429
  let last = -1;
1406
1430
  let tag;
1407
1431
  while ((tag = DSML_ANY_TAG_RE.exec(content)) !== null) {
1408
- if (first === -1) first = tag.index;
1432
+ if (first === -1)
1433
+ first = tag.index;
1409
1434
  last = tag.index + tag[0].length;
1410
1435
  }
1411
1436
  const remaining = first === -1 ? content : (content.slice(0, first) + content.slice(last)).trim();
@@ -1421,7 +1446,8 @@ function finalizeOpenRouterMessage(id, raw) {
1421
1446
  const functionCalls = [];
1422
1447
  for (let i = 0; i < raw.toolCalls.length; i++) {
1423
1448
  const tc = raw.toolCalls[i];
1424
- if (!tc.name) continue;
1449
+ if (!tc.name)
1450
+ continue;
1425
1451
  functionCalls.push({
1426
1452
  id: (_a = tc.id) != null ? _a : `call_${i}`,
1427
1453
  name: tc.name,
@@ -1472,7 +1498,8 @@ async function callOpenRouterStream(id, payload, streamTimeoutMs = OPENROUTER_ST
1472
1498
  controller.abort();
1473
1499
  };
1474
1500
  const unref = (t) => {
1475
- if (typeof t === "object" && "unref" in t) t.unref();
1501
+ if (typeof t === "object" && "unref" in t)
1502
+ t.unref();
1476
1503
  return t;
1477
1504
  };
1478
1505
  const totalTimer = unref(
@@ -1538,88 +1565,98 @@ async function callOpenRouterStream(id, payload, streamTimeoutMs = OPENROUTER_ST
1538
1565
  const reader = response.body.getReader();
1539
1566
  const decoder = new TextDecoder();
1540
1567
  let lineBuffer = "";
1541
- outer: while (true) {
1542
- const { done, value } = await reader.read();
1543
- if (done) break;
1544
- lineBuffer += decoder.decode(value, { stream: true });
1545
- let newlineIdx;
1546
- while ((newlineIdx = lineBuffer.indexOf("\n")) !== -1) {
1547
- let line = lineBuffer.slice(0, newlineIdx);
1548
- lineBuffer = lineBuffer.slice(newlineIdx + 1);
1549
- if (line.endsWith("\r")) line = line.slice(0, -1);
1550
- if (!line) continue;
1551
- if (line.startsWith(":")) continue;
1552
- if (!line.startsWith("data:")) continue;
1553
- const dataStr = line.slice(5).trimStart();
1554
- if (dataStr === "[DONE]") {
1555
- sawDone = true;
1556
- break outer;
1557
- }
1558
- let json;
1559
- try {
1560
- json = JSON.parse(dataStr);
1561
- } catch (e) {
1562
- logger_default.error(
1563
- id,
1564
- "OpenRouter stream: unparseable data line:",
1565
- dataStr.slice(0, 200)
1566
- );
1567
- continue;
1568
- }
1569
- dataChunks++;
1570
- if (json.error) {
1571
- logger_default.error(id, "OpenRouter stream error event:", json.error);
1572
- const error2 = new Error(
1573
- `OpenRouter error: ${json.error.message}`
1574
- );
1575
- error2.data = json.error;
1576
- throw error2;
1577
- }
1578
- if (json.provider) provider = json.provider;
1579
- let useful = false;
1580
- if (json.usage) {
1581
- usage = json.usage;
1582
- useful = true;
1583
- }
1584
- const choice = (_c = json.choices) == null ? void 0 : _c[0];
1585
- if (choice) {
1586
- const delta = (_d = choice.delta) != null ? _d : {};
1587
- if (delta.content) {
1588
- paragraph += delta.content;
1589
- useful = true;
1568
+ outer:
1569
+ while (true) {
1570
+ const { done, value } = await reader.read();
1571
+ if (done)
1572
+ break;
1573
+ lineBuffer += decoder.decode(value, { stream: true });
1574
+ let newlineIdx;
1575
+ while ((newlineIdx = lineBuffer.indexOf("\n")) !== -1) {
1576
+ let line = lineBuffer.slice(0, newlineIdx);
1577
+ lineBuffer = lineBuffer.slice(newlineIdx + 1);
1578
+ if (line.endsWith("\r"))
1579
+ line = line.slice(0, -1);
1580
+ if (!line)
1581
+ continue;
1582
+ if (line.startsWith(":"))
1583
+ continue;
1584
+ if (!line.startsWith("data:"))
1585
+ continue;
1586
+ const dataStr = line.slice(5).trimStart();
1587
+ if (dataStr === "[DONE]") {
1588
+ sawDone = true;
1589
+ break outer;
1590
1590
  }
1591
- if (delta.reasoning) {
1592
- reasoning += delta.reasoning;
1593
- useful = true;
1591
+ let json;
1592
+ try {
1593
+ json = JSON.parse(dataStr);
1594
+ } catch (e) {
1595
+ logger_default.error(
1596
+ id,
1597
+ "OpenRouter stream: unparseable data line:",
1598
+ dataStr.slice(0, 200)
1599
+ );
1600
+ continue;
1601
+ }
1602
+ dataChunks++;
1603
+ if (json.error) {
1604
+ logger_default.error(id, "OpenRouter stream error event:", json.error);
1605
+ const error2 = new Error(
1606
+ `OpenRouter error: ${json.error.message}`
1607
+ );
1608
+ error2.data = json.error;
1609
+ throw error2;
1594
1610
  }
1595
- if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) {
1596
- reasoningDetails.push(...delta.reasoning_details);
1611
+ if (json.provider)
1612
+ provider = json.provider;
1613
+ let useful = false;
1614
+ if (json.usage) {
1615
+ usage = json.usage;
1597
1616
  useful = true;
1598
1617
  }
1599
- if (Array.isArray(delta.tool_calls)) {
1600
- for (const toolCall of delta.tool_calls) {
1601
- const idx = (_e = toolCall.index) != null ? _e : 0;
1602
- while (toolCalls.length <= idx) {
1603
- toolCalls.push({ name: "", argumentsJson: "" });
1618
+ const choice = (_c = json.choices) == null ? void 0 : _c[0];
1619
+ if (choice) {
1620
+ const delta = (_d = choice.delta) != null ? _d : {};
1621
+ if (delta.content) {
1622
+ paragraph += delta.content;
1623
+ useful = true;
1624
+ }
1625
+ if (delta.reasoning) {
1626
+ reasoning += delta.reasoning;
1627
+ useful = true;
1628
+ }
1629
+ if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) {
1630
+ reasoningDetails.push(...delta.reasoning_details);
1631
+ useful = true;
1632
+ }
1633
+ if (Array.isArray(delta.tool_calls)) {
1634
+ for (const toolCall of delta.tool_calls) {
1635
+ const idx = (_e = toolCall.index) != null ? _e : 0;
1636
+ while (toolCalls.length <= idx) {
1637
+ toolCalls.push({ name: "", argumentsJson: "" });
1638
+ }
1639
+ if (toolCall.id)
1640
+ toolCalls[idx].id = toolCall.id;
1641
+ if ((_f = toolCall.function) == null ? void 0 : _f.name)
1642
+ toolCalls[idx].name += toolCall.function.name;
1643
+ if ((_g = toolCall.function) == null ? void 0 : _g.arguments)
1644
+ toolCalls[idx].argumentsJson += toolCall.function.arguments;
1645
+ useful = true;
1604
1646
  }
1605
- if (toolCall.id) toolCalls[idx].id = toolCall.id;
1606
- if ((_f = toolCall.function) == null ? void 0 : _f.name)
1607
- toolCalls[idx].name += toolCall.function.name;
1608
- if ((_g = toolCall.function) == null ? void 0 : _g.arguments)
1609
- toolCalls[idx].argumentsJson += toolCall.function.arguments;
1647
+ }
1648
+ if (choice.finish_reason) {
1649
+ finishReason = choice.finish_reason;
1610
1650
  useful = true;
1611
1651
  }
1612
1652
  }
1613
- if (choice.finish_reason) {
1614
- finishReason = choice.finish_reason;
1615
- useful = true;
1616
- }
1653
+ if (useful)
1654
+ armStallTimer();
1617
1655
  }
1618
- if (useful) armStallTimer();
1619
1656
  }
1620
- }
1621
- if (sawDone) reader.cancel().catch(() => {
1622
- });
1657
+ if (sawDone)
1658
+ reader.cancel().catch(() => {
1659
+ });
1623
1660
  if (!sawDone && !finishReason) {
1624
1661
  logger_default.error(
1625
1662
  id,
@@ -1658,7 +1695,9 @@ function parseOpenRouterBody(id, data) {
1658
1695
  var _a, _b, _c, _d, _e, _f, _g, _h;
1659
1696
  if (data.error) {
1660
1697
  logger_default.error(id, "OpenRouter error:", data.error);
1661
- throw new Error(`OpenRouter error: ${data.error.message}`);
1698
+ const error2 = new Error(`OpenRouter error: ${data.error.message}`);
1699
+ error2.data = data.error;
1700
+ throw error2;
1662
1701
  }
1663
1702
  const answer = (_b = (_a = data.choices) == null ? void 0 : _a[0]) == null ? void 0 : _b.message;
1664
1703
  if (!answer) {
@@ -1695,22 +1734,81 @@ async function callOpenRouterNonStreaming(id, payload, requestTimeoutMs = OPENRO
1695
1734
  );
1696
1735
  return parseOpenRouterBody(id, response.data);
1697
1736
  }
1737
+ var normalizeProviderKey = (name) => name.split("/")[0].toLowerCase().replace(/[^a-z0-9]/g, "");
1738
+ var MODERATION_RE = /moderat|inappropriate content|content polic|content management|flagged/i;
1739
+ function moderationEvictionSlug(error2, payload) {
1740
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1741
+ const body = (_c = error2 == null ? void 0 : error2.data) != null ? _c : (_b = (_a = error2 == null ? void 0 : error2.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error;
1742
+ if (!body)
1743
+ return null;
1744
+ const message = String((_d = body.message) != null ? _d : "");
1745
+ const isModeration = body.code === 403 || Array.isArray((_e = body.metadata) == null ? void 0 : _e.reasons) || MODERATION_RE.test(message);
1746
+ if (!isModeration)
1747
+ return null;
1748
+ const display = (_h = (_f = body.metadata) == null ? void 0 : _f.provider_name) != null ? _h : (_g = /Upstream error from ([^:]+):/.exec(message)) == null ? void 0 : _g[1];
1749
+ if (!display)
1750
+ return null;
1751
+ const key = normalizeProviderKey(display);
1752
+ const fromOrder = (_j = (_i = payload.provider) == null ? void 0 : _i.order) == null ? void 0 : _j.find(
1753
+ (entry) => normalizeProviderKey(entry) === key
1754
+ );
1755
+ return fromOrder ? fromOrder.split("/")[0] : display.toLowerCase();
1756
+ }
1757
+ var MIN_STREAM_ATTEMPT_MS = 1e4;
1758
+ function streamAttemptBudgetMs(options) {
1759
+ if (options.streamDeadlineAt === void 0)
1760
+ return options.streamTimeoutMs;
1761
+ const remaining = options.streamDeadlineAt - Date.now();
1762
+ if (remaining < MIN_STREAM_ATTEMPT_MS)
1763
+ return null;
1764
+ return Math.min(options.streamTimeoutMs, remaining);
1765
+ }
1698
1766
  async function callOpenRouterWithRetries(id, payload, retries = 5, options, signal) {
1767
+ const evicted = [];
1699
1768
  return withRetries(
1700
1769
  id,
1701
1770
  "OpenRouter",
1702
- () => options.streaming ? callOpenRouterStream(
1703
- id,
1704
- payload,
1705
- options.streamTimeoutMs,
1706
- options.chunkTimeoutMs,
1707
- signal
1708
- ) : callOpenRouterNonStreaming(
1771
+ () => (options.streaming ? (() => {
1772
+ const budget = streamAttemptBudgetMs(options);
1773
+ if (budget === null) {
1774
+ const error2 = new Error(
1775
+ "OpenRouter stream skipped: caller deadline leaves too little time for another attempt"
1776
+ );
1777
+ error2.deadlineExceeded = true;
1778
+ logger_default.error(id, error2.message);
1779
+ throw error2;
1780
+ }
1781
+ return callOpenRouterStream(
1782
+ id,
1783
+ payload,
1784
+ budget,
1785
+ options.chunkTimeoutMs,
1786
+ signal
1787
+ );
1788
+ })() : callOpenRouterNonStreaming(
1709
1789
  id,
1710
1790
  payload,
1711
1791
  options.requestTimeoutMs,
1712
1792
  signal
1713
- ),
1793
+ )).catch((error2) => {
1794
+ var _a, _b, _c, _d;
1795
+ const slug = moderationEvictionSlug(error2, payload);
1796
+ if (slug && !evicted.includes(slug)) {
1797
+ evicted.push(slug);
1798
+ payload.provider = {
1799
+ ...payload.provider,
1800
+ ignore: [...(_b = (_a = payload.provider) == null ? void 0 : _a.ignore) != null ? _b : [], slug],
1801
+ order: (_d = (_c = payload.provider) == null ? void 0 : _c.order) == null ? void 0 : _d.filter(
1802
+ (entry) => normalizeProviderKey(entry) !== normalizeProviderKey(slug)
1803
+ )
1804
+ };
1805
+ logger_default.log(
1806
+ id,
1807
+ `OpenRouter moderation eviction: ignoring provider "${slug}" for remaining attempts`
1808
+ );
1809
+ }
1810
+ throw error2;
1811
+ }),
1714
1812
  { retries, signal }
1715
1813
  );
1716
1814
  }
@@ -1807,6 +1905,7 @@ async function callWithRetries(id, aiPayload, aiConfig, retries = 5, chunkTimeou
1807
1905
  {
1808
1906
  streaming: (_b = aiPayload.streaming) != null ? _b : true,
1809
1907
  streamTimeoutMs: (_c = aiPayload.streamTimeoutMs) != null ? _c : OPENROUTER_STREAM_TIMEOUT_MS,
1908
+ streamDeadlineAt: aiPayload.streamDeadlineAt,
1810
1909
  requestTimeoutMs: (_d = aiPayload.requestTimeoutMs) != null ? _d : OPENROUTER_NONSTREAM_TIMEOUT_MS,
1811
1910
  chunkTimeoutMs
1812
1911
  },
@@ -1817,7 +1916,8 @@ async function callWithRetries(id, aiPayload, aiConfig, retries = 5, chunkTimeou
1817
1916
  (_e = result.provider) != null ? _e : result.provider = provider;
1818
1917
  return result;
1819
1918
  } catch (error2) {
1820
- if ((_f = aiPayload.signal) == null ? void 0 : _f.aborted) throw error2;
1919
+ if ((_f = aiPayload.signal) == null ? void 0 : _f.aborted)
1920
+ throw error2;
1821
1921
  if (aiPayload.fallbackModel) {
1822
1922
  logger_default.error(
1823
1923
  id,
@@ -1848,6 +1948,7 @@ async function callWithRetries(id, aiPayload, aiConfig, retries = 5, chunkTimeou
1848
1948
  GPTModel,
1849
1949
  GeminiModel,
1850
1950
  GroqModel,
1951
+ MIN_STREAM_ATTEMPT_MS,
1851
1952
  OPENROUTER_NONSTREAM_TIMEOUT_MS,
1852
1953
  OPENROUTER_STREAM_TIMEOUT_MS,
1853
1954
  OpenRouterModel,