@bastani/pi-ai 0.9.14 → 0.9.15

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 (113) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/NOTICE.md +4 -5
  3. package/README.md +6 -4
  4. package/dist/api/anthropic-messages.d.ts.map +1 -1
  5. package/dist/api/anthropic-messages.js +55 -25
  6. package/dist/api/anthropic-messages.js.map +1 -1
  7. package/dist/api/azure-openai-responses.d.ts +2 -0
  8. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  9. package/dist/api/azure-openai-responses.js +9 -2
  10. package/dist/api/azure-openai-responses.js.map +1 -1
  11. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  12. package/dist/api/bedrock-converse-stream.js +132 -8
  13. package/dist/api/bedrock-converse-stream.js.map +1 -1
  14. package/dist/api/github-copilot-headers.d.ts +22 -1
  15. package/dist/api/github-copilot-headers.d.ts.map +1 -1
  16. package/dist/api/github-copilot-headers.js +30 -0
  17. package/dist/api/github-copilot-headers.js.map +1 -1
  18. package/dist/api/google-generative-ai.d.ts +2 -2
  19. package/dist/api/google-generative-ai.d.ts.map +1 -1
  20. package/dist/api/google-generative-ai.js +28 -18
  21. package/dist/api/google-generative-ai.js.map +1 -1
  22. package/dist/api/google-shared.d.ts +5 -2
  23. package/dist/api/google-shared.d.ts.map +1 -1
  24. package/dist/api/google-shared.js +16 -0
  25. package/dist/api/google-shared.js.map +1 -1
  26. package/dist/api/google-vertex.d.ts +2 -2
  27. package/dist/api/google-vertex.d.ts.map +1 -1
  28. package/dist/api/google-vertex.js +15 -11
  29. package/dist/api/google-vertex.js.map +1 -1
  30. package/dist/api/mistral-conversations.d.ts.map +1 -1
  31. package/dist/api/mistral-conversations.js +15 -5
  32. package/dist/api/mistral-conversations.js.map +1 -1
  33. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  34. package/dist/api/openai-codex-responses.js +15 -6
  35. package/dist/api/openai-codex-responses.js.map +1 -1
  36. package/dist/api/openai-completions.d.ts +3 -2
  37. package/dist/api/openai-completions.d.ts.map +1 -1
  38. package/dist/api/openai-completions.js +142 -94
  39. package/dist/api/openai-completions.js.map +1 -1
  40. package/dist/api/openai-responses.d.ts.map +1 -1
  41. package/dist/api/openai-responses.js +20 -10
  42. package/dist/api/openai-responses.js.map +1 -1
  43. package/dist/api/pi-messages.d.ts.map +1 -1
  44. package/dist/api/pi-messages.js +23 -5
  45. package/dist/api/pi-messages.js.map +1 -1
  46. package/dist/api/simple-options.d.ts +4 -0
  47. package/dist/api/simple-options.d.ts.map +1 -1
  48. package/dist/api/simple-options.js +18 -10
  49. package/dist/api/simple-options.js.map +1 -1
  50. package/dist/auth/oauth/device-code.d.ts +1 -0
  51. package/dist/auth/oauth/device-code.d.ts.map +1 -1
  52. package/dist/auth/oauth/device-code.js +1 -1
  53. package/dist/auth/oauth/device-code.js.map +1 -1
  54. package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  55. package/dist/auth/oauth/github-copilot.js +98 -34
  56. package/dist/auth/oauth/github-copilot.js.map +1 -1
  57. package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  58. package/dist/auth/oauth/kimi-coding.js +1 -14
  59. package/dist/auth/oauth/kimi-coding.js.map +1 -1
  60. package/dist/index.d.ts +2 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/providers/data/.manifest.json +1 -1
  65. package/dist/providers/data/amazon-bedrock.json +1 -1
  66. package/dist/providers/data/anthropic.json +1 -1
  67. package/dist/providers/data/baseten.json +1 -1
  68. package/dist/providers/data/cerebras.json +1 -1
  69. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  70. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  71. package/dist/providers/data/github-copilot.json +1 -1
  72. package/dist/providers/data/google-vertex.json +1 -1
  73. package/dist/providers/data/google.json +1 -1
  74. package/dist/providers/data/huggingface.json +1 -1
  75. package/dist/providers/data/nvidia.json +1 -1
  76. package/dist/providers/data/opencode-go.json +1 -1
  77. package/dist/providers/data/opencode.json +1 -1
  78. package/dist/providers/data/openrouter.json +1 -1
  79. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  80. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  81. package/dist/providers/data/qwen-token-plan.json +1 -1
  82. package/dist/providers/data/together.json +1 -1
  83. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  84. package/dist/providers/data/xai.json +1 -1
  85. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  86. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  87. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  88. package/dist/providers/data/xiaomi.json +1 -1
  89. package/dist/providers/data/zai-coding-cn.json +1 -1
  90. package/dist/providers/data/zai.json +1 -1
  91. package/dist/providers/github-copilot-env.d.ts +36 -0
  92. package/dist/providers/github-copilot-env.d.ts.map +1 -0
  93. package/dist/providers/github-copilot-env.js +94 -0
  94. package/dist/providers/github-copilot-env.js.map +1 -0
  95. package/dist/providers/xai.d.ts +1 -1
  96. package/dist/providers/xai.d.ts.map +1 -1
  97. package/dist/providers/xai.js +1 -5
  98. package/dist/providers/xai.js.map +1 -1
  99. package/dist/types.d.ts +43 -5
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/types.js.map +1 -1
  102. package/dist/utils/retry.d.ts.map +1 -1
  103. package/dist/utils/retry.js +14 -0
  104. package/dist/utils/retry.js.map +1 -1
  105. package/dist/utils/sleep.d.ts +2 -0
  106. package/dist/utils/sleep.d.ts.map +1 -0
  107. package/dist/utils/sleep.js +15 -0
  108. package/dist/utils/sleep.js.map +1 -0
  109. package/dist/utils/stream-deadline.d.ts +74 -0
  110. package/dist/utils/stream-deadline.d.ts.map +1 -0
  111. package/dist/utils/stream-deadline.js +196 -0
  112. package/dist/utils/stream-deadline.js.map +1 -0
  113. package/package.json +118 -120
