190proof 1.0.114 → 1.0.116

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
@@ -136,6 +136,16 @@ const payload: GenericPayload = {
136
136
  const response = await callWithRetries("image-example", payload);
137
137
  ```
138
138
 
139
+ How images reach the model depends on the provider. OpenAI, Anthropic, and
140
+ Google get native image blocks. Groq is text-only: images degrade to an inline
141
+ `Image (url)` text reference. OpenRouter sends OpenAI-style `image_url` content
142
+ parts (the remote URL when present, else a `data:` URI) — but if the model has
143
+ no vision-capable endpoints, OpenRouter rejects the request with a
144
+ routing-layer 404, so the retry loop resends the payload with images degraded
145
+ to the same inline text references Groq gets, and remembers the model
146
+ (in-process, until restart) so later calls degrade up front. Messages without
147
+ image attachments serialize identically either way.
148
+
139
149
  ### With System Messages
140
150
 
141
151
  ```typescript
@@ -280,6 +290,7 @@ Optional per-request knobs live on `payload` (`GenericPayload`):
280
290
  - `payload.streamTimeoutMs`: `number` - OpenRouter-only: total wall-clock budget per streaming attempt (default: 600000).
281
291
  - `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
292
  - `payload.thinkingConfig`: `Record<string, unknown>` - Google-only: forwarded verbatim as `generationConfig.thinkingConfig` on the Gemini request — e.g. `{ thinkingBudget: 0 }` to disable thinking, `{ thinkingLevel: "HIGH" }` on models that take a level. Ignored by all other adapters; shapes are model-specific and validated by Google, not the SDK.
293
+ - `payload.reasoningEffort`: `string` - OpenAI-only: forwarded as `reasoning_effort` on the request. Valid values are model-dependent (`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`). Reasoning-by-default models (the gpt-5.6 family) reject function tools on `/chat/completions` with a 400 unless this is explicitly `"none"` — their implicit default is `medium`. Ignored by all other adapters.
283
294
 
284
295
  When a streaming attempt is cut at its **total deadline** and prose has already arrived, the partial answer is returned with `truncated: true` on the response rather than discarded — those tokens were generated and billed, so throwing them away costs money and gives the user nothing. Surface such a reply as incomplete. Salvage never applies to tool-call turns (half-streamed arguments are unparseable JSON), to stalls (the provider died mid-thought), or to caller aborts. When nothing is salvageable, the discard is logged with an approximate token count — aborted attempts never receive OpenRouter's `usage` chunk, so that log line is the only record of the wasted spend.
285
296
 
package/dist/index.d.mts CHANGED
@@ -295,6 +295,14 @@ interface GenericPayload {
295
295
  * adapters. Forwarded as the request body's `provider` field.
296
296
  */
297
297
  provider?: OpenRouterProviderPreferences;
298
+ /**
299
+ * OpenAI-only: forwarded as `reasoning_effort` on the request. Valid values
300
+ * are model-dependent (`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`).
301
+ * Reasoning-by-default models (gpt-5.6 family) 400 on /chat/completions when
302
+ * function tools are present unless this is explicitly `"none"` — their
303
+ * implicit default is `medium`. Ignored by all other adapters.
304
+ */
305
+ reasoningEffort?: string;
298
306
  /**
299
307
  * Per-request HTTP timeout in ms for the underlying provider call (applied
300
308
  * per attempt, not across retries). Honored by all adapters (Anthropic,
@@ -344,6 +352,15 @@ interface GenericPayload {
344
352
  signal?: AbortSignal;
345
353
  }
346
354
 
355
+ /**
356
+ * In-process memory of OpenRouter models that rejected image input (the
357
+ * routing-layer 404 "No endpoints found that support image input"). Payloads
358
+ * for these models degrade image attachments to inline `Image (url)` text
359
+ * references up front — the exact pre-vision serialization — instead of
360
+ * burning a doomed attempt per call. Populated by the retry loop on first
361
+ * rejection; cleared only by process restart (exported so tests can reset it).
362
+ */
363
+ declare const openRouterImageRejectedModels: Set<string>;
347
364
  declare const OPENROUTER_STREAM_TIMEOUT_MS = 600000;
348
365
  declare const OPENROUTER_NONSTREAM_TIMEOUT_MS = 180000;
349
366
  /**
@@ -358,4 +375,4 @@ declare function parseModelString(model: string): {
358
375
  };
359
376
  declare function callWithRetries(id: string | string[], aiPayload: GenericPayload, aiConfig?: OpenAIConfig | AnthropicAIConfig, retries?: number, chunkTimeoutMs?: number): Promise<ParsedResponseMessage>;
360
377
 
361
- 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 };
378
+ 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, openRouterImageRejectedModels, parseModelString };
package/dist/index.d.ts CHANGED
@@ -295,6 +295,14 @@ interface GenericPayload {
295
295
  * adapters. Forwarded as the request body's `provider` field.
296
296
  */
297
297
  provider?: OpenRouterProviderPreferences;
298
+ /**
299
+ * OpenAI-only: forwarded as `reasoning_effort` on the request. Valid values
300
+ * are model-dependent (`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`).
301
+ * Reasoning-by-default models (gpt-5.6 family) 400 on /chat/completions when
302
+ * function tools are present unless this is explicitly `"none"` — their
303
+ * implicit default is `medium`. Ignored by all other adapters.
304
+ */
305
+ reasoningEffort?: string;
298
306
  /**
299
307
  * Per-request HTTP timeout in ms for the underlying provider call (applied
300
308
  * per attempt, not across retries). Honored by all adapters (Anthropic,
@@ -344,6 +352,15 @@ interface GenericPayload {
344
352
  signal?: AbortSignal;
345
353
  }
346
354
 
355
+ /**
356
+ * In-process memory of OpenRouter models that rejected image input (the
357
+ * routing-layer 404 "No endpoints found that support image input"). Payloads
358
+ * for these models degrade image attachments to inline `Image (url)` text
359
+ * references up front — the exact pre-vision serialization — instead of
360
+ * burning a doomed attempt per call. Populated by the retry loop on first
361
+ * rejection; cleared only by process restart (exported so tests can reset it).
362
+ */
363
+ declare const openRouterImageRejectedModels: Set<string>;
347
364
  declare const OPENROUTER_STREAM_TIMEOUT_MS = 600000;
348
365
  declare const OPENROUTER_NONSTREAM_TIMEOUT_MS = 180000;
349
366
  /**
@@ -358,4 +375,4 @@ declare function parseModelString(model: string): {
358
375
  };
359
376
  declare function callWithRetries(id: string | string[], aiPayload: GenericPayload, aiConfig?: OpenAIConfig | AnthropicAIConfig, retries?: number, chunkTimeoutMs?: number): Promise<ParsedResponseMessage>;
360
377
 
361
- 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 };
378
+ 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, openRouterImageRejectedModels, parseModelString };
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  OPENROUTER_STREAM_TIMEOUT_MS: () => OPENROUTER_STREAM_TIMEOUT_MS,
40
40
  OpenRouterModel: () => OpenRouterModel,
41
41
  callWithRetries: () => callWithRetries,
42
+ openRouterImageRejectedModels: () => openRouterImageRejectedModels,
42
43
  parseModelString: () => parseModelString
43
44
  });
44
45
  module.exports = __toCommonJS(index_exports);
@@ -346,6 +347,7 @@ async function prepareOpenAIPayload(identifier, payload) {
346
347
  const preparedPayload = {
347
348
  model: payload.model,
348
349
  messages: [],
350
+ reasoning_effort: payload.reasoningEffort,
349
351
  tools: (_a = payload.functions) == null ? void 0 : _a.map((fn) => ({
350
352
  type: "function",
351
353
  function: fn
@@ -416,7 +418,7 @@ async function prepareOpenAIPayload(identifier, payload) {
416
418
  return preparedPayload;
417
419
  }
418
420
  async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs, requestTimeoutMs = 12e4, signal) {
419
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
421
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
420
422
  const functionNames = openAiPayload.tools ? new Set(openAiPayload.tools.map((fn) => fn.function.name)) : null;
421
423
  const { endpoint, headers } = buildOpenAIRequestConfig(
422
424
  id,
@@ -434,7 +436,15 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
434
436
  const response = await fetch(endpoint, {
435
437
  method: "POST",
436
438
  headers,
437
- body: JSON.stringify({ ...openAiPayload, stream: true }),
439
+ // include_usage: OpenAI only reports token usage on streams when asked,
440
+ // via a final choices-less chunk — without it every streamed call is
441
+ // invisible to usage accounting. Skipped for Azure, where older
442
+ // api-versions reject stream_options.
443
+ body: JSON.stringify({
444
+ ...openAiPayload,
445
+ stream: true,
446
+ ...(openAiConfig == null ? void 0 : openAiConfig.service) !== "azure" ? { stream_options: { include_usage: true } } : {}
447
+ }),
438
448
  // Merge (don't overwrite) the internal timeout controller with the caller's
439
449
  // cancellation signal so both an internal timeout and an external abort stop
440
450
  // the stream.
@@ -445,6 +455,7 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
445
455
  }
446
456
  let paragraph = "";
447
457
  let reasoning = "";
458
+ let streamUsage = null;
448
459
  const toolCallAccumulators = [];
449
460
  const reader = response.body.getReader();
450
461
  let partialChunk = "";
@@ -475,13 +486,15 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
475
486
  if (!jsonString) continue;
476
487
  if (jsonString.includes("[DONE]")) {
477
488
  clearTimeout(overallTimeout);
478
- return parseStreamedResponse(
489
+ const parsed = parseStreamedResponse(
479
490
  id,
480
491
  paragraph,
481
492
  toolCallAccumulators,
482
493
  functionNames,
483
494
  reasoning
484
495
  );
496
+ parsed.usage = streamUsage;
497
+ return parsed;
485
498
  }
486
499
  let json;
487
500
  try {
@@ -491,6 +504,15 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
491
504
  continue;
492
505
  }
493
506
  if (!((_a = json.choices) == null ? void 0 : _a.length)) {
507
+ if (json.usage) {
508
+ streamUsage = {
509
+ prompt_tokens: json.usage.prompt_tokens,
510
+ completion_tokens: json.usage.completion_tokens,
511
+ total_tokens: json.usage.total_tokens,
512
+ cached_tokens: (_c = (_b = json.usage.prompt_tokens_details) == null ? void 0 : _b.cached_tokens) != null ? _c : 0
513
+ };
514
+ continue;
515
+ }
494
516
  if (json.error) {
495
517
  logger_default.error(id, "Stream error from OpenAI:", json.error);
496
518
  const error2 = new Error("Stream error: OpenAI error");
@@ -503,23 +525,23 @@ async function callOpenAIStream(id, openAiPayload, openAiConfig, chunkTimeoutMs,
503
525
  }
504
526
  continue;
505
527
  }
506
- const toolCalls = (_c = (_b = json.choices[0]) == null ? void 0 : _b.delta) == null ? void 0 : _c.tool_calls;
528
+ const toolCalls = (_e = (_d = json.choices[0]) == null ? void 0 : _d.delta) == null ? void 0 : _e.tool_calls;
507
529
  if (toolCalls) {
508
530
  for (const toolCall of toolCalls) {
509
- const idx = (_d = toolCall.index) != null ? _d : 0;
531
+ const idx = (_f = toolCall.index) != null ? _f : 0;
510
532
  while (toolCallAccumulators.length <= idx) {
511
533
  toolCallAccumulators.push({ name: "", arguments: "" });
512
534
  }
513
535
  if (toolCall.id) toolCallAccumulators[idx].id = toolCall.id;
514
- if ((_e = toolCall.function) == null ? void 0 : _e.name)
536
+ if ((_g = toolCall.function) == null ? void 0 : _g.name)
515
537
  toolCallAccumulators[idx].name += toolCall.function.name;
516
- if ((_f = toolCall.function) == null ? void 0 : _f.arguments)
538
+ if ((_h = toolCall.function) == null ? void 0 : _h.arguments)
517
539
  toolCallAccumulators[idx].arguments += toolCall.function.arguments;
518
540
  }
519
541
  }
520
- const text = (_h = (_g = json.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content;
542
+ const text = (_j = (_i = json.choices[0]) == null ? void 0 : _i.delta) == null ? void 0 : _j.content;
521
543
  if (text) paragraph += text;
522
- const reasoningDelta = (_j = (_i = json.choices[0]) == null ? void 0 : _i.delta) == null ? void 0 : _j.reasoning;
544
+ const reasoningDelta = (_l = (_k = json.choices[0]) == null ? void 0 : _k.delta) == null ? void 0 : _l.reasoning;
523
545
  if (reasoningDelta) reasoning += reasoningDelta;
524
546
  }
525
547
  }
@@ -1232,7 +1254,7 @@ async function callGoogleAIWithRetries(id, payload, retries = 5, requestTimeoutM
1232
1254
  function normalizeMessageContent(content) {
1233
1255
  return Array.isArray(content) ? content.map((c) => c.type === "text" ? c.text : `[${c.type}]`).join("\n") : content;
1234
1256
  }
1235
- function prepareOpenAICompatMessages(messages) {
1257
+ function prepareOpenAICompatMessages(messages, opts = {}) {
1236
1258
  var _a;
1237
1259
  const out = [];
1238
1260
  for (const message of messages) {
@@ -1254,6 +1276,22 @@ function prepareOpenAICompatMessages(messages) {
1254
1276
  role: message.role,
1255
1277
  content
1256
1278
  };
1279
+ if (opts.imageParts) {
1280
+ const imageBlocks = (message.files || []).filter(
1281
+ (file) => ALLOWED_IMAGE_MIME_TYPES.includes(file.mimeType) && (file.url || file.data)
1282
+ ).map((file) => ({
1283
+ type: "image_url",
1284
+ image_url: {
1285
+ url: file.url || `data:${file.mimeType};base64,${file.data}`
1286
+ }
1287
+ }));
1288
+ if (imageBlocks.length) {
1289
+ outMessage.content = [
1290
+ ...content ? [{ type: "text", text: content }] : [],
1291
+ ...imageBlocks
1292
+ ];
1293
+ }
1294
+ }
1257
1295
  if ((_a = message.functionCalls) == null ? void 0 : _a.length) {
1258
1296
  outMessage.tool_calls = message.functionCalls.map((fc, i) => {
1259
1297
  var _a2;
@@ -1266,7 +1304,8 @@ function prepareOpenAICompatMessages(messages) {
1266
1304
  }
1267
1305
  };
1268
1306
  });
1269
- if (!content) outMessage.content = null;
1307
+ if (!content && !Array.isArray(outMessage.content))
1308
+ outMessage.content = null;
1270
1309
  }
1271
1310
  if (message.reasoning) outMessage.reasoning = message.reasoning;
1272
1311
  const reasoningDetails = filterOpenAICompatReasoningDetails(
@@ -1360,11 +1399,14 @@ async function callGroqWithRetries(id, payload, retries = 5, requestTimeoutMs =
1360
1399
  signal
1361
1400
  });
1362
1401
  }
1402
+ var openRouterImageRejectedModels = /* @__PURE__ */ new Set();
1363
1403
  function prepareOpenRouterPayload(payload) {
1364
1404
  var _a;
1365
1405
  return {
1366
1406
  model: payload.model,
1367
- messages: prepareOpenAICompatMessages(payload.messages),
1407
+ messages: prepareOpenAICompatMessages(payload.messages, {
1408
+ imageParts: !openRouterImageRejectedModels.has(String(payload.model))
1409
+ }),
1368
1410
  tools: (_a = payload.functions) == null ? void 0 : _a.map((fn) => ({
1369
1411
  type: "function",
1370
1412
  function: fn
@@ -1754,6 +1796,11 @@ function moderationEvictionSlug(error2, payload) {
1754
1796
  }
1755
1797
  var MIN_STREAM_ATTEMPT_MS = 1e4;
1756
1798
  var estimateTokens = (text) => Math.round(text.length / 4);
1799
+ function isImageInputRejection(error2) {
1800
+ var _a, _b, _c, _d;
1801
+ 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;
1802
+ return /no endpoints found.*image input/i.test(String((_d = body == null ? void 0 : body.message) != null ? _d : ""));
1803
+ }
1757
1804
  function streamAttemptBudgetMs(options) {
1758
1805
  if (options.streamDeadlineAt === void 0) return options.streamTimeoutMs;
1759
1806
  const remaining = options.streamDeadlineAt - Date.now();
@@ -1804,6 +1851,16 @@ async function callOpenRouterWithRetries(id, payload, retries = 5, options, sign
1804
1851
  `OpenRouter moderation eviction: ignoring provider "${slug}" for remaining attempts`
1805
1852
  );
1806
1853
  }
1854
+ if (isImageInputRejection(error2) && options.genericMessages) {
1855
+ openRouterImageRejectedModels.add(String(payload.model));
1856
+ payload.messages = prepareOpenAICompatMessages(
1857
+ options.genericMessages
1858
+ );
1859
+ logger_default.log(
1860
+ id,
1861
+ `OpenRouter: ${payload.model} has no image-capable endpoints; retrying with images as text refs`
1862
+ );
1863
+ }
1807
1864
  throw error2;
1808
1865
  }),
1809
1866
  { retries, signal }
@@ -1904,7 +1961,8 @@ async function callWithRetries(id, aiPayload, aiConfig, retries = 5, chunkTimeou
1904
1961
  streamTimeoutMs: (_c = aiPayload.streamTimeoutMs) != null ? _c : OPENROUTER_STREAM_TIMEOUT_MS,
1905
1962
  streamDeadlineAt: aiPayload.streamDeadlineAt,
1906
1963
  requestTimeoutMs: (_d = aiPayload.requestTimeoutMs) != null ? _d : OPENROUTER_NONSTREAM_TIMEOUT_MS,
1907
- chunkTimeoutMs
1964
+ chunkTimeoutMs,
1965
+ genericMessages: routingPayload.messages
1908
1966
  },
1909
1967
  signal
1910
1968
  );
@@ -1949,6 +2007,7 @@ async function callWithRetries(id, aiPayload, aiConfig, retries = 5, chunkTimeou
1949
2007
  OPENROUTER_STREAM_TIMEOUT_MS,
1950
2008
  OpenRouterModel,
1951
2009
  callWithRetries,
2010
+ openRouterImageRejectedModels,
1952
2011
  parseModelString
1953
2012
  });
1954
2013
  //# sourceMappingURL=index.js.map