@prestyj/ai 4.6.4 → 4.11.4

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/dist/index.d.cts CHANGED
@@ -255,8 +255,9 @@ declare class StreamResult implements AsyncIterable<StreamEvent> {
255
255
  private resolveResponse;
256
256
  private rejectResponse;
257
257
  private resolveWait;
258
- constructor(generator: AsyncGenerator<StreamEvent, StreamResponse>);
258
+ constructor(generator: AsyncGenerator<StreamEvent, StreamResponse>, signal?: AbortSignal);
259
259
  private pump;
260
+ private _nextWithAbort;
260
261
  [Symbol.asyncIterator](): AsyncIterator<StreamEvent>;
261
262
  then<TResult1 = StreamResponse, TResult2 = never>(onfulfilled?: ((value: StreamResponse) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
262
263
  }
@@ -414,6 +415,19 @@ declare function formatError(err: unknown): FormattedError;
414
415
  */
415
416
  declare function formatErrorForDisplay(err: unknown): string;
416
417
 
418
+ /**
419
+ * Inspect a raw provider error message and tag it with a clearer, actionable
420
+ * prefix so a worker orchestrator can route on intent instead of regexing JSON.
421
+ * Preserves the original message verbatim after the prefix — helpful for
422
+ * debugging.
423
+ *
424
+ * Order matters: context-overflow is checked first because some providers wrap
425
+ * overflow errors in HTTP 429 envelopes; we want the structural meaning, not
426
+ * the transport status. Billing comes before auth/rate-limit because "402
427
+ * Payment Required" must not be mis-routed as a rate-limit retry.
428
+ */
429
+ declare function classifyProviderError(message: string): string;
430
+
417
431
  /**
418
432
  * Provider-level diagnostic hook. Mirrors the pattern used by gg-agent's
419
433
  * setStreamDiagnostic — the host app wires a callback (typically writing to
@@ -506,4 +520,4 @@ interface PalsuProviderConfig {
506
520
  */
507
521
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
508
522
 
509
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
523
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, classifyProviderError, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
package/dist/index.d.ts CHANGED
@@ -255,8 +255,9 @@ declare class StreamResult implements AsyncIterable<StreamEvent> {
255
255
  private resolveResponse;
256
256
  private rejectResponse;
257
257
  private resolveWait;
258
- constructor(generator: AsyncGenerator<StreamEvent, StreamResponse>);
258
+ constructor(generator: AsyncGenerator<StreamEvent, StreamResponse>, signal?: AbortSignal);
259
259
  private pump;
260
+ private _nextWithAbort;
260
261
  [Symbol.asyncIterator](): AsyncIterator<StreamEvent>;
261
262
  then<TResult1 = StreamResponse, TResult2 = never>(onfulfilled?: ((value: StreamResponse) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
262
263
  }
@@ -414,6 +415,19 @@ declare function formatError(err: unknown): FormattedError;
414
415
  */
415
416
  declare function formatErrorForDisplay(err: unknown): string;
416
417
 
418
+ /**
419
+ * Inspect a raw provider error message and tag it with a clearer, actionable
420
+ * prefix so a worker orchestrator can route on intent instead of regexing JSON.
421
+ * Preserves the original message verbatim after the prefix — helpful for
422
+ * debugging.
423
+ *
424
+ * Order matters: context-overflow is checked first because some providers wrap
425
+ * overflow errors in HTTP 429 envelopes; we want the structural meaning, not
426
+ * the transport status. Billing comes before auth/rate-limit because "402
427
+ * Payment Required" must not be mis-routed as a rate-limit retry.
428
+ */
429
+ declare function classifyProviderError(message: string): string;
430
+
417
431
  /**
418
432
  * Provider-level diagnostic hook. Mirrors the pattern used by gg-agent's
419
433
  * setStreamDiagnostic — the host app wires a callback (typically writing to
@@ -506,4 +520,4 @@ interface PalsuProviderConfig {
506
520
  */
507
521
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
508
522
 
509
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
523
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, classifyProviderError, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
package/dist/index.js CHANGED
@@ -84,10 +84,25 @@ function formatResetTime(resetsAt) {
84
84
  minute: "2-digit"
85
85
  });
86
86
  }
87
+ function isMythosAccessError(message) {
88
+ const lower = message.toLowerCase();
89
+ return lower.includes("mythos") && (lower.includes("not_found") || lower.includes("not found") || lower.includes("no access"));
90
+ }
87
91
  function formatError(err) {
88
92
  if (err instanceof ProviderError) {
89
93
  const name = providerDisplayName(err.provider);
90
94
  const cleanMessage = cleanProviderMessage(err.message);
95
+ if (isMythosAccessError(cleanMessage)) {
96
+ return {
97
+ headline: "Claude Mythos 5 is invitation-only.",
98
+ source: "provider",
99
+ message: "Your Anthropic account isn't approved for Project Glasswing, so the API reports the model as not found.",
100
+ provider: err.provider,
101
+ statusCode: err.statusCode,
102
+ ...err.requestId ? { requestId: err.requestId } : {},
103
+ guidance: "Request access via your Anthropic account team (see platform.claude.com/docs/en/about-claude/models/overview), or switch to claude-fable-5 with /model \u2014 same underlying model, generally available."
104
+ };
105
+ }
91
106
  if (isUsageLimitError(err)) {
92
107
  const resetClause = err.resetsAt ? ` It resets at ${formatResetTime(err.resetsAt)}.` : "";
93
108
  return {
@@ -274,21 +289,21 @@ var StreamResult = class {
274
289
  resolveResponse;
275
290
  rejectResponse;
276
291
  resolveWait = null;
277
- constructor(generator) {
292
+ constructor(generator, signal) {
278
293
  this.response = new Promise((resolve, reject) => {
279
294
  this.resolveResponse = resolve;
280
295
  this.rejectResponse = reject;
281
296
  });
282
- this.pump(generator);
297
+ this.pump(generator, signal);
283
298
  }
284
- async pump(generator) {
299
+ async pump(generator, signal) {
285
300
  try {
286
- let next = await generator.next();
301
+ let next = await this._nextWithAbort(generator, signal);
287
302
  while (!next.done) {
288
303
  this.buffer.push(next.value);
289
304
  this.resolveWait?.();
290
305
  this.resolveWait = null;
291
- next = await generator.next();
306
+ next = await this._nextWithAbort(generator, signal);
292
307
  }
293
308
  this.done = true;
294
309
  this.resolveResponse(next.value);
@@ -303,6 +318,28 @@ var StreamResult = class {
303
318
  this.resolveWait = null;
304
319
  }
305
320
  }
321
+ async _nextWithAbort(generator, signal) {
322
+ if (!signal) {
323
+ return generator.next();
324
+ }
325
+ if (signal.aborted) {
326
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
327
+ }
328
+ let onAbort;
329
+ const abortPromise = new Promise((_, reject) => {
330
+ onAbort = () => {
331
+ generator.return?.(void 0).catch(() => {
332
+ });
333
+ reject(new DOMException("Aborted", "AbortError"));
334
+ };
335
+ signal.addEventListener("abort", onAbort, { once: true });
336
+ });
337
+ try {
338
+ return await Promise.race([generator.next(), abortPromise]);
339
+ } finally {
340
+ if (onAbort) signal.removeEventListener("abort", onAbort);
341
+ }
342
+ }
306
343
  async *[Symbol.asyncIterator]() {
307
344
  let index = 0;
308
345
  while (true) {
@@ -399,6 +436,29 @@ function isRawThinking(part) {
399
436
  const t = part.data.type;
400
437
  return t === "thinking" || t === "redacted_thinking";
401
438
  }
439
+ var ANTHROPIC_INPUT_BLOCK_TYPES = /* @__PURE__ */ new Set([
440
+ "bash_code_execution_tool_result",
441
+ "code_execution_tool_result",
442
+ "connector_text",
443
+ "container_upload",
444
+ "document",
445
+ "image",
446
+ "mid_conv_system",
447
+ "redacted_thinking",
448
+ "search_result",
449
+ "server_tool_use",
450
+ "text",
451
+ "text_editor_code_execution_tool_result",
452
+ "thinking",
453
+ "tool_result",
454
+ "tool_search_tool_result",
455
+ "tool_use",
456
+ "web_fetch_tool_result",
457
+ "web_search_tool_result"
458
+ ]);
459
+ function isAnthropicCompatibleRaw(part) {
460
+ return ANTHROPIC_INPUT_BLOCK_TYPES.has(part.data.type);
461
+ }
402
462
  function isPositionSensitiveThinking(part) {
403
463
  if (part.type === "thinking") return hasValidThinkingSignature(part);
404
464
  return isRawThinking(part);
@@ -425,7 +485,8 @@ function toAnthropicAssistantPart(part, idMap) {
425
485
  };
426
486
  if (part.type === "server_tool_result")
427
487
  return part.data;
428
- if (part.type === "raw") return part.data;
488
+ if (part.type === "raw")
489
+ return isAnthropicCompatibleRaw(part) ? part.data : null;
429
490
  return null;
430
491
  }
431
492
  function toAnthropicAssistantContent(content, preserveThinking, idMap) {
@@ -684,12 +745,12 @@ function toAnthropicToolChoice(choice) {
684
745
  return { type: "tool", name: choice.name };
685
746
  }
686
747
  function isAdaptiveThinkingModel(model) {
687
- return /fable-5|opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6/.test(model);
748
+ return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6|fable-5|mythos-5/.test(model);
688
749
  }
689
750
  function toAnthropicThinking(level, maxTokens, model) {
690
751
  if (isAdaptiveThinkingModel(model)) {
691
752
  let effort = level;
692
- if (effort === "xhigh" && !/opus-4-8|opus-4-7|fable-5/.test(model)) {
753
+ if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
693
754
  effort = "high";
694
755
  }
695
756
  return {
@@ -698,16 +759,17 @@ function toAnthropicThinking(level, maxTokens, model) {
698
759
  outputConfig: { effort }
699
760
  };
700
761
  }
762
+ const VISIBLE_FLOOR = 1024;
701
763
  const effectiveLevel = level === "xhigh" || level === "max" ? "high" : level;
702
764
  const budgetMap = {
703
- low: Math.max(1024, Math.floor(maxTokens * 0.25)),
704
- medium: Math.max(2048, Math.floor(maxTokens * 0.5)),
705
- high: Math.max(4096, maxTokens)
765
+ low: Math.max(1024, Math.floor(maxTokens * 0.2)),
766
+ medium: Math.max(2048, Math.floor(maxTokens * 0.45)),
767
+ high: Math.max(4096, Math.floor(maxTokens * 0.8))
706
768
  };
707
- const budget = budgetMap[effectiveLevel];
769
+ const budget = Math.max(0, Math.min(budgetMap[effectiveLevel], maxTokens - VISIBLE_FLOOR));
708
770
  return {
709
771
  thinking: { type: "enabled", budget_tokens: budget },
710
- maxTokens: maxTokens + budget
772
+ maxTokens
711
773
  };
712
774
  }
713
775
  function remapToolCallId(id, idMap) {
@@ -919,10 +981,10 @@ function createClient(options) {
919
981
  ...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
920
982
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
921
983
  ...options.fetch ? { fetch: options.fetch } : {},
922
- // Allow SDK retries for connection-level failures (socket hang up, 500s,
923
- // connection refused). Our stall detection handles abort-initiated retries
924
- // separately SDK retries only fire on genuine transport errors.
925
- maxRetries: 2,
984
+ // Disable SDK retries the agent loop has its own stall/overload retry
985
+ // logic that surfaces errors properly. SDK retries on 429s can cause
986
+ // multi-minute hangs when the provider stops responding mid-retry.
987
+ maxRetries: 0,
926
988
  ...isOAuth ? {
927
989
  defaultHeaders: {
928
990
  // Anthropic's OAuth edge validates the claude-cli version. Callers
@@ -935,7 +997,7 @@ function createClient(options) {
935
997
  });
936
998
  }
937
999
  function streamAnthropic(options) {
938
- return new StreamResult(runStream(options));
1000
+ return new StreamResult(runStream(options), options.signal);
939
1001
  }
940
1002
  async function* runStream(options) {
941
1003
  const client = createClient(options);
@@ -1030,7 +1092,6 @@ async function* runStream(options) {
1030
1092
  throw toError(err);
1031
1093
  }
1032
1094
  }
1033
- const stream2 = client.messages.stream(params, requestOptions);
1034
1095
  const contentParts = [];
1035
1096
  const blocks = /* @__PURE__ */ new Map();
1036
1097
  let inputTokens = 0;
@@ -1039,8 +1100,14 @@ async function* runStream(options) {
1039
1100
  let cacheWrite;
1040
1101
  let stopReason = null;
1041
1102
  const keepalive = { type: "keepalive" };
1103
+ let receivedAnyEvent = false;
1042
1104
  try {
1105
+ const stream2 = await client.messages.create(
1106
+ params,
1107
+ requestOptions
1108
+ );
1043
1109
  for await (const event of stream2) {
1110
+ receivedAnyEvent = true;
1044
1111
  switch (event.type) {
1045
1112
  case "message_start": {
1046
1113
  const usage = event.message.usage;
@@ -1077,7 +1144,7 @@ async function* runStream(options) {
1077
1144
  accum.toolId = block.id;
1078
1145
  accum.toolName = block.name;
1079
1146
  accum.input = block.input;
1080
- } else if (block.type === "redacted_thinking") {
1147
+ } else if (block.type !== "text" && block.type !== "thinking") {
1081
1148
  accum.raw = block;
1082
1149
  }
1083
1150
  blocks.set(idx, accum);
@@ -1174,8 +1241,7 @@ async function* runStream(options) {
1174
1241
  contentParts.push({ type: "raw", data: accum.raw });
1175
1242
  yield keepalive;
1176
1243
  } else {
1177
- const msg = stream2.currentMessage;
1178
- const rawBlock = msg?.content[event.index];
1244
+ const rawBlock = accum.raw;
1179
1245
  if (rawBlock) {
1180
1246
  const blockType = rawBlock.type;
1181
1247
  if (blockType === "web_search_tool_result") {
@@ -1221,6 +1287,11 @@ async function* runStream(options) {
1221
1287
  } catch (err) {
1222
1288
  throw toError(err);
1223
1289
  }
1290
+ if (!receivedAnyEvent) {
1291
+ throw new ProviderError("anthropic", "Stream ended without producing any events.", {
1292
+ statusCode: 504
1293
+ });
1294
+ }
1224
1295
  const normalizedStop = normalizeAnthropicStopReason(stopReason);
1225
1296
  const response = {
1226
1297
  message: {
@@ -1512,7 +1583,7 @@ function createClient2(options) {
1512
1583
  });
1513
1584
  }
1514
1585
  function streamOpenAI(options) {
1515
- return new StreamResult(runStream2(options));
1586
+ return new StreamResult(runStream2(options), options.signal);
1516
1587
  }
1517
1588
  async function* runStream2(options) {
1518
1589
  const providerName = options.provider ?? "openai";
@@ -1604,51 +1675,62 @@ async function* runStream2(options) {
1604
1675
  let outputTokens = 0;
1605
1676
  let cacheRead = 0;
1606
1677
  let finishReason = null;
1607
- for await (const chunk of stream2) {
1608
- const choice = chunk.choices?.[0];
1609
- if (chunk.usage) {
1610
- ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1611
- }
1612
- if (!choice) continue;
1613
- if (choice.finish_reason) {
1614
- finishReason = choice.finish_reason;
1615
- }
1616
- const delta = choice.delta;
1617
- const reasoningContent = delta.reasoning_content;
1618
- if (typeof reasoningContent === "string" && reasoningContent) {
1619
- thinkingAccum += reasoningContent;
1620
- if (options.thinking) {
1621
- yield { type: "thinking_delta", text: reasoningContent };
1678
+ let receivedAnyChunk = false;
1679
+ try {
1680
+ for await (const chunk of stream2) {
1681
+ receivedAnyChunk = true;
1682
+ const choice = chunk.choices?.[0];
1683
+ if (chunk.usage) {
1684
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1622
1685
  }
1623
- }
1624
- if (delta.content) {
1625
- textAccum += delta.content;
1626
- yield { type: "text_delta", text: delta.content };
1627
- }
1628
- if (delta.tool_calls) {
1629
- for (const tc of delta.tool_calls) {
1630
- let accum = toolCallAccum.get(tc.index);
1631
- if (!accum) {
1632
- accum = {
1633
- id: tc.id ?? "",
1634
- name: tc.function?.name ?? "",
1635
- argsJson: ""
1636
- };
1637
- toolCallAccum.set(tc.index, accum);
1686
+ if (!choice) continue;
1687
+ if (choice.finish_reason) {
1688
+ finishReason = choice.finish_reason;
1689
+ }
1690
+ const delta = choice.delta;
1691
+ const reasoningContent = delta.reasoning_content;
1692
+ if (typeof reasoningContent === "string" && reasoningContent) {
1693
+ thinkingAccum += reasoningContent;
1694
+ if (options.thinking) {
1695
+ yield { type: "thinking_delta", text: reasoningContent };
1638
1696
  }
1639
- if (tc.id) accum.id = tc.id;
1640
- if (tc.function?.name) accum.name = tc.function.name;
1641
- if (tc.function?.arguments) {
1642
- accum.argsJson += tc.function.arguments;
1643
- yield {
1644
- type: "toolcall_delta",
1645
- id: accum.id,
1646
- name: accum.name,
1647
- argsJson: tc.function.arguments
1648
- };
1697
+ }
1698
+ if (delta.content) {
1699
+ textAccum += delta.content;
1700
+ yield { type: "text_delta", text: delta.content };
1701
+ }
1702
+ if (delta.tool_calls) {
1703
+ for (const tc of delta.tool_calls) {
1704
+ let accum = toolCallAccum.get(tc.index);
1705
+ if (!accum) {
1706
+ accum = {
1707
+ id: tc.id ?? "",
1708
+ name: tc.function?.name ?? "",
1709
+ argsJson: ""
1710
+ };
1711
+ toolCallAccum.set(tc.index, accum);
1712
+ }
1713
+ if (tc.id) accum.id = tc.id;
1714
+ if (tc.function?.name) accum.name = tc.function.name;
1715
+ if (tc.function?.arguments) {
1716
+ accum.argsJson += tc.function.arguments;
1717
+ yield {
1718
+ type: "toolcall_delta",
1719
+ id: accum.id,
1720
+ name: accum.name,
1721
+ argsJson: tc.function.arguments
1722
+ };
1723
+ }
1649
1724
  }
1650
1725
  }
1651
1726
  }
1727
+ } catch (err) {
1728
+ throw toError2(err, providerName);
1729
+ }
1730
+ if (!receivedAnyChunk) {
1731
+ throw new ProviderError(providerName, "Stream ended without producing any chunks.", {
1732
+ statusCode: 504
1733
+ });
1652
1734
  }
1653
1735
  if (thinkingAccum) {
1654
1736
  contentParts.push({ type: "thinking", text: thinkingAccum });
@@ -1891,7 +1973,7 @@ function isVisibleOutputItem(itemType) {
1891
1973
  return itemType === "message";
1892
1974
  }
1893
1975
  function streamOpenAICodex(options) {
1894
- return new StreamResult(runStream3(options));
1976
+ return new StreamResult(runStream3(options), options.signal);
1895
1977
  }
1896
1978
  async function* runStream3(options) {
1897
1979
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -2221,10 +2303,16 @@ async function* parseSSE(body) {
2221
2303
  }
2222
2304
  }
2223
2305
  function remapCodexId(id, idMap) {
2224
- if (id.startsWith("fc_") || id.startsWith("fc-")) return id;
2225
2306
  const existing = idMap.get(id);
2226
2307
  if (existing) return existing;
2227
- const mapped = `fc_${id.replace(/^toolu_/, "")}`;
2308
+ const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
2309
+ const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
2310
+ let mapped = sanitized;
2311
+ let suffix = 2;
2312
+ const used = new Set(idMap.values());
2313
+ while (used.has(mapped)) {
2314
+ mapped = `${sanitized}_${suffix++}`;
2315
+ }
2228
2316
  idMap.set(id, mapped);
2229
2317
  return mapped;
2230
2318
  }
@@ -2747,7 +2835,7 @@ async function fetchCodeAssistWithRetry(plan, options) {
2747
2835
  throw lastError ?? new ProviderError("gemini", "Gemini Code Assist request failed.");
2748
2836
  }
2749
2837
  function streamGemini(options) {
2750
- return new StreamResult(runStream4(options));
2838
+ return new StreamResult(runStream4(options), options.signal);
2751
2839
  }
2752
2840
  async function* runStream4(options) {
2753
2841
  const useStreaming = options.streaming !== false;
@@ -2967,6 +3055,96 @@ function messagesContainVideo(messages) {
2967
3055
  return false;
2968
3056
  }
2969
3057
 
3058
+ // src/error-classification.ts
3059
+ var CONTEXT_OVERFLOW_PATTERNS = [
3060
+ /context_length_exceeded/i,
3061
+ /context length exceeded/i,
3062
+ /context window/i,
3063
+ // OpenAI Codex / Responses
3064
+ /maximum context length/i,
3065
+ // OpenAI / OpenRouter / Mistral
3066
+ /prompt is too long/i,
3067
+ // Anthropic
3068
+ /request_too_large/i,
3069
+ // Anthropic HTTP 413
3070
+ /input is too long/i,
3071
+ // Bedrock
3072
+ /input token count.*exceeds the maximum/i,
3073
+ // Gemini
3074
+ /maximum prompt length/i,
3075
+ // xAI / Grok
3076
+ /reduce the length of the messages/i,
3077
+ // Groq
3078
+ /too large for model/i,
3079
+ // Mistral
3080
+ /token limit/i
3081
+ // generic
3082
+ ];
3083
+ var RATE_LIMIT_PATTERNS = [
3084
+ /rate[ _-]?limit/i,
3085
+ /\b429\b/,
3086
+ /too many requests/i,
3087
+ /tokens per minute/i,
3088
+ /requests per minute/i
3089
+ ];
3090
+ var PROVIDER_TRANSIENT_PATTERNS = [
3091
+ /\b5\d\d\b/,
3092
+ /api_error/i,
3093
+ /server_error/i,
3094
+ /internal server error/i,
3095
+ /bad gateway/i,
3096
+ /service unavailable/i,
3097
+ /gateway timeout/i,
3098
+ /overloaded/i,
3099
+ /\b529\b/
3100
+ ];
3101
+ var BILLING_PATTERNS = [
3102
+ /payment required/i,
3103
+ /\b402\b/,
3104
+ /quota_exceeded/i,
3105
+ // underscore variant not in isHardBillingMessage
3106
+ /credit balance/i
3107
+ ];
3108
+ var AUTH_PATTERNS = [
3109
+ /invalid[ _]api[ _]key/i,
3110
+ /unauthorized/i,
3111
+ /\b401\b/,
3112
+ /authentication[ _]failed/i,
3113
+ /please run \/login/i
3114
+ // Anthropic Claude Code-style hint
3115
+ ];
3116
+ function matchesAny(message, patterns) {
3117
+ return patterns.some((p) => p.test(message));
3118
+ }
3119
+ function classifyProviderError(message) {
3120
+ if (matchesAny(message, CONTEXT_OVERFLOW_PATTERNS)) {
3121
+ return `[context_overflow] Worker context window exceeded \u2014 the conversation is too large to continue. Recovery: call reset_worker(project) to wipe history, then re-prompt with the task. Re-prompting WITHOUT reset will fail the same way.
3122
+
3123
+ Original: ${message}`;
3124
+ }
3125
+ if (isHardBillingMessage(message) || matchesAny(message, BILLING_PATTERNS)) {
3126
+ return `[billing] Provider billing/quota issue. Recovery: surface to the user \u2014 they need to top up or switch providers. Do NOT retry.
3127
+
3128
+ Original: ${message}`;
3129
+ }
3130
+ if (matchesAny(message, AUTH_PATTERNS)) {
3131
+ return `[auth] Provider authentication failed. Recovery: surface to the user \u2014 they need to re-login. Do NOT retry.
3132
+
3133
+ Original: ${message}`;
3134
+ }
3135
+ if (matchesAny(message, RATE_LIMIT_PATTERNS)) {
3136
+ return `[rate_limited] Provider rate limit hit. Recovery: wait ~30s, then re-prompt the same worker (no reset needed).
3137
+
3138
+ Original: ${message}`;
3139
+ }
3140
+ if (matchesAny(message, PROVIDER_TRANSIENT_PATTERNS)) {
3141
+ return `[provider_transient] Provider server-side/transient error. Recovery: wait briefly, then re-prompt the same worker (no reset needed). If it keeps happening, switch models/providers or check provider status.
3142
+
3143
+ Original: ${message}`;
3144
+ }
3145
+ return message;
3146
+ }
3147
+
2970
3148
  // src/providers/palsu.ts
2971
3149
  function palsuText(text) {
2972
3150
  return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
@@ -3113,7 +3291,7 @@ function registerPalsuProvider(config) {
3113
3291
  const stopReason = explicitStop ?? (hasToolCalls ? "tool_use" : "end_turn");
3114
3292
  return yield* simulateStream(message, stopReason, options.signal, cacheUsage);
3115
3293
  })();
3116
- return new StreamResult(gen);
3294
+ return new StreamResult(gen, options.signal);
3117
3295
  }
3118
3296
  });
3119
3297
  return handle;
@@ -3123,6 +3301,7 @@ export {
3123
3301
  EventStream,
3124
3302
  ProviderError,
3125
3303
  StreamResult,
3304
+ classifyProviderError,
3126
3305
  formatError,
3127
3306
  formatErrorForDisplay,
3128
3307
  isHardBillingMessage,