@@ -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\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nconst NON_RETRYABLE_PROVIDER_LIMIT_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\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\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\"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\"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// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n]);\n\n/**\n * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).\n * Matches `settings.retry` (`enabled`, `maxRetries`, `baseDelayMs`) 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}\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 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 = policy!.baseDelayMs * 2 ** (attempt - 1);\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\treturn { ...response, stopReason: \"aborted\", errorMessage: undefined };\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 (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\tconst errorMessage = message.errorMessage;\n\tif (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage)) return false;\n\treturn RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);\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\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nconst NON_RETRYABLE_PROVIDER_LIMIT_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\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\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\"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\"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// Response-body decompression failures. Node/Bun zlib rejects a body whose\n\t// Content-Encoding lied or whose compressed bytes were corrupted, and the\n\t// fetch/WebSocket layer wraps it with a \"Library error:\" prefix. GitHub\n\t// Copilot on the default `transport: \"auto\"` reports this as\n\t// \"Library error: zlib error: incorrect header check\" (#2553). The transfer\n\t// is already unusable, and a fresh attempt normally succeeds, so treat every\n\t// variant as a transient transport failure rather than a deterministic error.\n\t\"zlib\",\n\t\"incorrect header check\",\n\t\"decompress\",\n\t\"library error\",\n\n\t// Idle-stream deadline enforced below the HTTP layer by `withStreamDeadline`\n\t// so a stream that never settles cannot hang an attempt forever (#2553).\n\t\"stream deadline exceeded\",\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// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n]);\n\n/**\n * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).\n * Matches `settings.retry` (`enabled`, `maxRetries`, `baseDelayMs`) 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}\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 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 = policy!.baseDelayMs * 2 ** (attempt - 1);\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\treturn { ...response, stopReason: \"aborted\", errorMessage: undefined };\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 (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\tconst errorMessage = message.errorMessage;\n\tif (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage)) return false;\n\treturn RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
2
+ //# sourceMappingURL=sleep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAapE","sourcesContent":["export function sleep(ms: number, signal: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tsignal.throwIfAborted();\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(signal.reason);\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n"]}
@@ -0,0 +1,15 @@
1
+ export function sleep(ms, signal) {
2
+ return new Promise((resolve, reject) => {
3
+ signal.throwIfAborted();
4
+ const onAbort = () => {
5
+ clearTimeout(timeout);
6
+ reject(signal.reason);
7
+ };
8
+ const timeout = setTimeout(() => {
9
+ signal.removeEventListener("abort", onAbort);
10
+ resolve();
11
+ }, ms);
12
+ signal.addEventListener("abort", onAbort, { once: true });
13
+ });
14
+ }
15
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAmB,EAAiB;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACvC,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;YACrB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAAA,CACtB,CAAC;QACF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;YAChC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC;QAAA,CACV,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAAA,CAC1D,CAAC,CAAC;AAAA,CACH","sourcesContent":["export function sleep(ms: number, signal: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tsignal.throwIfAborted();\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(signal.reason);\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n"]}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Idle deadline for provider response streams (#2553).
3
+ *
4
+ * The HTTP layer only bounds the request/response handshake and, for SDK clients,
5
+ * the socket idle timeout its dispatcher enforces. A body that decodes into a
6
+ * decompression failure can destroy the underlying stream without ever rejecting
7
+ * the async iterator the adapter is awaiting, so `for await (...)` stays pending
8
+ * forever, the adapter's promise never settles, and retry/model-fallback logic
9
+ * never advances. GitHub Copilot with the default `transport: "auto"` reproduces
10
+ * this as a repeated `Library error: zlib error: incorrect header check`.
11
+ *
12
+ * {@link withStreamDeadline} bounds the gap *between* stream events, below the
13
+ * HTTP layer, so a stalled stream always settles as a transient transport error
14
+ * that {@link isRetryableAssistantError} classifies as retryable.
15
+ */
16
+ /**
17
+ * Default idle gap allowed between two provider stream events, in milliseconds.
18
+ *
19
+ * Deliberately below the 600000 ms default HTTP idle timeout so a stalled stream
20
+ * is cut by this deadline rather than waiting on the transport, while staying
21
+ * generous enough for slow reasoning models that go quiet between events.
22
+ */
23
+ export declare const DEFAULT_STREAM_DEADLINE_MS = 300000;
24
+ /** Message text used when a stream exceeds its idle deadline. */
25
+ export declare function streamDeadlineErrorMessage(deadlineMs: number): string;
26
+ /**
27
+ * Raised when a provider stream produces no event within its idle deadline.
28
+ *
29
+ * The message intentionally carries transport-timeout wording so the shared
30
+ * transient-error classifier treats it as a retryable transport failure.
31
+ */
32
+ export declare class StreamDeadlineError extends Error {
33
+ readonly deadlineMs: number;
34
+ constructor(deadlineMs: number);
35
+ }
36
+ /**
37
+ * Deadline-owned signal used to cancel the provider request when the stream
38
+ * deadline fires. The caller signal remains separate, so an internal deadline
39
+ * abort is reported as a retryable error rather than `stopReason: "aborted"`.
40
+ */
41
+ export interface StreamDeadlineHandle {
42
+ readonly deadlineMs: number | undefined;
43
+ readonly signal: AbortSignal | undefined;
44
+ readonly abortedByDeadline: boolean;
45
+ abort(): void;
46
+ cleanup(): void;
47
+ }
48
+ /**
49
+ * Resolve the effective idle deadline for a stream.
50
+ *
51
+ * `undefined` selects {@link DEFAULT_STREAM_DEADLINE_MS}. A non-positive or
52
+ * non-finite value disables the deadline and returns `undefined`, matching how
53
+ * `httpIdleTimeoutMs` and `websocketConnectTimeoutMs` treat `0`/`"disabled"`.
54
+ */
55
+ export declare function resolveStreamDeadlineMs(value: number | undefined): number | undefined;
56
+ /**
57
+ * Create a request signal that combines caller cancellation with a private
58
+ * deadline controller. Calling {@link StreamDeadlineHandle.abort} aborts the
59
+ * provider transport without marking the caller's signal as aborted.
60
+ */
61
+ export declare function createStreamDeadline(value: number | undefined, callerSignal?: AbortSignal): StreamDeadlineHandle;
62
+ /**
63
+ * Wrap an async iterable so each pending `next()` is bounded by an idle deadline.
64
+ *
65
+ * The timer is restarted per event, so it caps the gap between events rather than
66
+ * total stream duration: a long but progressing stream is never cut. When the
67
+ * deadline expires, `onDeadline` is invoked before the wrapper rejects. Adapters
68
+ * use that callback to abort the request signal, which interrupts the pending
69
+ * provider read and runs the source's cleanup before fallback starts.
70
+ *
71
+ * A deadline of `undefined` delegates straight to the source and adds no timer.
72
+ */
73
+ export declare function withStreamDeadline<T>(source: AsyncIterable<T>, deadlineMs: number | undefined, onDeadline?: () => void): AsyncGenerator<T, void, undefined>;
74
+ //# sourceMappingURL=stream-deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-deadline.d.ts","sourceRoot":"","sources":["../../src/utils/stream-deadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAKlD,iEAAiE;AACjE,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,YAAY,UAAU,EAAE,MAAM,EAI7B;CACD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIrF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,CAAC,EAAE,WAAW,GAAG,oBAAoB,CA6BhH;AAgCD;;;;;;;;;;GAUG;AACH,wBAAuB,kBAAkB,CAAC,CAAC,EAC1C,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,EACxB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,GACrB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CA0DpC","sourcesContent":["/**\n * Idle deadline for provider response streams (#2553).\n *\n * The HTTP layer only bounds the request/response handshake and, for SDK clients,\n * the socket idle timeout its dispatcher enforces. A body that decodes into a\n * decompression failure can destroy the underlying stream without ever rejecting\n * the async iterator the adapter is awaiting, so `for await (...)` stays pending\n * forever, the adapter's promise never settles, and retry/model-fallback logic\n * never advances. GitHub Copilot with the default `transport: \"auto\"` reproduces\n * this as a repeated `Library error: zlib error: incorrect header check`.\n *\n * {@link withStreamDeadline} bounds the gap *between* stream events, below the\n * HTTP layer, so a stalled stream always settles as a transient transport error\n * that {@link isRetryableAssistantError} classifies as retryable.\n */\n\nimport { combineAbortSignals, type CombinedAbortSignal } from \"./abort-signals.ts\";\n\n/**\n * Default idle gap allowed between two provider stream events, in milliseconds.\n *\n * Deliberately below the 600000 ms default HTTP idle timeout so a stalled stream\n * is cut by this deadline rather than waiting on the transport, while staying\n * generous enough for slow reasoning models that go quiet between events.\n */\nexport const DEFAULT_STREAM_DEADLINE_MS = 300_000;\n\n/** The largest delay accepted by a platform timer without overflow. */\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\n\n/** Message text used when a stream exceeds its idle deadline. */\nexport function streamDeadlineErrorMessage(deadlineMs: number): string {\n\treturn `Provider stream timed out after ${deadlineMs}ms without a new event (stream deadline exceeded)`;\n}\n\n/**\n * Raised when a provider stream produces no event within its idle deadline.\n *\n * The message intentionally carries transport-timeout wording so the shared\n * transient-error classifier treats it as a retryable transport failure.\n */\nexport class StreamDeadlineError extends Error {\n\treadonly deadlineMs: number;\n\n\tconstructor(deadlineMs: number) {\n\t\tsuper(streamDeadlineErrorMessage(deadlineMs));\n\t\tthis.name = \"StreamDeadlineError\";\n\t\tthis.deadlineMs = deadlineMs;\n\t}\n}\n\n/**\n * Deadline-owned signal used to cancel the provider request when the stream\n * deadline fires. The caller signal remains separate, so an internal deadline\n * abort is reported as a retryable error rather than `stopReason: \"aborted\"`.\n */\nexport interface StreamDeadlineHandle {\n\treadonly deadlineMs: number | undefined;\n\treadonly signal: AbortSignal | undefined;\n\treadonly abortedByDeadline: boolean;\n\tabort(): void;\n\tcleanup(): void;\n}\n\n/**\n * Resolve the effective idle deadline for a stream.\n *\n * `undefined` selects {@link DEFAULT_STREAM_DEADLINE_MS}. A non-positive or\n * non-finite value disables the deadline and returns `undefined`, matching how\n * `httpIdleTimeoutMs` and `websocketConnectTimeoutMs` treat `0`/`\"disabled\"`.\n */\nexport function resolveStreamDeadlineMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return DEFAULT_STREAM_DEADLINE_MS;\n\tif (!Number.isFinite(value) || value <= 0) return undefined;\n\treturn Math.floor(value);\n}\n\n/**\n * Create a request signal that combines caller cancellation with a private\n * deadline controller. Calling {@link StreamDeadlineHandle.abort} aborts the\n * provider transport without marking the caller's signal as aborted.\n */\nexport function createStreamDeadline(value: number | undefined, callerSignal?: AbortSignal): StreamDeadlineHandle {\n\tconst deadlineMs = resolveStreamDeadlineMs(value);\n\tif (deadlineMs === undefined) {\n\t\treturn {\n\t\t\tdeadlineMs,\n\t\t\tsignal: callerSignal,\n\t\t\tabortedByDeadline: false,\n\t\t\tabort: () => {},\n\t\t\tcleanup: () => {},\n\t\t};\n\t}\n\n\tconst controller = new AbortController();\n\tconst combined: CombinedAbortSignal = combineAbortSignals([callerSignal, controller.signal]);\n\tlet abortedByDeadline = false;\n\treturn {\n\t\tdeadlineMs,\n\t\tsignal: combined.signal,\n\t\tget abortedByDeadline() {\n\t\t\treturn abortedByDeadline;\n\t\t},\n\t\tabort: () => {\n\t\t\tif (!controller.signal.aborted) {\n\t\t\t\tabortedByDeadline = true;\n\t\t\t\tcontroller.abort(new StreamDeadlineError(deadlineMs));\n\t\t\t}\n\t\t},\n\t\tcleanup: combined.cleanup,\n\t};\n}\n\n/**\n * Schedule a timer without allowing the platform's 32-bit delay clamp to turn\n * a valid long deadline into a one-millisecond timeout.\n */\nfunction scheduleDeadline(callback: () => void, delayMs: number): () => void {\n\tlet remaining = delayMs;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet cancelled = false;\n\n\tconst arm = () => {\n\t\tif (cancelled) return;\n\t\tconst delay = Math.min(remaining, MAX_TIMER_DELAY_MS);\n\t\ttimer = setTimeout(() => {\n\t\t\tif (cancelled) return;\n\t\t\tremaining -= delay;\n\t\t\tif (remaining <= 0) {\n\t\t\t\tcallback();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tarm();\n\t\t}, delay);\n\t};\n\n\tarm();\n\treturn () => {\n\t\tcancelled = true;\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t};\n}\n\n/**\n * Wrap an async iterable so each pending `next()` is bounded by an idle deadline.\n *\n * The timer is restarted per event, so it caps the gap between events rather than\n * total stream duration: a long but progressing stream is never cut. When the\n * deadline expires, `onDeadline` is invoked before the wrapper rejects. Adapters\n * use that callback to abort the request signal, which interrupts the pending\n * provider read and runs the source's cleanup before fallback starts.\n *\n * A deadline of `undefined` delegates straight to the source and adds no timer.\n */\nexport async function* withStreamDeadline<T>(\n\tsource: AsyncIterable<T>,\n\tdeadlineMs: number | undefined,\n\tonDeadline?: () => void,\n): AsyncGenerator<T, void, undefined> {\n\tif (deadlineMs === undefined) {\n\t\tyield* source;\n\t\treturn;\n\t}\n\n\tconst iterator = source[Symbol.asyncIterator]();\n\tlet closed = false;\n\tconst closeSource = (): void => {\n\t\tif (closed) return;\n\t\tclosed = true;\n\t\ttry {\n\t\t\tvoid Promise.resolve(iterator.return?.()).catch(() => {});\n\t\t} catch {\n\t\t\t// A source iterator without a usable `return` needs no cleanup.\n\t\t}\n\t};\n\n\ttry {\n\t\tfor (;;) {\n\t\t\tlet cancelTimer: (() => void) | undefined;\n\t\t\tlet deadlineFired = false;\n\t\t\tconst pending = Promise.resolve(iterator.next());\n\t\t\tconst deadline = new Promise<never>((_resolve, reject) => {\n\t\t\t\tcancelTimer = scheduleDeadline(() => {\n\t\t\t\t\tdeadlineFired = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonDeadline?.();\n\t\t\t\t\t} finally {\n\t\t\t\t\t\treject(new StreamDeadlineError(deadlineMs));\n\t\t\t\t\t}\n\t\t\t\t}, deadlineMs);\n\t\t\t});\n\n\t\t\tlet result: IteratorResult<T>;\n\t\t\ttry {\n\t\t\t\tresult = await Promise.race([pending, deadline]);\n\t\t\t} catch (error) {\n\t\t\t\t// The transport abort normally rejects the losing read. Swallow its\n\t\t\t\t// eventual rejection, but preserve the deadline error as the public\n\t\t\t\t// failure even if abort delivery wins the race by a microtask.\n\t\t\t\tvoid pending.catch(() => {});\n\t\t\t\tcloseSource();\n\t\t\t\tif (deadlineFired) throw new StreamDeadlineError(deadlineMs);\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\tcancelTimer?.();\n\t\t\t}\n\n\t\t\tif (result.done) {\n\t\t\t\tclosed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tyield result.value;\n\t\t}\n\t} finally {\n\t\tcloseSource();\n\t}\n}\n"]}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Idle deadline for provider response streams (#2553).
3
+ *
4
+ * The HTTP layer only bounds the request/response handshake and, for SDK clients,
5
+ * the socket idle timeout its dispatcher enforces. A body that decodes into a
6
+ * decompression failure can destroy the underlying stream without ever rejecting
7
+ * the async iterator the adapter is awaiting, so `for await (...)` stays pending
8
+ * forever, the adapter's promise never settles, and retry/model-fallback logic
9
+ * never advances. GitHub Copilot with the default `transport: "auto"` reproduces
10
+ * this as a repeated `Library error: zlib error: incorrect header check`.
11
+ *
12
+ * {@link withStreamDeadline} bounds the gap *between* stream events, below the
13
+ * HTTP layer, so a stalled stream always settles as a transient transport error
14
+ * that {@link isRetryableAssistantError} classifies as retryable.
15
+ */
16
+ import { combineAbortSignals } from "./abort-signals.js";
17
+ /**
18
+ * Default idle gap allowed between two provider stream events, in milliseconds.
19
+ *
20
+ * Deliberately below the 600000 ms default HTTP idle timeout so a stalled stream
21
+ * is cut by this deadline rather than waiting on the transport, while staying
22
+ * generous enough for slow reasoning models that go quiet between events.
23
+ */
24
+ export const DEFAULT_STREAM_DEADLINE_MS = 300_000;
25
+ /** The largest delay accepted by a platform timer without overflow. */
26
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
27
+ /** Message text used when a stream exceeds its idle deadline. */
28
+ export function streamDeadlineErrorMessage(deadlineMs) {
29
+ return `Provider stream timed out after ${deadlineMs}ms without a new event (stream deadline exceeded)`;
30
+ }
31
+ /**
32
+ * Raised when a provider stream produces no event within its idle deadline.
33
+ *
34
+ * The message intentionally carries transport-timeout wording so the shared
35
+ * transient-error classifier treats it as a retryable transport failure.
36
+ */
37
+ export class StreamDeadlineError extends Error {
38
+ deadlineMs;
39
+ constructor(deadlineMs) {
40
+ super(streamDeadlineErrorMessage(deadlineMs));
41
+ this.name = "StreamDeadlineError";
42
+ this.deadlineMs = deadlineMs;
43
+ }
44
+ }
45
+ /**
46
+ * Resolve the effective idle deadline for a stream.
47
+ *
48
+ * `undefined` selects {@link DEFAULT_STREAM_DEADLINE_MS}. A non-positive or
49
+ * non-finite value disables the deadline and returns `undefined`, matching how
50
+ * `httpIdleTimeoutMs` and `websocketConnectTimeoutMs` treat `0`/`"disabled"`.
51
+ */
52
+ export function resolveStreamDeadlineMs(value) {
53
+ if (value === undefined)
54
+ return DEFAULT_STREAM_DEADLINE_MS;
55
+ if (!Number.isFinite(value) || value <= 0)
56
+ return undefined;
57
+ return Math.floor(value);
58
+ }
59
+ /**
60
+ * Create a request signal that combines caller cancellation with a private
61
+ * deadline controller. Calling {@link StreamDeadlineHandle.abort} aborts the
62
+ * provider transport without marking the caller's signal as aborted.
63
+ */
64
+ export function createStreamDeadline(value, callerSignal) {
65
+ const deadlineMs = resolveStreamDeadlineMs(value);
66
+ if (deadlineMs === undefined) {
67
+ return {
68
+ deadlineMs,
69
+ signal: callerSignal,
70
+ abortedByDeadline: false,
71
+ abort: () => { },
72
+ cleanup: () => { },
73
+ };
74
+ }
75
+ const controller = new AbortController();
76
+ const combined = combineAbortSignals([callerSignal, controller.signal]);
77
+ let abortedByDeadline = false;
78
+ return {
79
+ deadlineMs,
80
+ signal: combined.signal,
81
+ get abortedByDeadline() {
82
+ return abortedByDeadline;
83
+ },
84
+ abort: () => {
85
+ if (!controller.signal.aborted) {
86
+ abortedByDeadline = true;
87
+ controller.abort(new StreamDeadlineError(deadlineMs));
88
+ }
89
+ },
90
+ cleanup: combined.cleanup,
91
+ };
92
+ }
93
+ /**
94
+ * Schedule a timer without allowing the platform's 32-bit delay clamp to turn
95
+ * a valid long deadline into a one-millisecond timeout.
96
+ */
97
+ function scheduleDeadline(callback, delayMs) {
98
+ let remaining = delayMs;
99
+ let timer;
100
+ let cancelled = false;
101
+ const arm = () => {
102
+ if (cancelled)
103
+ return;
104
+ const delay = Math.min(remaining, MAX_TIMER_DELAY_MS);
105
+ timer = setTimeout(() => {
106
+ if (cancelled)
107
+ return;
108
+ remaining -= delay;
109
+ if (remaining <= 0) {
110
+ callback();
111
+ return;
112
+ }
113
+ arm();
114
+ }, delay);
115
+ };
116
+ arm();
117
+ return () => {
118
+ cancelled = true;
119
+ if (timer !== undefined)
120
+ clearTimeout(timer);
121
+ };
122
+ }
123
+ /**
124
+ * Wrap an async iterable so each pending `next()` is bounded by an idle deadline.
125
+ *
126
+ * The timer is restarted per event, so it caps the gap between events rather than
127
+ * total stream duration: a long but progressing stream is never cut. When the
128
+ * deadline expires, `onDeadline` is invoked before the wrapper rejects. Adapters
129
+ * use that callback to abort the request signal, which interrupts the pending
130
+ * provider read and runs the source's cleanup before fallback starts.
131
+ *
132
+ * A deadline of `undefined` delegates straight to the source and adds no timer.
133
+ */
134
+ export async function* withStreamDeadline(source, deadlineMs, onDeadline) {
135
+ if (deadlineMs === undefined) {
136
+ yield* source;
137
+ return;
138
+ }
139
+ const iterator = source[Symbol.asyncIterator]();
140
+ let closed = false;
141
+ const closeSource = () => {
142
+ if (closed)
143
+ return;
144
+ closed = true;
145
+ try {
146
+ void Promise.resolve(iterator.return?.()).catch(() => { });
147
+ }
148
+ catch {
149
+ // A source iterator without a usable `return` needs no cleanup.
150
+ }
151
+ };
152
+ try {
153
+ for (;;) {
154
+ let cancelTimer;
155
+ let deadlineFired = false;
156
+ const pending = Promise.resolve(iterator.next());
157
+ const deadline = new Promise((_resolve, reject) => {
158
+ cancelTimer = scheduleDeadline(() => {
159
+ deadlineFired = true;
160
+ try {
161
+ onDeadline?.();
162
+ }
163
+ finally {
164
+ reject(new StreamDeadlineError(deadlineMs));
165
+ }
166
+ }, deadlineMs);
167
+ });
168
+ let result;
169
+ try {
170
+ result = await Promise.race([pending, deadline]);
171
+ }
172
+ catch (error) {
173
+ // The transport abort normally rejects the losing read. Swallow its
174
+ // eventual rejection, but preserve the deadline error as the public
175
+ // failure even if abort delivery wins the race by a microtask.
176
+ void pending.catch(() => { });
177
+ closeSource();
178
+ if (deadlineFired)
179
+ throw new StreamDeadlineError(deadlineMs);
180
+ throw error;
181
+ }
182
+ finally {
183
+ cancelTimer?.();
184
+ }
185
+ if (result.done) {
186
+ closed = true;
187
+ return;
188
+ }
189
+ yield result.value;
190
+ }
191
+ }
192
+ finally {
193
+ closeSource();
194
+ }
195
+ }
196
+ //# sourceMappingURL=stream-deadline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-deadline.js","sourceRoot":"","sources":["../../src/utils/stream-deadline.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Idle deadline for provider response streams (#2553).\n *\n * The HTTP layer only bounds the request/response handshake and, for SDK clients,\n * the socket idle timeout its dispatcher enforces. A body that decodes into a\n * decompression failure can destroy the underlying stream without ever rejecting\n * the async iterator the adapter is awaiting, so `for await (...)` stays pending\n * forever, the adapter's promise never settles, and retry/model-fallback logic\n * never advances. GitHub Copilot with the default `transport: \"auto\"` reproduces\n * this as a repeated `Library error: zlib error: incorrect header check`.\n *\n * {@link withStreamDeadline} bounds the gap *between* stream events, below the\n * HTTP layer, so a stalled stream always settles as a transient transport error\n * that {@link isRetryableAssistantError} classifies as retryable.\n */\n\nimport { combineAbortSignals, type CombinedAbortSignal } from \"./abort-signals.ts\";\n\n/**\n * Default idle gap allowed between two provider stream events, in milliseconds.\n *\n * Deliberately below the 600000 ms default HTTP idle timeout so a stalled stream\n * is cut by this deadline rather than waiting on the transport, while staying\n * generous enough for slow reasoning models that go quiet between events.\n */\nexport const DEFAULT_STREAM_DEADLINE_MS = 300_000;\n\n/** The largest delay accepted by a platform timer without overflow. */\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\n\n/** Message text used when a stream exceeds its idle deadline. */\nexport function streamDeadlineErrorMessage(deadlineMs: number): string {\n\treturn `Provider stream timed out after ${deadlineMs}ms without a new event (stream deadline exceeded)`;\n}\n\n/**\n * Raised when a provider stream produces no event within its idle deadline.\n *\n * The message intentionally carries transport-timeout wording so the shared\n * transient-error classifier treats it as a retryable transport failure.\n */\nexport class StreamDeadlineError extends Error {\n\treadonly deadlineMs: number;\n\n\tconstructor(deadlineMs: number) {\n\t\tsuper(streamDeadlineErrorMessage(deadlineMs));\n\t\tthis.name = \"StreamDeadlineError\";\n\t\tthis.deadlineMs = deadlineMs;\n\t}\n}\n\n/**\n * Deadline-owned signal used to cancel the provider request when the stream\n * deadline fires. The caller signal remains separate, so an internal deadline\n * abort is reported as a retryable error rather than `stopReason: \"aborted\"`.\n */\nexport interface StreamDeadlineHandle {\n\treadonly deadlineMs: number | undefined;\n\treadonly signal: AbortSignal | undefined;\n\treadonly abortedByDeadline: boolean;\n\tabort(): void;\n\tcleanup(): void;\n}\n\n/**\n * Resolve the effective idle deadline for a stream.\n *\n * `undefined` selects {@link DEFAULT_STREAM_DEADLINE_MS}. A non-positive or\n * non-finite value disables the deadline and returns `undefined`, matching how\n * `httpIdleTimeoutMs` and `websocketConnectTimeoutMs` treat `0`/`\"disabled\"`.\n */\nexport function resolveStreamDeadlineMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return DEFAULT_STREAM_DEADLINE_MS;\n\tif (!Number.isFinite(value) || value <= 0) return undefined;\n\treturn Math.floor(value);\n}\n\n/**\n * Create a request signal that combines caller cancellation with a private\n * deadline controller. Calling {@link StreamDeadlineHandle.abort} aborts the\n * provider transport without marking the caller's signal as aborted.\n */\nexport function createStreamDeadline(value: number | undefined, callerSignal?: AbortSignal): StreamDeadlineHandle {\n\tconst deadlineMs = resolveStreamDeadlineMs(value);\n\tif (deadlineMs === undefined) {\n\t\treturn {\n\t\t\tdeadlineMs,\n\t\t\tsignal: callerSignal,\n\t\t\tabortedByDeadline: false,\n\t\t\tabort: () => {},\n\t\t\tcleanup: () => {},\n\t\t};\n\t}\n\n\tconst controller = new AbortController();\n\tconst combined: CombinedAbortSignal = combineAbortSignals([callerSignal, controller.signal]);\n\tlet abortedByDeadline = false;\n\treturn {\n\t\tdeadlineMs,\n\t\tsignal: combined.signal,\n\t\tget abortedByDeadline() {\n\t\t\treturn abortedByDeadline;\n\t\t},\n\t\tabort: () => {\n\t\t\tif (!controller.signal.aborted) {\n\t\t\t\tabortedByDeadline = true;\n\t\t\t\tcontroller.abort(new StreamDeadlineError(deadlineMs));\n\t\t\t}\n\t\t},\n\t\tcleanup: combined.cleanup,\n\t};\n}\n\n/**\n * Schedule a timer without allowing the platform's 32-bit delay clamp to turn\n * a valid long deadline into a one-millisecond timeout.\n */\nfunction scheduleDeadline(callback: () => void, delayMs: number): () => void {\n\tlet remaining = delayMs;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet cancelled = false;\n\n\tconst arm = () => {\n\t\tif (cancelled) return;\n\t\tconst delay = Math.min(remaining, MAX_TIMER_DELAY_MS);\n\t\ttimer = setTimeout(() => {\n\t\t\tif (cancelled) return;\n\t\t\tremaining -= delay;\n\t\t\tif (remaining <= 0) {\n\t\t\t\tcallback();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tarm();\n\t\t}, delay);\n\t};\n\n\tarm();\n\treturn () => {\n\t\tcancelled = true;\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t};\n}\n\n/**\n * Wrap an async iterable so each pending `next()` is bounded by an idle deadline.\n *\n * The timer is restarted per event, so it caps the gap between events rather than\n * total stream duration: a long but progressing stream is never cut. When the\n * deadline expires, `onDeadline` is invoked before the wrapper rejects. Adapters\n * use that callback to abort the request signal, which interrupts the pending\n * provider read and runs the source's cleanup before fallback starts.\n *\n * A deadline of `undefined` delegates straight to the source and adds no timer.\n */\nexport async function* withStreamDeadline<T>(\n\tsource: AsyncIterable<T>,\n\tdeadlineMs: number | undefined,\n\tonDeadline?: () => void,\n): AsyncGenerator<T, void, undefined> {\n\tif (deadlineMs === undefined) {\n\t\tyield* source;\n\t\treturn;\n\t}\n\n\tconst iterator = source[Symbol.asyncIterator]();\n\tlet closed = false;\n\tconst closeSource = (): void => {\n\t\tif (closed) return;\n\t\tclosed = true;\n\t\ttry {\n\t\t\tvoid Promise.resolve(iterator.return?.()).catch(() => {});\n\t\t} catch {\n\t\t\t// A source iterator without a usable `return` needs no cleanup.\n\t\t}\n\t};\n\n\ttry {\n\t\tfor (;;) {\n\t\t\tlet cancelTimer: (() => void) | undefined;\n\t\t\tlet deadlineFired = false;\n\t\t\tconst pending = Promise.resolve(iterator.next());\n\t\t\tconst deadline = new Promise<never>((_resolve, reject) => {\n\t\t\t\tcancelTimer = scheduleDeadline(() => {\n\t\t\t\t\tdeadlineFired = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonDeadline?.();\n\t\t\t\t\t} finally {\n\t\t\t\t\t\treject(new StreamDeadlineError(deadlineMs));\n\t\t\t\t\t}\n\t\t\t\t}, deadlineMs);\n\t\t\t});\n\n\t\t\tlet result: IteratorResult<T>;\n\t\t\ttry {\n\t\t\t\tresult = await Promise.race([pending, deadline]);\n\t\t\t} catch (error) {\n\t\t\t\t// The transport abort normally rejects the losing read. Swallow its\n\t\t\t\t// eventual rejection, but preserve the deadline error as the public\n\t\t\t\t// failure even if abort delivery wins the race by a microtask.\n\t\t\t\tvoid pending.catch(() => {});\n\t\t\t\tcloseSource();\n\t\t\t\tif (deadlineFired) throw new StreamDeadlineError(deadlineMs);\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\tcancelTimer?.();\n\t\t\t}\n\n\t\t\tif (result.done) {\n\t\t\t\tclosed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tyield result.value;\n\t\t}\n\t} finally {\n\t\tcloseSource();\n\t}\n}\n"]}