@code-yeongyu/senpi 2026.9.16-2 → 2026.9.16-3

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +18 -2
  2. package/dist/cli/help-extension-flags.d.ts +22 -0
  3. package/dist/cli/help-extension-flags.d.ts.map +1 -0
  4. package/dist/cli/help-extension-flags.js +30 -0
  5. package/dist/cli/help-extension-flags.js.map +1 -0
  6. package/dist/cli/help-fast-path.d.ts +19 -0
  7. package/dist/cli/help-fast-path.d.ts.map +1 -0
  8. package/dist/cli/help-fast-path.js +64 -0
  9. package/dist/cli/help-fast-path.js.map +1 -0
  10. package/dist/cli/help-flags-cache.d.ts +23 -0
  11. package/dist/cli/help-flags-cache.d.ts.map +1 -0
  12. package/dist/cli/help-flags-cache.js +118 -0
  13. package/dist/cli/help-flags-cache.js.map +1 -0
  14. package/dist/cli/startup-loading-indicator.d.ts.map +1 -1
  15. package/dist/cli/startup-loading-indicator.js +10 -4
  16. package/dist/cli/startup-loading-indicator.js.map +1 -1
  17. package/dist/cli.js +9 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +0 -11
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +6 -50
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts +38 -0
  24. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts.map +1 -0
  25. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js +155 -0
  26. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js.map +1 -0
  27. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts +2 -0
  28. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js +4 -1
  30. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js.map +1 -1
  31. package/dist/core/retry-fallback/settings.d.ts +0 -3
  32. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  33. package/dist/core/retry-fallback/settings.js.map +1 -1
  34. package/dist/core/sdk.d.ts.map +1 -1
  35. package/dist/core/sdk.js +0 -1
  36. package/dist/core/sdk.js.map +1 -1
  37. package/dist/core/settings-manager.d.ts +1 -12
  38. package/dist/core/settings-manager.d.ts.map +1 -1
  39. package/dist/core/settings-manager.js +0 -21
  40. package/dist/core/settings-manager.js.map +1 -1
  41. package/dist/main.d.ts.map +1 -1
  42. package/dist/main.js +31 -3
  43. package/dist/main.js.map +1 -1
  44. package/dist/modes/interactive/interactive-mode.d.ts +1 -12
  45. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  46. package/dist/modes/interactive/interactive-mode.js +1 -34
  47. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  48. package/dist/modes/interactive/working-status.d.ts +2 -3
  49. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  50. package/dist/modes/interactive/working-status.js +4 -17
  51. package/dist/modes/interactive/working-status.js.map +1 -1
  52. package/dist/senpi +9 -0
  53. package/docs/settings.md +1 -19
  54. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  55. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  56. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  57. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +3 -30
  58. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  59. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +0 -5
  60. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  61. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +0 -2
  62. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  63. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +0 -2
  64. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  65. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +0 -1
  66. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  67. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +0 -9
  68. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  69. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  70. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  71. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  72. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  73. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  74. package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.json +1 -1
  75. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +0 -1
  76. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +0 -18
  78. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  80. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  81. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  82. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  83. package/package.json +7 -7
  84. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts +0 -101
  85. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts.map +0 -1
  86. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js +0 -178
  87. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\nimport { FORWARDED_EMPTY_RESPONSE_ERROR, FORWARDED_EMPTY_TOOL_USE_ERROR } from \"./empty-response-errors.ts\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst NON_RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t// OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's\n\t// Zen API. These are subscription/account limits, not transient throttles.\n\t\"GoUsageLimitError\",\n\t\"FreeUsageLimitError\",\n\n\t// OpenCode Go subscription-limit text asks users to enable available-balance\n\t// usage after rolling/weekly/monthly limits are reached.\n\t\"Monthly usage limit reached\",\n\t\"available balance\",\n\n\t// Generic quota/budget/billing exhaustion. `insufficient_quota` is OpenAI's\n\t// quota/billing error code; the other strings cover common gateway wording.\n\t\"insufficient_quota\",\n\t\"out of budget\",\n\t\"quota exceeded\",\n\t\"billing\",\n\n\t// Anthropic Console credit exhaustion: a 429 rate_limit_error whose details\n\t// carry error_code credits_required (\"Usage credits are required for this\n\t// model.\"). The account is dead until the user buys credits or raises the\n\t// spend limit, so same-model retries can never recover it.\n\t\"credits_required\",\n\t\"credits are required\",\n\n\t// Request-shape rejections: the provider refused the payload we built, not the\n\t// work it describes. Gateways wrap these in whatever status they like — the\n\t// observed Apitopia/Kimi case arrives as `500 server_error: Invalid request:\n\t// tools.function.parameters.type is required and must be \"object\"` — so the\n\t// status text alone would classify a permanent failure as transient. The same\n\t// bytes are rejected on every attempt and on every fallback model, so retrying\n\t// can only burn the turn. Anchored on the `tools[...]`/`functions[...]` request\n\t// path so unrelated prose mentioning tools stays retryable.\n\t\"invalid request: tools\\\\.\",\n\t\"invalid request: functions\\\\.\",\n\t\"tools\\\\.[^ ]*function\\\\.parameters\",\n\t\"tools\\\\.\\\\d+\\\\.function\\\\.parameters\",\n\t\"invalid tool schema\",\n]);\n\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t// Local credential/auth sidecar lock exhaustion is infrastructure contention,\n\t// not a provider failure and must stay on the transient retry path.\n\t\"Credential store is busy: lock\",\n\t// Generic provider load, HTTP status, and server-side transient failures.\n\t\"overloaded\",\n\t\"rate.?limit\",\n\t\"too many requests\",\n\t\"429\",\n\t\"500\",\n\t\"502\",\n\t\"503\",\n\t\"504\",\n\t// Cloudflare 522 (Connection timed out): origin stopped responding; transient\n\t// like the other 5xx gateway statuses, surfaced as \"Error: error code: 522\".\n\t\"522\",\n\t\"524\",\n\t\"service.?unavailable\",\n\t\"server.?error\",\n\t\"internal.?error\",\n\n\t// Wrapper/provider text for transient upstream failures, including OpenRouter\n\t// \"Provider returned error\" responses (#2264).\n\t\"provider.?returned.?error\",\n\t\"exceeded request buffer limit while retrying upstream\",\n\n\t// Network, proxy, and fetch transport failures. This includes OpenAI Codex\n\t// raw-fetch failures such as \"upstream connect\", \"connection refused\", and\n\t// \"reset before headers\" (#733), plus OpenRouter connection drops (#3317).\n\t\"network.?error\",\n\t\"connection.?error\",\n\t\"connection.?refused\",\n\t\"connection.?lost\",\n\t\"other side closed\",\n\t\"fetch failed\",\n\t\"getaddrinfo\",\n\t\"ENOTFOUND\",\n\t\"EAI_AGAIN\",\n\t\"upstream.?connect\",\n\t\"upstream.?unavailable\",\n\t\"reset before headers\",\n\t\"socket hang up\",\n\t\"socket connection was closed\",\n\t\"timed? out\",\n\t\"timeout\",\n\t\"terminated\",\n\n\t// WebSocket transports can report close/error text instead of HTTP/fetch text.\n\t\"websocket.?closed\",\n\t\"websocket.?error\",\n\n\t// Premature stream endings from SDKs and transports. Anthropic can throw\n\t// \"stream ended without ...\" and \"Anthropic stream ended before message_stop\"\n\t// (#4433); Bedrock/Smithy can throw an HTTP/2 no-response error (#3594).\n\t\"ended without\",\n\t\"stream ended before message_stop\",\n\t\"stream ended before a terminal response event\",\n\t\"http2 request did not get a response\",\n\n\t// Provider-requested retry delay cap failures should flow through the outer\n\t// retry policy so callers can surface/abort the backoff (#1123).\n\t\"retry delay\",\n\n\t// Explicit retry guidance emitted mid-stream by OpenAI Responses and Bedrock\n\t// stream exceptions (#6019).\n\t\"you can retry your request\",\n\t\"try your request again\",\n\t\"please retry your request\",\n\n\t// Gateway/proxy-side rejection of the whole model request, e.g. \"Error: The\n\t// model request was rejected. Check the request and try again.\" (observed in a\n\t// live session, 2026-08-11). Coupling the rejection sentence to its explicit\n\t// retry instruction keeps unrelated permission, request-shape, and content-\n\t// policy rejections terminal while the bounded retry policy absorbs this\n\t// transient wrapper wording.\n\t\"the model request was rejected\\\\.\\\\s*check the request and try again\\\\.?\",\n\n\t// Anthropic server-tool pairing rejections, e.g. \"`web_search` tool use with id\n\t// `srvtoolu_...` was found without a corresponding `web_search_tool_result`\n\t// block\". A turn closed before its deferred server tool could answer leaves\n\t// the unpairable half in history, and replaying it 400s every later request.\n\t// The Anthropic request builder repairs the replayed history, so a retry sends\n\t// a valid payload; keeping the class retryable is what lets retry and the model\n\t// fallback chain unwedge such a session instead of dead-ending it. The trailing\n\t// backtick keeps the pattern on Anthropic's pairing-error template.\n\t\"was found without a corresponding `\",\n\n\t// An empty stop or tool_use-without-tool-call on a model whose reasoning had already streamed\n\t// live. The stream-level wrapper (pi-agent-core empty-assistant-recovery) cannot replay such an\n\t// attempt, so it ends the turn with these exact texts for the turn retry to re-request; the\n\t// \"twice\" variants are deliberately absent because the wrapper already spent its own retry.\n\tescapeRegExp(FORWARDED_EMPTY_RESPONSE_ERROR),\n\tescapeRegExp(FORWARDED_EMPTY_TOOL_USE_ERROR),\n\n\t// Agent-loop throughput watchdog verdict (#1739). The upstream is answering,\n\t// just uselessly slowly, so the turn must move - to the fallback chain, not\n\t// through the same-model budget (see isProviderStreamThroughputDegradedError).\n\t\"provider stream throughput degraded\",\n\n\t// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n\n\t// Claude Agent SDK session.json lock contention. A second stream/resume\n\t// hits proper-lockfile while the previous subprocess still holds the file.\n\t// Same-process retry recovers; hopping providers cannot release that lock.\n\t\"Lock file is already being held\",\n]);\n\n/**\n * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).\n * `maxAgentDelayMs` caps each computed delay and defaults to 60 seconds.\n * Matches `settings.retry` (`enabled`, `maxRetries`, `baseDelayMs`, `maxAgentDelayMs`) in coding-agent; kept\n * here so the classifier and the policy-driven retry loop live together and stay reusable\n * by the SDK and other callers.\n */\nexport interface RetryPolicy {\n\tenabled: boolean;\n\t/** Max retry attempts (0 = no retries). The initial call never counts as a retry. */\n\tmaxRetries: number;\n\t/** Base delay in ms. Per-attempt delay is `baseDelayMs * 2^(attempt-1)` before jitter. */\n\tbaseDelayMs: number;\n\t/** Optional cap for agent-level retry delays in ms. Defaults to 60 seconds. */\n\tmaxAgentDelayMs?: number;\n\t/** Injectable source used for Codex-style +/-10% backoff jitter. */\n\trandom?: () => number;\n}\n\nexport const DEFAULT_MAX_AGENT_RETRY_DELAY_MS = 60_000;\n\n/**\n * Per-attempt backoff: `baseDelayMs * 2^(attempt-1)`, jittered by +/-10% through the\n * injectable `random` source, then clamped to `maxAgentDelayMs` (60s by default). The\n * jitter runs before the clamp so a capped delay stays exactly at the cap instead of\n * scattering above it, and the safe-integer guard keeps a large `attempt` from producing\n * `Infinity` before the clamp.\n */\nexport function retryDelayMs(\n\tpolicy: Pick<RetryPolicy, \"baseDelayMs\" | \"maxAgentDelayMs\" | \"random\">,\n\tattempt: number,\n): number {\n\tconst scheduledDelayMs = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);\n\tconst sample = Math.min(1, Math.max(0, (policy.random ?? Math.random)()));\n\tconst jitteredDelayMs = Math.round(scheduledDelayMs * (0.9 + sample * 0.2));\n\tconst safeDelay = Number.isSafeInteger(jitteredDelayMs) ? jitteredDelayMs : Number.MAX_SAFE_INTEGER;\n\treturn Math.min(safeDelay, policy.maxAgentDelayMs ?? DEFAULT_MAX_AGENT_RETRY_DELAY_MS);\n}\n\n/** Optional callbacks emitted by {@link retryAssistantCall} around each retry. */\nexport interface RetryCallbacks {\n\t/** Emitted before the backoff sleep of each retry attempt (1-indexed). */\n\tonRetryScheduled?: (\n\t\tattempt: number,\n\t\tmaxAttempts: number,\n\t\tdelayMs: number,\n\t\terrorMessage: string,\n\t) => void | Promise<void>;\n\t/** Emitted after the backoff sleep, immediately before the retried call starts. */\n\tonRetryAttemptStart?: () => void | Promise<void>;\n\t/** Emitted once when the loop ends: success if a later call completed normally. */\n\tonRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void | Promise<void>;\n}\n\nclass RetrySleepAbortError extends Error {\n\tconstructor() {\n\t\tsuper(\"Aborted\");\n\t}\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new RetrySleepAbortError());\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new RetrySleepAbortError());\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\n/**\n * Run a producer that signals failure by THROWING, with the same bounded\n * backoff, abort, and callback semantics as {@link retryAssistantCall}.\n *\n * `retryAssistantCall` is value-based: its producer reports failure by resolving\n * an `AssistantMessage` with `stopReason: \"error\"`. Callers that instead reject\n * (compaction summarization, and any other request pipeline that throws) need\n * the identical retry policy without reshaping their failures into assistant\n * messages first, so both live here and share one implementation of the delay,\n * abort, and callback contract.\n *\n * Behavior:\n * - A resolved value is returned immediately.\n * - A throw that `isRetryable` rejects is rethrown at once: deterministic\n * failures never spend a retry.\n * - Otherwise the call is retried up to `policy.maxRetries` times with the same\n * exponential backoff (`baseDelayMs * 2^(attempt-1)`), rethrowing the final\n * error when the budget is exhausted.\n * - An abort during the backoff sleep stops the loop and rethrows; unlike the\n * assistant-message path there is no aborted value to normalize to.\n *\n * When `policy` is undefined or disabled the producer runs exactly once and its\n * error propagates unchanged.\n */\nexport async function retryTransientCall<T>(\n\tproduce: () => Promise<T>,\n\tisRetryable: (error: unknown) => boolean,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<T> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\n\tlet attempt = 0;\n\tlet lastRetry: { attempt: number; errorMessage: string } | undefined;\n\tfor (;;) {\n\t\ttry {\n\t\t\tconst value = await produce();\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(true, lastRetry.attempt);\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\tif (error instanceof RetrySleepAbortError) throw error;\n\t\t\tif (attempt >= maxAttempts || !isRetryable(error)) {\n\t\t\t\tif (lastRetry) {\n\t\t\t\t\tawait callbacks?.onRetryFinished?.(false, lastRetry.attempt, errorMessageOf(error));\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tattempt++;\n\t\t\tlastRetry = { attempt, errorMessage: errorMessageOf(error) };\n\t\t\tconst delayMs = retryDelayMs(policy!, attempt);\n\t\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, delayMs, lastRetry.errorMessage);\n\n\t\t\ttry {\n\t\t\t\tawait sleep(delayMs, signal);\n\t\t\t} catch (sleepError) {\n\t\t\t\tawait callbacks?.onRetryFinished?.(false, attempt, lastRetry.errorMessage);\n\t\t\t\tthrow sleepError instanceof RetrySleepAbortError ? error : sleepError;\n\t\t\t}\n\t\t\tawait callbacks?.onRetryAttemptStart?.();\n\t\t}\n\t}\n}\n\nfunction errorMessageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message || \"Unknown error\";\n\treturn String(error) || \"Unknown error\";\n}\n\n/**\n * Run a single assistant-producing call with bounded retry on transient errors.\n *\n * Behavior:\n * - A successful response is returned immediately. Aborts are terminal and never\n * retried, but reported as unsuccessful if they happen after a retry was scheduled.\n * Aborts during the backoff sleep are normalized to an aborted `AssistantMessage`\n * too, so callers do not need to care when cancellation happened.\n * - A non-retryable error (per {@link isRetryableAssistantError}, including quota/\n * billing exhaustion) is returned immediately so deterministic errors fail fast.\n * - Otherwise retries up to `maxRetries` times with exponential backoff, emitting\n * `onRetryScheduled` before each sleep, `onRetryAttemptStart` after each sleep before\n * the retried call starts, and `onRetryFinished` once at the end (whether the loop\n * ends in success, exhausted retries, or an aborted backoff).\n *\n * When `policy` is undefined or disabled, the first response is returned unchanged\n * (equivalent to calling `produce()` directly).\n */\nexport async function retryAssistantCall(\n\tproduce: () => Promise<AssistantMessage>,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<AssistantMessage> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\n\tlet attempt = 0;\n\tlet lastRetry: { attempt: number; errorMessage: string } | undefined;\n\tfor (;;) {\n\t\tconst response = await produce();\n\n\t\t// Abort: terminal but not successful. Never retry an aborted message.\n\t\tif (response.stopReason === \"aborted\") {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt);\n\t\t\treturn response;\n\t\t}\n\n\t\t// Success: non-error, non-abort responses return as-is.\n\t\tif (response.stopReason !== \"error\") {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(true, lastRetry.attempt);\n\t\t\treturn response;\n\t\t}\n\n\t\t// Non-retryable, or budget exhausted: return the final error message.\n\t\tif (attempt >= maxAttempts || !isRetryableAssistantError(response)) {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt, response.errorMessage);\n\t\t\treturn response;\n\t\t}\n\n\t\tattempt++;\n\t\tlastRetry = { attempt, errorMessage: response.errorMessage || \"Unknown error\" };\n\t\tconst delayMs = retryDelayMs(policy!, attempt);\n\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, delayMs, lastRetry.errorMessage);\n\n\t\t// Normalize aborts during retry backoff to the same AssistantMessage shape as\n\t\t// provider stream aborts, so callers do not need to care when cancellation happened.\n\t\ttry {\n\t\t\tawait sleep(delayMs, signal);\n\t\t} catch (error) {\n\t\t\tawait callbacks?.onRetryFinished?.(false, attempt, lastRetry.errorMessage);\n\t\t\tif (error instanceof RetrySleepAbortError) {\n\t\t\t\tconst { errorMessage: _errorMessage, ...rest } = response;\n\t\t\t\treturn { ...rest, stopReason: \"aborted\" };\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tawait callbacks?.onRetryAttemptStart?.();\n\t}\n}\n\n/**\n * Classifies whether a failed assistant message looks like a transient provider\n * or transport error, so callers can decide if the last assistant turn should be\n * restarted.\n *\n * This does not implement retry policy. Callers should first handle context\n * overflow separately, then apply their own retry budget, backoff, and reporting\n * before restarting the assistant turn.\n */\nexport function isRetryableAssistantError(message: AssistantMessage): boolean {\n\tif (\n\t\tmessage.stopReason !== \"error\" ||\n\t\tmessage.stopDetails?.type === \"refusal\" ||\n\t\tmessage.stopDetails?.type === \"sensitive\" ||\n\t\t!message.errorMessage\n\t) {\n\t\treturn false;\n\t}\n\treturn isRetryableErrorMessage(message.errorMessage);\n}\n\n/**\n * Matches the agent-loop stream watchdog failures (\"Idle timeout waiting for\n * provider stream after <n>ms\" and \"Provider stream start timed out after\n * <n>ms\"), the WebSocket liveness verdict (\"WebSocket liveness timeout after\n * <n>ms (<k> pings unanswered)\"), and the Responses completion-phase verdict\n * (\"Provider stream stalled after the last output item: response.completed\n * timed out after <n>ms\"). These anchored shapes\n * distinguish provider-stream stalls from unrelated extension, command, or MCP\n * timeout diagnostics.\n */\nconst PROVIDER_STREAM_STALL_ERROR_PATTERN =\n\t/^(?:Idle timeout waiting for provider stream after \\d+ms|Provider stream start timed out after \\d+ms(?: \\([^)]*\\))?|WebSocket liveness timeout after \\d+ms \\(\\d+ pings unanswered\\)|Provider stream stalled after the last output item: response\\.completed timed out after \\d+ms)$/i;\nconst PROVIDER_TRANSPORT_TIMEOUT_ERROR_PATTERN = /^Request timed out\\.?$/i;\n\nexport function isProviderStreamStallError(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"error\" && PROVIDER_STREAM_STALL_ERROR_PATTERN.test(message.errorMessage ?? \"\");\n}\n\n/** One row per stall watchdog: how to read its message, and how to explain it. */\nconst PROVIDER_STALL_PHASES: ReadonlyArray<{\n\tpattern: RegExp;\n\t/** What the provider failed to do, in the user's words. */\n\tsymptom: string;\n\t/** The setting that widens this bound, when one exists. */\n\tsetting?: string;\n}> = [\n\t{\n\t\tpattern: /^Provider stream start timed out after (\\d+)ms/i,\n\t\tsymptom: \"accepted the request but never started sending a response\",\n\t\tsetting: \"retry.provider.streamStartTimeoutMs\",\n\t},\n\t{\n\t\tpattern: /^Idle timeout waiting for provider stream after (\\d+)ms/i,\n\t\tsymptom: \"started the response and then went silent\",\n\t\tsetting: \"retry.provider.timeoutMs\",\n\t},\n\t{\n\t\tpattern: /^WebSocket liveness timeout after (\\d+)ms/i,\n\t\tsymptom: \"stopped answering connection health checks\",\n\t},\n\t{\n\t\tpattern: /^Provider stream stalled after the last output item: response\\.completed timed out after (\\d+)ms/i,\n\t\tsymptom: \"finished its output but never sent the end-of-response event\",\n\t},\n];\n\nexport interface ProviderStallDescriptionOptions {\n\t/** Same-model attempts already spent on this turn. */\n\tattempts?: number;\n\t/** Selector of the model that stalled, for example `anthropic/claude-opus-5`. */\n\tmodel?: string;\n\t/**\n\t * Appends the \"what to do next\" sentence. Omit it while the turn can still\n\t * recover: a retry in flight is not the moment to tell the user to act.\n\t */\n\trecovery?: \"no-fallback-configured\" | \"chain-exhausted\";\n}\n\nfunction formatStallDuration(timeoutMs: number): string {\n\tif (timeoutMs < 1000) return `${timeoutMs}ms`;\n\tif (timeoutMs < 120_000) return `${Math.round(timeoutMs / 100) / 10}s`;\n\treturn `${Math.round(timeoutMs / 6000) / 10}m`;\n}\n\n/**\n * Plain-language replacement for a stall watchdog's own `Error.message`.\n *\n * The watchdog wording (`Provider stream start timed out after 180000ms ...`)\n * is a classifier token - {@link isProviderStreamStallError} and the turn retry\n * gate both match on it - so it must stay on the assistant message. It was also\n * the only thing the user ever saw when a turn died on a stall, which explains\n * nothing and names no next step (senpi#1740). Every user-facing surface routes\n * the message through here first and falls back to the raw text for anything\n * that is not a stall.\n */\nexport function describeProviderStallForUser(\n\terrorMessage: string | undefined,\n\toptions: ProviderStallDescriptionOptions = {},\n): string | undefined {\n\tif (!errorMessage) return undefined;\n\tfor (const { pattern, symptom, setting } of PROVIDER_STALL_PHASES) {\n\t\tconst match = pattern.exec(errorMessage);\n\t\tif (!match) continue;\n\t\tconst subject = options.model ? `The provider for ${options.model}` : \"The provider\";\n\t\tconst duration = formatStallDuration(Number(match[1]));\n\t\tconst sentences = [`${subject} ${symptom} within ${duration}, so the request was cancelled.`];\n\t\tconst attempts = options.attempts ?? 0;\n\t\tif (attempts > 0) {\n\t\t\tsentences.push(`Retried ${attempts} time${attempts === 1 ? \"\" : \"s\"} on the same model with the same result.`);\n\t\t}\n\t\tif (options.recovery) {\n\t\t\tconst raise = setting ? `, or raise ${setting} in settings (0 disables the bound)` : \"\";\n\t\t\tconst lead =\n\t\t\t\toptions.recovery === \"no-fallback-configured\"\n\t\t\t\t\t? \"No fallback model is configured for it, so nothing could take the turn over: run /fallback to add one\"\n\t\t\t\t\t: \"Every model in its fallback chain was tried as well: run /fallback to review the chain\";\n\t\t\tsentences.push(`${lead}, send the message again${raise}.`);\n\t\t}\n\t\treturn sentences.join(\" \");\n\t}\n\treturn undefined;\n}\n\n/**\n * Matches the agent-loop throughput watchdog verdict (\"Provider stream\n * throughput degraded: <n> tok/s over <n>s (floor <n> tok/s)\", optionally\n * followed by the settings hint). Deliberately NOT part of the stall pattern\n * above: a stall is silence, which a same-model retry can genuinely fix, while\n * a degraded stream is an upstream that answers too slowly for replaying the\n * same payload to help. Callers use this to skip the same-model retry budget\n * and consult the fallback chain immediately.\n */\nconst PROVIDER_STREAM_THROUGHPUT_DEGRADED_ERROR_PATTERN =\n\t/^Provider stream throughput degraded: \\d+(?:\\.\\d+)? tok\\/s over \\d+(?:\\.\\d+)?s \\(floor \\d+(?:\\.\\d+)? tok\\/s\\)(?: \\([^)]*\\))?$/i;\n\nexport function isProviderStreamThroughputDegradedError(message: AssistantMessage): boolean {\n\treturn (\n\t\tmessage.stopReason === \"error\" &&\n\t\tPROVIDER_STREAM_THROUGHPUT_DEGRADED_ERROR_PATTERN.test(message.errorMessage ?? \"\")\n\t);\n}\n\n/**\n * Classifies timeout failures that originate from the provider stream or its\n * transport. Transport timeouts may arrive as `aborted`; stream watchdog\n * failures are ordinary error responses.\n */\nexport function isProviderTimeoutError(message: AssistantMessage): boolean {\n\tif (message.abortSource === \"provider\") return true;\n\tif (isProviderStreamStallError(message)) return true;\n\tif (message.stopReason !== \"error\" && message.stopReason !== \"aborted\") return false;\n\treturn PROVIDER_TRANSPORT_TIMEOUT_ERROR_PATTERN.test(message.errorMessage ?? \"\");\n}\n\n/**\n * Classifies a raw error-message string with the same transient-vs-terminal\n * rules as {@link isRetryableAssistantError}, for callers that hold a thrown\n * `Error` instead of an `AssistantMessage` (e.g. compaction summarization\n * failures that must decide between degrading and surfacing loudly).\n */\nexport function isRetryableErrorMessage(errorMessage: string): boolean {\n\treturn classifyErrorMessage(errorMessage) === \"retryable\";\n}\n\n/**\n * Tri-state form of {@link isRetryableErrorMessage}. The regexes only carry\n * three outcomes — a non-retryable match, a retryable match, or no match at\n * all — and callers that hold structured failure facts (status codes, provider\n * error codes) need to distinguish \"the regexes say terminal\" from \"the\n * regexes say nothing\" so they can consult the structured facts only in the\n * latter case. Non-retryable still outranks retryable, exactly as before.\n */\nexport function classifyErrorMessage(errorMessage: string): \"non-retryable\" | \"retryable\" | \"unknown\" {\n\tif (!errorMessage) return \"unknown\";\n\tif (NON_RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage)) return \"non-retryable\";\n\tif (RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage)) return \"retryable\";\n\treturn \"unknown\";\n}\n"]}
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\nimport { FORWARDED_EMPTY_RESPONSE_ERROR, FORWARDED_EMPTY_TOOL_USE_ERROR } from \"./empty-response-errors.ts\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst NON_RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t// OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's\n\t// Zen API. These are subscription/account limits, not transient throttles.\n\t\"GoUsageLimitError\",\n\t\"FreeUsageLimitError\",\n\n\t// OpenCode Go subscription-limit text asks users to enable available-balance\n\t// usage after rolling/weekly/monthly limits are reached.\n\t\"Monthly usage limit reached\",\n\t\"available balance\",\n\n\t// Generic quota/budget/billing exhaustion. `insufficient_quota` is OpenAI's\n\t// quota/billing error code; the other strings cover common gateway wording.\n\t\"insufficient_quota\",\n\t\"out of budget\",\n\t\"quota exceeded\",\n\t\"billing\",\n\n\t// Anthropic Console credit exhaustion: a 429 rate_limit_error whose details\n\t// carry error_code credits_required (\"Usage credits are required for this\n\t// model.\"). The account is dead until the user buys credits or raises the\n\t// spend limit, so same-model retries can never recover it.\n\t\"credits_required\",\n\t\"credits are required\",\n\n\t// Request-shape rejections: the provider refused the payload we built, not the\n\t// work it describes. Gateways wrap these in whatever status they like — the\n\t// observed Apitopia/Kimi case arrives as `500 server_error: Invalid request:\n\t// tools.function.parameters.type is required and must be \"object\"` — so the\n\t// status text alone would classify a permanent failure as transient. The same\n\t// bytes are rejected on every attempt and on every fallback model, so retrying\n\t// can only burn the turn. Anchored on the `tools[...]`/`functions[...]` request\n\t// path so unrelated prose mentioning tools stays retryable.\n\t\"invalid request: tools\\\\.\",\n\t\"invalid request: functions\\\\.\",\n\t\"tools\\\\.[^ ]*function\\\\.parameters\",\n\t\"tools\\\\.\\\\d+\\\\.function\\\\.parameters\",\n\t\"invalid tool schema\",\n]);\n\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t// Local credential/auth sidecar lock exhaustion is infrastructure contention,\n\t// not a provider failure and must stay on the transient retry path.\n\t\"Credential store is busy: lock\",\n\t// Generic provider load, HTTP status, and server-side transient failures.\n\t\"overloaded\",\n\t\"rate.?limit\",\n\t\"too many requests\",\n\t\"429\",\n\t\"500\",\n\t\"502\",\n\t\"503\",\n\t\"504\",\n\t// Cloudflare 522 (Connection timed out): origin stopped responding; transient\n\t// like the other 5xx gateway statuses, surfaced as \"Error: error code: 522\".\n\t\"522\",\n\t\"524\",\n\t\"service.?unavailable\",\n\t\"server.?error\",\n\t\"internal.?error\",\n\n\t// Wrapper/provider text for transient upstream failures, including OpenRouter\n\t// \"Provider returned error\" responses (#2264).\n\t\"provider.?returned.?error\",\n\t\"exceeded request buffer limit while retrying upstream\",\n\n\t// Network, proxy, and fetch transport failures. This includes OpenAI Codex\n\t// raw-fetch failures such as \"upstream connect\", \"connection refused\", and\n\t// \"reset before headers\" (#733), plus OpenRouter connection drops (#3317).\n\t\"network.?error\",\n\t\"connection.?error\",\n\t\"connection.?refused\",\n\t\"connection.?lost\",\n\t\"other side closed\",\n\t\"fetch failed\",\n\t\"getaddrinfo\",\n\t\"ENOTFOUND\",\n\t\"EAI_AGAIN\",\n\t\"upstream.?connect\",\n\t\"upstream.?unavailable\",\n\t\"reset before headers\",\n\t\"socket hang up\",\n\t\"socket connection was closed\",\n\t\"timed? out\",\n\t\"timeout\",\n\t\"terminated\",\n\n\t// WebSocket transports can report close/error text instead of HTTP/fetch text.\n\t\"websocket.?closed\",\n\t\"websocket.?error\",\n\n\t// Premature stream endings from SDKs and transports. Anthropic can throw\n\t// \"stream ended without ...\" and \"Anthropic stream ended before message_stop\"\n\t// (#4433); Bedrock/Smithy can throw an HTTP/2 no-response error (#3594).\n\t\"ended without\",\n\t\"stream ended before message_stop\",\n\t\"stream ended before a terminal response event\",\n\t\"http2 request did not get a response\",\n\n\t// Provider-requested retry delay cap failures should flow through the outer\n\t// retry policy so callers can surface/abort the backoff (#1123).\n\t\"retry delay\",\n\n\t// Explicit retry guidance emitted mid-stream by OpenAI Responses and Bedrock\n\t// stream exceptions (#6019).\n\t\"you can retry your request\",\n\t\"try your request again\",\n\t\"please retry your request\",\n\n\t// Gateway/proxy-side rejection of the whole model request, e.g. \"Error: The\n\t// model request was rejected. Check the request and try again.\" (observed in a\n\t// live session, 2026-08-11). Coupling the rejection sentence to its explicit\n\t// retry instruction keeps unrelated permission, request-shape, and content-\n\t// policy rejections terminal while the bounded retry policy absorbs this\n\t// transient wrapper wording.\n\t\"the model request was rejected\\\\.\\\\s*check the request and try again\\\\.?\",\n\n\t// Anthropic server-tool pairing rejections, e.g. \"`web_search` tool use with id\n\t// `srvtoolu_...` was found without a corresponding `web_search_tool_result`\n\t// block\". A turn closed before its deferred server tool could answer leaves\n\t// the unpairable half in history, and replaying it 400s every later request.\n\t// The Anthropic request builder repairs the replayed history, so a retry sends\n\t// a valid payload; keeping the class retryable is what lets retry and the model\n\t// fallback chain unwedge such a session instead of dead-ending it. The trailing\n\t// backtick keeps the pattern on Anthropic's pairing-error template.\n\t\"was found without a corresponding `\",\n\n\t// An empty stop or tool_use-without-tool-call on a model whose reasoning had already streamed\n\t// live. The stream-level wrapper (pi-agent-core empty-assistant-recovery) cannot replay such an\n\t// attempt, so it ends the turn with these exact texts for the turn retry to re-request; the\n\t// \"twice\" variants are deliberately absent because the wrapper already spent its own retry.\n\tescapeRegExp(FORWARDED_EMPTY_RESPONSE_ERROR),\n\tescapeRegExp(FORWARDED_EMPTY_TOOL_USE_ERROR),\n\n\t// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n\n\t// Claude Agent SDK session.json lock contention. A second stream/resume\n\t// hits proper-lockfile while the previous subprocess still holds the file.\n\t// Same-process retry recovers; hopping providers cannot release that lock.\n\t\"Lock file is already being held\",\n]);\n\n/**\n * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).\n * `maxAgentDelayMs` caps each computed delay and defaults to 60 seconds.\n * Matches `settings.retry` (`enabled`, `maxRetries`, `baseDelayMs`, `maxAgentDelayMs`) in coding-agent; kept\n * here so the classifier and the policy-driven retry loop live together and stay reusable\n * by the SDK and other callers.\n */\nexport interface RetryPolicy {\n\tenabled: boolean;\n\t/** Max retry attempts (0 = no retries). The initial call never counts as a retry. */\n\tmaxRetries: number;\n\t/** Base delay in ms. Per-attempt delay is `baseDelayMs * 2^(attempt-1)` before jitter. */\n\tbaseDelayMs: number;\n\t/** Optional cap for agent-level retry delays in ms. Defaults to 60 seconds. */\n\tmaxAgentDelayMs?: number;\n\t/** Injectable source used for Codex-style +/-10% backoff jitter. */\n\trandom?: () => number;\n}\n\nexport const DEFAULT_MAX_AGENT_RETRY_DELAY_MS = 60_000;\n\n/**\n * Per-attempt backoff: `baseDelayMs * 2^(attempt-1)`, jittered by +/-10% through the\n * injectable `random` source, then clamped to `maxAgentDelayMs` (60s by default). The\n * jitter runs before the clamp so a capped delay stays exactly at the cap instead of\n * scattering above it, and the safe-integer guard keeps a large `attempt` from producing\n * `Infinity` before the clamp.\n */\nexport function retryDelayMs(\n\tpolicy: Pick<RetryPolicy, \"baseDelayMs\" | \"maxAgentDelayMs\" | \"random\">,\n\tattempt: number,\n): number {\n\tconst scheduledDelayMs = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);\n\tconst sample = Math.min(1, Math.max(0, (policy.random ?? Math.random)()));\n\tconst jitteredDelayMs = Math.round(scheduledDelayMs * (0.9 + sample * 0.2));\n\tconst safeDelay = Number.isSafeInteger(jitteredDelayMs) ? jitteredDelayMs : Number.MAX_SAFE_INTEGER;\n\treturn Math.min(safeDelay, policy.maxAgentDelayMs ?? DEFAULT_MAX_AGENT_RETRY_DELAY_MS);\n}\n\n/** Optional callbacks emitted by {@link retryAssistantCall} around each retry. */\nexport interface RetryCallbacks {\n\t/** Emitted before the backoff sleep of each retry attempt (1-indexed). */\n\tonRetryScheduled?: (\n\t\tattempt: number,\n\t\tmaxAttempts: number,\n\t\tdelayMs: number,\n\t\terrorMessage: string,\n\t) => void | Promise<void>;\n\t/** Emitted after the backoff sleep, immediately before the retried call starts. */\n\tonRetryAttemptStart?: () => void | Promise<void>;\n\t/** Emitted once when the loop ends: success if a later call completed normally. */\n\tonRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void | Promise<void>;\n}\n\nclass RetrySleepAbortError extends Error {\n\tconstructor() {\n\t\tsuper(\"Aborted\");\n\t}\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new RetrySleepAbortError());\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new RetrySleepAbortError());\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\n/**\n * Run a producer that signals failure by THROWING, with the same bounded\n * backoff, abort, and callback semantics as {@link retryAssistantCall}.\n *\n * `retryAssistantCall` is value-based: its producer reports failure by resolving\n * an `AssistantMessage` with `stopReason: \"error\"`. Callers that instead reject\n * (compaction summarization, and any other request pipeline that throws) need\n * the identical retry policy without reshaping their failures into assistant\n * messages first, so both live here and share one implementation of the delay,\n * abort, and callback contract.\n *\n * Behavior:\n * - A resolved value is returned immediately.\n * - A throw that `isRetryable` rejects is rethrown at once: deterministic\n * failures never spend a retry.\n * - Otherwise the call is retried up to `policy.maxRetries` times with the same\n * exponential backoff (`baseDelayMs * 2^(attempt-1)`), rethrowing the final\n * error when the budget is exhausted.\n * - An abort during the backoff sleep stops the loop and rethrows; unlike the\n * assistant-message path there is no aborted value to normalize to.\n *\n * When `policy` is undefined or disabled the producer runs exactly once and its\n * error propagates unchanged.\n */\nexport async function retryTransientCall<T>(\n\tproduce: () => Promise<T>,\n\tisRetryable: (error: unknown) => boolean,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<T> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\n\tlet attempt = 0;\n\tlet lastRetry: { attempt: number; errorMessage: string } | undefined;\n\tfor (;;) {\n\t\ttry {\n\t\t\tconst value = await produce();\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(true, lastRetry.attempt);\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\tif (error instanceof RetrySleepAbortError) throw error;\n\t\t\tif (attempt >= maxAttempts || !isRetryable(error)) {\n\t\t\t\tif (lastRetry) {\n\t\t\t\t\tawait callbacks?.onRetryFinished?.(false, lastRetry.attempt, errorMessageOf(error));\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tattempt++;\n\t\t\tlastRetry = { attempt, errorMessage: errorMessageOf(error) };\n\t\t\tconst delayMs = retryDelayMs(policy!, attempt);\n\t\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, delayMs, lastRetry.errorMessage);\n\n\t\t\ttry {\n\t\t\t\tawait sleep(delayMs, signal);\n\t\t\t} catch (sleepError) {\n\t\t\t\tawait callbacks?.onRetryFinished?.(false, attempt, lastRetry.errorMessage);\n\t\t\t\tthrow sleepError instanceof RetrySleepAbortError ? error : sleepError;\n\t\t\t}\n\t\t\tawait callbacks?.onRetryAttemptStart?.();\n\t\t}\n\t}\n}\n\nfunction errorMessageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message || \"Unknown error\";\n\treturn String(error) || \"Unknown error\";\n}\n\n/**\n * Run a single assistant-producing call with bounded retry on transient errors.\n *\n * Behavior:\n * - A successful response is returned immediately. Aborts are terminal and never\n * retried, but reported as unsuccessful if they happen after a retry was scheduled.\n * Aborts during the backoff sleep are normalized to an aborted `AssistantMessage`\n * too, so callers do not need to care when cancellation happened.\n * - A non-retryable error (per {@link isRetryableAssistantError}, including quota/\n * billing exhaustion) is returned immediately so deterministic errors fail fast.\n * - Otherwise retries up to `maxRetries` times with exponential backoff, emitting\n * `onRetryScheduled` before each sleep, `onRetryAttemptStart` after each sleep before\n * the retried call starts, and `onRetryFinished` once at the end (whether the loop\n * ends in success, exhausted retries, or an aborted backoff).\n *\n * When `policy` is undefined or disabled, the first response is returned unchanged\n * (equivalent to calling `produce()` directly).\n */\nexport async function retryAssistantCall(\n\tproduce: () => Promise<AssistantMessage>,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<AssistantMessage> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\n\tlet attempt = 0;\n\tlet lastRetry: { attempt: number; errorMessage: string } | undefined;\n\tfor (;;) {\n\t\tconst response = await produce();\n\n\t\t// Abort: terminal but not successful. Never retry an aborted message.\n\t\tif (response.stopReason === \"aborted\") {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt);\n\t\t\treturn response;\n\t\t}\n\n\t\t// Success: non-error, non-abort responses return as-is.\n\t\tif (response.stopReason !== \"error\") {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(true, lastRetry.attempt);\n\t\t\treturn response;\n\t\t}\n\n\t\t// Non-retryable, or budget exhausted: return the final error message.\n\t\tif (attempt >= maxAttempts || !isRetryableAssistantError(response)) {\n\t\t\tif (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt, response.errorMessage);\n\t\t\treturn response;\n\t\t}\n\n\t\tattempt++;\n\t\tlastRetry = { attempt, errorMessage: response.errorMessage || \"Unknown error\" };\n\t\tconst delayMs = retryDelayMs(policy!, attempt);\n\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, delayMs, lastRetry.errorMessage);\n\n\t\t// Normalize aborts during retry backoff to the same AssistantMessage shape as\n\t\t// provider stream aborts, so callers do not need to care when cancellation happened.\n\t\ttry {\n\t\t\tawait sleep(delayMs, signal);\n\t\t} catch (error) {\n\t\t\tawait callbacks?.onRetryFinished?.(false, attempt, lastRetry.errorMessage);\n\t\t\tif (error instanceof RetrySleepAbortError) {\n\t\t\t\tconst { errorMessage: _errorMessage, ...rest } = response;\n\t\t\t\treturn { ...rest, stopReason: \"aborted\" };\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tawait callbacks?.onRetryAttemptStart?.();\n\t}\n}\n\n/**\n * Classifies whether a failed assistant message looks like a transient provider\n * or transport error, so callers can decide if the last assistant turn should be\n * restarted.\n *\n * This does not implement retry policy. Callers should first handle context\n * overflow separately, then apply their own retry budget, backoff, and reporting\n * before restarting the assistant turn.\n */\nexport function isRetryableAssistantError(message: AssistantMessage): boolean {\n\tif (\n\t\tmessage.stopReason !== \"error\" ||\n\t\tmessage.stopDetails?.type === \"refusal\" ||\n\t\tmessage.stopDetails?.type === \"sensitive\" ||\n\t\t!message.errorMessage\n\t) {\n\t\treturn false;\n\t}\n\treturn isRetryableErrorMessage(message.errorMessage);\n}\n\n/**\n * Matches the agent-loop stream watchdog failures (\"Idle timeout waiting for\n * provider stream after <n>ms\" and \"Provider stream start timed out after\n * <n>ms\"), the WebSocket liveness verdict (\"WebSocket liveness timeout after\n * <n>ms (<k> pings unanswered)\"), and the Responses completion-phase verdict\n * (\"Provider stream stalled after the last output item: response.completed\n * timed out after <n>ms\"). These anchored shapes\n * distinguish provider-stream stalls from unrelated extension, command, or MCP\n * timeout diagnostics.\n */\nconst PROVIDER_STREAM_STALL_ERROR_PATTERN =\n\t/^(?:Idle timeout waiting for provider stream after \\d+ms|Provider stream start timed out after \\d+ms(?: \\([^)]*\\))?|WebSocket liveness timeout after \\d+ms \\(\\d+ pings unanswered\\)|Provider stream stalled after the last output item: response\\.completed timed out after \\d+ms)$/i;\nconst PROVIDER_TRANSPORT_TIMEOUT_ERROR_PATTERN = /^Request timed out\\.?$/i;\n\nexport function isProviderStreamStallError(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"error\" && PROVIDER_STREAM_STALL_ERROR_PATTERN.test(message.errorMessage ?? \"\");\n}\n\n/** One row per stall watchdog: how to read its message, and how to explain it. */\nconst PROVIDER_STALL_PHASES: ReadonlyArray<{\n\tpattern: RegExp;\n\t/** What the provider failed to do, in the user's words. */\n\tsymptom: string;\n\t/** The setting that widens this bound, when one exists. */\n\tsetting?: string;\n}> = [\n\t{\n\t\tpattern: /^Provider stream start timed out after (\\d+)ms/i,\n\t\tsymptom: \"accepted the request but never started sending a response\",\n\t\tsetting: \"retry.provider.streamStartTimeoutMs\",\n\t},\n\t{\n\t\tpattern: /^Idle timeout waiting for provider stream after (\\d+)ms/i,\n\t\tsymptom: \"started the response and then went silent\",\n\t\tsetting: \"retry.provider.timeoutMs\",\n\t},\n\t{\n\t\tpattern: /^WebSocket liveness timeout after (\\d+)ms/i,\n\t\tsymptom: \"stopped answering connection health checks\",\n\t},\n\t{\n\t\tpattern: /^Provider stream stalled after the last output item: response\\.completed timed out after (\\d+)ms/i,\n\t\tsymptom: \"finished its output but never sent the end-of-response event\",\n\t},\n];\n\nexport interface ProviderStallDescriptionOptions {\n\t/** Same-model attempts already spent on this turn. */\n\tattempts?: number;\n\t/** Selector of the model that stalled, for example `anthropic/claude-opus-5`. */\n\tmodel?: string;\n\t/**\n\t * Appends the \"what to do next\" sentence. Omit it while the turn can still\n\t * recover: a retry in flight is not the moment to tell the user to act.\n\t */\n\trecovery?: \"no-fallback-configured\" | \"chain-exhausted\";\n}\n\nfunction formatStallDuration(timeoutMs: number): string {\n\tif (timeoutMs < 1000) return `${timeoutMs}ms`;\n\tif (timeoutMs < 120_000) return `${Math.round(timeoutMs / 100) / 10}s`;\n\treturn `${Math.round(timeoutMs / 6000) / 10}m`;\n}\n\n/**\n * Plain-language replacement for a stall watchdog's own `Error.message`.\n *\n * The watchdog wording (`Provider stream start timed out after 180000ms ...`)\n * is a classifier token - {@link isProviderStreamStallError} and the turn retry\n * gate both match on it - so it must stay on the assistant message. It was also\n * the only thing the user ever saw when a turn died on a stall, which explains\n * nothing and names no next step (senpi#1740). Every user-facing surface routes\n * the message through here first and falls back to the raw text for anything\n * that is not a stall.\n */\nexport function describeProviderStallForUser(\n\terrorMessage: string | undefined,\n\toptions: ProviderStallDescriptionOptions = {},\n): string | undefined {\n\tif (!errorMessage) return undefined;\n\tfor (const { pattern, symptom, setting } of PROVIDER_STALL_PHASES) {\n\t\tconst match = pattern.exec(errorMessage);\n\t\tif (!match) continue;\n\t\tconst subject = options.model ? `The provider for ${options.model}` : \"The provider\";\n\t\tconst duration = formatStallDuration(Number(match[1]));\n\t\tconst sentences = [`${subject} ${symptom} within ${duration}, so the request was cancelled.`];\n\t\tconst attempts = options.attempts ?? 0;\n\t\tif (attempts > 0) {\n\t\t\tsentences.push(`Retried ${attempts} time${attempts === 1 ? \"\" : \"s\"} on the same model with the same result.`);\n\t\t}\n\t\tif (options.recovery) {\n\t\t\tconst raise = setting ? `, or raise ${setting} in settings (0 disables the bound)` : \"\";\n\t\t\tconst lead =\n\t\t\t\toptions.recovery === \"no-fallback-configured\"\n\t\t\t\t\t? \"No fallback model is configured for it, so nothing could take the turn over: run /fallback to add one\"\n\t\t\t\t\t: \"Every model in its fallback chain was tried as well: run /fallback to review the chain\";\n\t\t\tsentences.push(`${lead}, send the message again${raise}.`);\n\t\t}\n\t\treturn sentences.join(\" \");\n\t}\n\treturn undefined;\n}\n\n/**\n * Classifies timeout failures that originate from the provider stream or its\n * transport. Transport timeouts may arrive as `aborted`; stream watchdog\n * failures are ordinary error responses.\n */\nexport function isProviderTimeoutError(message: AssistantMessage): boolean {\n\tif (message.abortSource === \"provider\") return true;\n\tif (isProviderStreamStallError(message)) return true;\n\tif (message.stopReason !== \"error\" && message.stopReason !== \"aborted\") return false;\n\treturn PROVIDER_TRANSPORT_TIMEOUT_ERROR_PATTERN.test(message.errorMessage ?? \"\");\n}\n\n/**\n * Classifies a raw error-message string with the same transient-vs-terminal\n * rules as {@link isRetryableAssistantError}, for callers that hold a thrown\n * `Error` instead of an `AssistantMessage` (e.g. compaction summarization\n * failures that must decide between degrading and surfacing loudly).\n */\nexport function isRetryableErrorMessage(errorMessage: string): boolean {\n\treturn classifyErrorMessage(errorMessage) === \"retryable\";\n}\n\n/**\n * Tri-state form of {@link isRetryableErrorMessage}. The regexes only carry\n * three outcomes — a non-retryable match, a retryable match, or no match at\n * all — and callers that hold structured failure facts (status codes, provider\n * error codes) need to distinguish \"the regexes say terminal\" from \"the\n * regexes say nothing\" so they can consult the structured facts only in the\n * latter case. Non-retryable still outranks retryable, exactly as before.\n */\nexport function classifyErrorMessage(errorMessage: string): \"non-retryable\" | \"retryable\" | \"unknown\" {\n\tif (!errorMessage) return \"unknown\";\n\tif (NON_RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage)) return \"non-retryable\";\n\tif (RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage)) return \"retryable\";\n\treturn \"unknown\";\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-ai",
3
- "version": "2026.9.16-2",
3
+ "version": "2026.9.16-3",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@anthropic-ai/sdk": "0.123.0",
86
86
  "@aws-sdk/client-bedrock-runtime": "3.1127.0",
87
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.16-2",
87
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.16-3",
88
88
  "@google/genai": "2.21.0",
89
89
  "@smithy/node-http-handler": "4.12.1",
90
90
  "http-proxy-agent": "9.1.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-pty",
3
3
  "private": true,
4
- "version": "2026.9.16-2",
4
+ "version": "2026.9.16-3",
5
5
  "description": "Typed PTY session loader for Senpi persistent terminals",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-telemetry",
3
- "version": "2026.9.16-2",
3
+ "version": "2026.9.16-3",
4
4
  "description": "Vendor-neutral telemetry contracts and typed schema utilities for pi",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-tui",
3
- "version": "2026.9.16-2",
3
+ "version": "2026.9.16-3",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi",
3
- "version": "2026.9.16-2",
3
+ "version": "2026.9.16-3",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -55,9 +55,9 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@earendil-works/chord": "0.85.1",
58
- "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.9.16-2",
59
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.16-2",
60
- "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.9.16-2",
58
+ "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.9.16-3",
59
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.16-3",
60
+ "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.9.16-3",
61
61
  "@silvia-odwyer/photon-node": "0.3.4",
62
62
  "chalk": "6.0.0",
63
63
  "cross-spawn": "7.0.6",
@@ -78,8 +78,8 @@
78
78
  "@anthropic-ai/sdk": "0.123.0",
79
79
  "@aws-sdk/client-bedrock-runtime": "3.1127.0",
80
80
  "@bufbuild/protobuf": "2.14.0",
81
- "@code-yeongyu/senpi-codemode": "2026.9.16-2",
82
- "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.9.16-2",
81
+ "@code-yeongyu/senpi-codemode": "2026.9.16-3",
82
+ "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.9.16-3",
83
83
  "@modelcontextprotocol/sdk": "1.30.0",
84
84
  "@mozilla/readability": "0.6.0",
85
85
  "@opentelemetry/api": "1.9.1",
@@ -118,7 +118,7 @@
118
118
  "@aws-sdk/xml-builder": "3.972.40",
119
119
  "@aws/lambda-invoke-store": "0.3.0",
120
120
  "@babel/runtime": "7.29.2",
121
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.16-2",
121
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.16-3",
122
122
  "@google/genai": "2.21.0",
123
123
  "@hono/node-server": "2.1.1",
124
124
  "@mixmark-io/domino": "2.2.0",
@@ -1,101 +0,0 @@
1
- /**
2
- * Rate guard for an in-progress provider stream.
3
- *
4
- * Every other guard on a live stream is a SILENCE detector: the stream-start
5
- * bound stops applying once the first event arrived, and the inter-event idle
6
- * bound is re-armed by every event. A provider that keeps answering at ~2 tok/s
7
- * therefore trips nothing at all, while the session is unusable — the reported
8
- * `gpt-6-astra` symptom (#1739). This measures the RATE of streamed text and
9
- * thinking units so a trickle becomes a first-class, retryable failure instead
10
- * of a healthy-looking turn.
11
- *
12
- * Deliberately not a wall-clock turn budget: tool-using turns legitimately last
13
- * many minutes. Only time spent waiting on the provider counts, and time the
14
- * provider spends executing local work (Cursor's exec channel) is excluded.
15
- */
16
- /** Sustained floor in streamed units per second; `0` disables the watchdog. */
17
- export declare const DEFAULT_STREAM_THROUGHPUT_FLOOR_TOKENS_PER_SECOND = 8;
18
- /** Observation window; the verdict needs a full window of measured streaming. */
19
- export declare const DEFAULT_STREAM_THROUGHPUT_WINDOW_MS = 20000;
20
- /** First-token jitter and a single long reasoning pause must not fire. */
21
- export declare const DEFAULT_STREAM_THROUGHPUT_GRACE_MS = 5000;
22
- /**
23
- * Minimum streamed units inside the window before a rate is judged at all, so a
24
- * two-token heartbeat is never divided into a verdict.
25
- */
26
- export declare const STREAM_THROUGHPUT_MIN_UNITS = 16;
27
- export interface StreamThroughputOptions {
28
- /** Sustained floor in units per second; `0` or negative disables the watchdog. */
29
- floorTokensPerSecond?: number;
30
- /** Observation window in milliseconds; `0` or negative disables the watchdog. */
31
- windowMs?: number;
32
- /** Milliseconds after the first stream event that are never measured. */
33
- graceMs?: number;
34
- }
35
- /**
36
- * One unit approximates one token. Providers that emit a delta per token give
37
- * one unit per delta; gateways that batch several tokens into one delta are
38
- * measured by length instead of by event count, so batching cannot be mistaken
39
- * for a trickle.
40
- */
41
- export declare function estimateStreamedUnits(text: string | undefined): number;
42
- /**
43
- * The wording is part of the contract: `packages/ai/src/utils/retry.ts`
44
- * classifies it as a retryable, throughput-degraded failure (distinct from the
45
- * silence stalls), and the session routes it straight to the fallback chain.
46
- */
47
- export declare function formatStreamThroughputDegradedMessage(tokensPerSecond: number, floorTokensPerSecond: number, windowMs: number): string;
48
- export declare class StreamThroughputDegradedError extends Error {
49
- readonly tokensPerSecond: number;
50
- readonly floorTokensPerSecond: number;
51
- readonly windowMs: number;
52
- constructor(tokensPerSecond: number, floorTokensPerSecond: number, windowMs: number);
53
- }
54
- /**
55
- * Sliding-window counter of streamed units over the time actually spent
56
- * waiting on the provider. Shared by the watchdog and by the interactive
57
- * working status, so the rate a user sees is the rate that gets judged.
58
- */
59
- export declare class StreamRateMeter {
60
- private readonly windowMs;
61
- private readonly now;
62
- private samples;
63
- private unitsInWindow;
64
- private excludedMs;
65
- private originMs;
66
- constructor(windowMs: number, now?: () => number);
67
- /** Wall clock minus the spans excluded from measurement; monotonic. */
68
- measuredNow(): number;
69
- /** Measured time since the first stream event, or undefined before it. */
70
- measuredElapsedMs(): number | undefined;
71
- /** Anchor the measurement at the first stream event. Idempotent. */
72
- start(): void;
73
- /** Drop a span of wall-clock time (provider-local tool work) from the measurement. */
74
- exclude(elapsedMs: number): void;
75
- record(units: number): void;
76
- /** Streamed units inside the trailing window. */
77
- units(): number;
78
- /**
79
- * Units per second over the trailing window, or undefined until enough
80
- * measured streaming exists for the number to mean anything.
81
- */
82
- ratePerSecond(): number | undefined;
83
- reset(): void;
84
- private prune;
85
- }
86
- export interface StreamThroughputWatchdog {
87
- /** Marks the first stream event; starts the grace clock. Idempotent. */
88
- start(): void;
89
- /** Records streamed units and returns the verdict when the floor is breached. */
90
- record(units: number): StreamThroughputDegradedError | undefined;
91
- /** Drops a span of wall-clock time (provider-local tool work) from the measurement. */
92
- exclude(elapsedMs: number): void;
93
- /** Live rate over the window, or undefined while it is still meaningless. */
94
- ratePerSecond(): number | undefined;
95
- }
96
- /**
97
- * Returns undefined when the watchdog is disabled (a floor or window of `0`),
98
- * so the caller can skip the measurement entirely.
99
- */
100
- export declare function createStreamThroughputWatchdog(options: StreamThroughputOptions | undefined, now?: () => number): StreamThroughputWatchdog | undefined;
101
- //# sourceMappingURL=stream-throughput-watchdog.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stream-throughput-watchdog.d.ts","sourceRoot":"","sources":["../src/stream-throughput-watchdog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,iDAAiD,IAAI,CAAC;AACnE,iFAAiF;AACjF,eAAO,MAAM,mCAAmC,QAAS,CAAC;AAC1D,0EAA0E;AAC1E,eAAO,MAAM,kCAAkC,OAAQ,CAAC;AACxD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAI9C,MAAM,WAAW,uBAAuB;IACvC,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGtE;AAWD;;;;GAIG;AACH,wBAAgB,qCAAqC,CACpD,eAAe,EAAE,MAAM,EACvB,oBAAoB,EAAE,MAAM,EAC5B,QAAQ,EAAE,MAAM,GACd,MAAM,CAMR;AAED,qBAAa,6BAA8B,SAAQ,KAAK;IACvD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,eAAe,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAMlF;CACD;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAqB;IAErC,YAAY,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,MAAiB,EAGzD;IAED,uEAAuE;IACvE,WAAW,IAAI,MAAM,CAEpB;IAED,0EAA0E;IAC1E,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAEZ;IAED,sFAAsF;IACtF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE/B;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAO1B;IAED,iDAAiD;IACjD,KAAK,IAAI,MAAM,CAGd;IAED;;;OAGG;IACH,aAAa,IAAI,MAAM,GAAG,SAAS,CAQlC;IAED,KAAK,IAAI,IAAI,CAKZ;IAED,OAAO,CAAC,KAAK;CASb;AAED,MAAM,WAAW,wBAAwB;IACxC,wEAAwE;IACxE,KAAK,IAAI,IAAI,CAAC;IACd,iFAAiF;IACjF,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,6BAA6B,GAAG,SAAS,CAAC;IACjE,uFAAuF;IACvF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6EAA6E;IAC7E,aAAa,IAAI,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,uBAAuB,GAAG,SAAS,EAC5C,GAAG,GAAE,MAAM,MAAiB,GAC1B,wBAAwB,GAAG,SAAS,CAyBtC"}
@@ -1,178 +0,0 @@
1
- /**
2
- * Rate guard for an in-progress provider stream.
3
- *
4
- * Every other guard on a live stream is a SILENCE detector: the stream-start
5
- * bound stops applying once the first event arrived, and the inter-event idle
6
- * bound is re-armed by every event. A provider that keeps answering at ~2 tok/s
7
- * therefore trips nothing at all, while the session is unusable — the reported
8
- * `gpt-6-astra` symptom (#1739). This measures the RATE of streamed text and
9
- * thinking units so a trickle becomes a first-class, retryable failure instead
10
- * of a healthy-looking turn.
11
- *
12
- * Deliberately not a wall-clock turn budget: tool-using turns legitimately last
13
- * many minutes. Only time spent waiting on the provider counts, and time the
14
- * provider spends executing local work (Cursor's exec channel) is excluded.
15
- */
16
- /** Sustained floor in streamed units per second; `0` disables the watchdog. */
17
- export const DEFAULT_STREAM_THROUGHPUT_FLOOR_TOKENS_PER_SECOND = 8;
18
- /** Observation window; the verdict needs a full window of measured streaming. */
19
- export const DEFAULT_STREAM_THROUGHPUT_WINDOW_MS = 20_000;
20
- /** First-token jitter and a single long reasoning pause must not fire. */
21
- export const DEFAULT_STREAM_THROUGHPUT_GRACE_MS = 5_000;
22
- /**
23
- * Minimum streamed units inside the window before a rate is judged at all, so a
24
- * two-token heartbeat is never divided into a verdict.
25
- */
26
- export const STREAM_THROUGHPUT_MIN_UNITS = 16;
27
- /** Live rate needs this much measured streaming before it means anything. */
28
- const MIN_RATE_SAMPLE_MS = 1_000;
29
- /**
30
- * One unit approximates one token. Providers that emit a delta per token give
31
- * one unit per delta; gateways that batch several tokens into one delta are
32
- * measured by length instead of by event count, so batching cannot be mistaken
33
- * for a trickle.
34
- */
35
- export function estimateStreamedUnits(text) {
36
- if (!text)
37
- return 0;
38
- return Math.max(1, Math.ceil(text.length / 4));
39
- }
40
- function formatRate(value) {
41
- return Number.isInteger(value) ? String(value) : value.toFixed(1);
42
- }
43
- function formatWindowSeconds(windowMs) {
44
- const seconds = windowMs / 1000;
45
- return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1);
46
- }
47
- /**
48
- * The wording is part of the contract: `packages/ai/src/utils/retry.ts`
49
- * classifies it as a retryable, throughput-degraded failure (distinct from the
50
- * silence stalls), and the session routes it straight to the fallback chain.
51
- */
52
- export function formatStreamThroughputDegradedMessage(tokensPerSecond, floorTokensPerSecond, windowMs) {
53
- return (`Provider stream throughput degraded: ${formatRate(tokensPerSecond)} tok/s over ` +
54
- `${formatWindowSeconds(windowMs)}s (floor ${formatRate(floorTokensPerSecond)} tok/s) ` +
55
- `(lower or disable with retry.provider.minThroughputTokensPerSecond in senpi settings; 0 disables)`);
56
- }
57
- export class StreamThroughputDegradedError extends Error {
58
- constructor(tokensPerSecond, floorTokensPerSecond, windowMs) {
59
- super(formatStreamThroughputDegradedMessage(tokensPerSecond, floorTokensPerSecond, windowMs));
60
- this.name = "StreamThroughputDegradedError";
61
- this.tokensPerSecond = tokensPerSecond;
62
- this.floorTokensPerSecond = floorTokensPerSecond;
63
- this.windowMs = windowMs;
64
- }
65
- }
66
- /**
67
- * Sliding-window counter of streamed units over the time actually spent
68
- * waiting on the provider. Shared by the watchdog and by the interactive
69
- * working status, so the rate a user sees is the rate that gets judged.
70
- */
71
- export class StreamRateMeter {
72
- constructor(windowMs, now = Date.now) {
73
- this.samples = [];
74
- this.unitsInWindow = 0;
75
- this.excludedMs = 0;
76
- this.windowMs = windowMs;
77
- this.now = now;
78
- }
79
- /** Wall clock minus the spans excluded from measurement; monotonic. */
80
- measuredNow() {
81
- return this.now() - this.excludedMs;
82
- }
83
- /** Measured time since the first stream event, or undefined before it. */
84
- measuredElapsedMs() {
85
- return this.originMs === undefined ? undefined : this.measuredNow() - this.originMs;
86
- }
87
- /** Anchor the measurement at the first stream event. Idempotent. */
88
- start() {
89
- if (this.originMs === undefined)
90
- this.originMs = this.measuredNow();
91
- }
92
- /** Drop a span of wall-clock time (provider-local tool work) from the measurement. */
93
- exclude(elapsedMs) {
94
- if (elapsedMs > 0)
95
- this.excludedMs += elapsedMs;
96
- }
97
- record(units) {
98
- if (units <= 0)
99
- return;
100
- this.start();
101
- const at = this.measuredNow();
102
- this.samples.push({ at, units });
103
- this.unitsInWindow += units;
104
- this.prune(at);
105
- }
106
- /** Streamed units inside the trailing window. */
107
- units() {
108
- this.prune(this.measuredNow());
109
- return this.unitsInWindow;
110
- }
111
- /**
112
- * Units per second over the trailing window, or undefined until enough
113
- * measured streaming exists for the number to mean anything.
114
- */
115
- ratePerSecond() {
116
- const elapsedMs = this.measuredElapsedMs();
117
- if (elapsedMs === undefined)
118
- return undefined;
119
- const spanMs = Math.min(this.windowMs, elapsedMs);
120
- if (spanMs < MIN_RATE_SAMPLE_MS)
121
- return undefined;
122
- const units = this.units();
123
- if (units <= 0)
124
- return 0;
125
- return units / (spanMs / 1000);
126
- }
127
- reset() {
128
- this.samples = [];
129
- this.unitsInWindow = 0;
130
- this.excludedMs = 0;
131
- this.originMs = undefined;
132
- }
133
- prune(at) {
134
- const cutoff = at - this.windowMs;
135
- let dropped = 0;
136
- while (dropped < this.samples.length && this.samples[dropped].at < cutoff) {
137
- this.unitsInWindow -= this.samples[dropped].units;
138
- dropped++;
139
- }
140
- if (dropped > 0)
141
- this.samples = this.samples.slice(dropped);
142
- }
143
- }
144
- /**
145
- * Returns undefined when the watchdog is disabled (a floor or window of `0`),
146
- * so the caller can skip the measurement entirely.
147
- */
148
- export function createStreamThroughputWatchdog(options, now = Date.now) {
149
- const floorTokensPerSecond = options?.floorTokensPerSecond ?? DEFAULT_STREAM_THROUGHPUT_FLOOR_TOKENS_PER_SECOND;
150
- const windowMs = options?.windowMs ?? DEFAULT_STREAM_THROUGHPUT_WINDOW_MS;
151
- const graceMs = Math.max(0, options?.graceMs ?? DEFAULT_STREAM_THROUGHPUT_GRACE_MS);
152
- if (!Number.isFinite(floorTokensPerSecond) || floorTokensPerSecond <= 0)
153
- return undefined;
154
- if (!Number.isFinite(windowMs) || windowMs <= 0)
155
- return undefined;
156
- const meter = new StreamRateMeter(windowMs, now);
157
- return {
158
- start: () => meter.start(),
159
- exclude: (elapsedMs) => meter.exclude(elapsedMs),
160
- ratePerSecond: () => meter.ratePerSecond(),
161
- record: (units) => {
162
- meter.record(units);
163
- const elapsedMs = meter.measuredElapsedMs();
164
- // Judge only on a full window of measured streaming that starts after
165
- // the grace period; anything earlier is jitter, not a sustained rate.
166
- if (elapsedMs === undefined || elapsedMs < graceMs + windowMs)
167
- return undefined;
168
- const unitsInWindow = meter.units();
169
- if (unitsInWindow < STREAM_THROUGHPUT_MIN_UNITS)
170
- return undefined;
171
- const tokensPerSecond = unitsInWindow / (windowMs / 1000);
172
- if (tokensPerSecond >= floorTokensPerSecond)
173
- return undefined;
174
- return new StreamThroughputDegradedError(tokensPerSecond, floorTokensPerSecond, windowMs);
175
- },
176
- };
177
- }
178
- //# sourceMappingURL=stream-throughput-watchdog.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stream-throughput-watchdog.js","sourceRoot":"","sources":["../src/stream-throughput-watchdog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rate guard for an in-progress provider stream.\n *\n * Every other guard on a live stream is a SILENCE detector: the stream-start\n * bound stops applying once the first event arrived, and the inter-event idle\n * bound is re-armed by every event. A provider that keeps answering at ~2 tok/s\n * therefore trips nothing at all, while the session is unusable — the reported\n * `gpt-6-astra` symptom (#1739). This measures the RATE of streamed text and\n * thinking units so a trickle becomes a first-class, retryable failure instead\n * of a healthy-looking turn.\n *\n * Deliberately not a wall-clock turn budget: tool-using turns legitimately last\n * many minutes. Only time spent waiting on the provider counts, and time the\n * provider spends executing local work (Cursor's exec channel) is excluded.\n */\n\n/** Sustained floor in streamed units per second; `0` disables the watchdog. */\nexport const DEFAULT_STREAM_THROUGHPUT_FLOOR_TOKENS_PER_SECOND = 8;\n/** Observation window; the verdict needs a full window of measured streaming. */\nexport const DEFAULT_STREAM_THROUGHPUT_WINDOW_MS = 20_000;\n/** First-token jitter and a single long reasoning pause must not fire. */\nexport const DEFAULT_STREAM_THROUGHPUT_GRACE_MS = 5_000;\n/**\n * Minimum streamed units inside the window before a rate is judged at all, so a\n * two-token heartbeat is never divided into a verdict.\n */\nexport const STREAM_THROUGHPUT_MIN_UNITS = 16;\n/** Live rate needs this much measured streaming before it means anything. */\nconst MIN_RATE_SAMPLE_MS = 1_000;\n\nexport interface StreamThroughputOptions {\n\t/** Sustained floor in units per second; `0` or negative disables the watchdog. */\n\tfloorTokensPerSecond?: number;\n\t/** Observation window in milliseconds; `0` or negative disables the watchdog. */\n\twindowMs?: number;\n\t/** Milliseconds after the first stream event that are never measured. */\n\tgraceMs?: number;\n}\n\n/**\n * One unit approximates one token. Providers that emit a delta per token give\n * one unit per delta; gateways that batch several tokens into one delta are\n * measured by length instead of by event count, so batching cannot be mistaken\n * for a trickle.\n */\nexport function estimateStreamedUnits(text: string | undefined): number {\n\tif (!text) return 0;\n\treturn Math.max(1, Math.ceil(text.length / 4));\n}\n\nfunction formatRate(value: number): string {\n\treturn Number.isInteger(value) ? String(value) : value.toFixed(1);\n}\n\nfunction formatWindowSeconds(windowMs: number): string {\n\tconst seconds = windowMs / 1000;\n\treturn Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1);\n}\n\n/**\n * The wording is part of the contract: `packages/ai/src/utils/retry.ts`\n * classifies it as a retryable, throughput-degraded failure (distinct from the\n * silence stalls), and the session routes it straight to the fallback chain.\n */\nexport function formatStreamThroughputDegradedMessage(\n\ttokensPerSecond: number,\n\tfloorTokensPerSecond: number,\n\twindowMs: number,\n): string {\n\treturn (\n\t\t`Provider stream throughput degraded: ${formatRate(tokensPerSecond)} tok/s over ` +\n\t\t`${formatWindowSeconds(windowMs)}s (floor ${formatRate(floorTokensPerSecond)} tok/s) ` +\n\t\t`(lower or disable with retry.provider.minThroughputTokensPerSecond in senpi settings; 0 disables)`\n\t);\n}\n\nexport class StreamThroughputDegradedError extends Error {\n\treadonly tokensPerSecond: number;\n\treadonly floorTokensPerSecond: number;\n\treadonly windowMs: number;\n\n\tconstructor(tokensPerSecond: number, floorTokensPerSecond: number, windowMs: number) {\n\t\tsuper(formatStreamThroughputDegradedMessage(tokensPerSecond, floorTokensPerSecond, windowMs));\n\t\tthis.name = \"StreamThroughputDegradedError\";\n\t\tthis.tokensPerSecond = tokensPerSecond;\n\t\tthis.floorTokensPerSecond = floorTokensPerSecond;\n\t\tthis.windowMs = windowMs;\n\t}\n}\n\n/**\n * Sliding-window counter of streamed units over the time actually spent\n * waiting on the provider. Shared by the watchdog and by the interactive\n * working status, so the rate a user sees is the rate that gets judged.\n */\nexport class StreamRateMeter {\n\tprivate readonly windowMs: number;\n\tprivate readonly now: () => number;\n\tprivate samples: { at: number; units: number }[] = [];\n\tprivate unitsInWindow = 0;\n\tprivate excludedMs = 0;\n\tprivate originMs: number | undefined;\n\n\tconstructor(windowMs: number, now: () => number = Date.now) {\n\t\tthis.windowMs = windowMs;\n\t\tthis.now = now;\n\t}\n\n\t/** Wall clock minus the spans excluded from measurement; monotonic. */\n\tmeasuredNow(): number {\n\t\treturn this.now() - this.excludedMs;\n\t}\n\n\t/** Measured time since the first stream event, or undefined before it. */\n\tmeasuredElapsedMs(): number | undefined {\n\t\treturn this.originMs === undefined ? undefined : this.measuredNow() - this.originMs;\n\t}\n\n\t/** Anchor the measurement at the first stream event. Idempotent. */\n\tstart(): void {\n\t\tif (this.originMs === undefined) this.originMs = this.measuredNow();\n\t}\n\n\t/** Drop a span of wall-clock time (provider-local tool work) from the measurement. */\n\texclude(elapsedMs: number): void {\n\t\tif (elapsedMs > 0) this.excludedMs += elapsedMs;\n\t}\n\n\trecord(units: number): void {\n\t\tif (units <= 0) return;\n\t\tthis.start();\n\t\tconst at = this.measuredNow();\n\t\tthis.samples.push({ at, units });\n\t\tthis.unitsInWindow += units;\n\t\tthis.prune(at);\n\t}\n\n\t/** Streamed units inside the trailing window. */\n\tunits(): number {\n\t\tthis.prune(this.measuredNow());\n\t\treturn this.unitsInWindow;\n\t}\n\n\t/**\n\t * Units per second over the trailing window, or undefined until enough\n\t * measured streaming exists for the number to mean anything.\n\t */\n\tratePerSecond(): number | undefined {\n\t\tconst elapsedMs = this.measuredElapsedMs();\n\t\tif (elapsedMs === undefined) return undefined;\n\t\tconst spanMs = Math.min(this.windowMs, elapsedMs);\n\t\tif (spanMs < MIN_RATE_SAMPLE_MS) return undefined;\n\t\tconst units = this.units();\n\t\tif (units <= 0) return 0;\n\t\treturn units / (spanMs / 1000);\n\t}\n\n\treset(): void {\n\t\tthis.samples = [];\n\t\tthis.unitsInWindow = 0;\n\t\tthis.excludedMs = 0;\n\t\tthis.originMs = undefined;\n\t}\n\n\tprivate prune(at: number): void {\n\t\tconst cutoff = at - this.windowMs;\n\t\tlet dropped = 0;\n\t\twhile (dropped < this.samples.length && this.samples[dropped].at < cutoff) {\n\t\t\tthis.unitsInWindow -= this.samples[dropped].units;\n\t\t\tdropped++;\n\t\t}\n\t\tif (dropped > 0) this.samples = this.samples.slice(dropped);\n\t}\n}\n\nexport interface StreamThroughputWatchdog {\n\t/** Marks the first stream event; starts the grace clock. Idempotent. */\n\tstart(): void;\n\t/** Records streamed units and returns the verdict when the floor is breached. */\n\trecord(units: number): StreamThroughputDegradedError | undefined;\n\t/** Drops a span of wall-clock time (provider-local tool work) from the measurement. */\n\texclude(elapsedMs: number): void;\n\t/** Live rate over the window, or undefined while it is still meaningless. */\n\tratePerSecond(): number | undefined;\n}\n\n/**\n * Returns undefined when the watchdog is disabled (a floor or window of `0`),\n * so the caller can skip the measurement entirely.\n */\nexport function createStreamThroughputWatchdog(\n\toptions: StreamThroughputOptions | undefined,\n\tnow: () => number = Date.now,\n): StreamThroughputWatchdog | undefined {\n\tconst floorTokensPerSecond = options?.floorTokensPerSecond ?? DEFAULT_STREAM_THROUGHPUT_FLOOR_TOKENS_PER_SECOND;\n\tconst windowMs = options?.windowMs ?? DEFAULT_STREAM_THROUGHPUT_WINDOW_MS;\n\tconst graceMs = Math.max(0, options?.graceMs ?? DEFAULT_STREAM_THROUGHPUT_GRACE_MS);\n\tif (!Number.isFinite(floorTokensPerSecond) || floorTokensPerSecond <= 0) return undefined;\n\tif (!Number.isFinite(windowMs) || windowMs <= 0) return undefined;\n\n\tconst meter = new StreamRateMeter(windowMs, now);\n\treturn {\n\t\tstart: () => meter.start(),\n\t\texclude: (elapsedMs: number) => meter.exclude(elapsedMs),\n\t\tratePerSecond: () => meter.ratePerSecond(),\n\t\trecord: (units: number) => {\n\t\t\tmeter.record(units);\n\t\t\tconst elapsedMs = meter.measuredElapsedMs();\n\t\t\t// Judge only on a full window of measured streaming that starts after\n\t\t\t// the grace period; anything earlier is jitter, not a sustained rate.\n\t\t\tif (elapsedMs === undefined || elapsedMs < graceMs + windowMs) return undefined;\n\t\t\tconst unitsInWindow = meter.units();\n\t\t\tif (unitsInWindow < STREAM_THROUGHPUT_MIN_UNITS) return undefined;\n\t\t\tconst tokensPerSecond = unitsInWindow / (windowMs / 1000);\n\t\t\tif (tokensPerSecond >= floorTokensPerSecond) return undefined;\n\t\t\treturn new StreamThroughputDegradedError(tokensPerSecond, floorTokensPerSecond, windowMs);\n\t\t},\n\t};\n}\n"]}