@earendil-works/pi-ai 0.85.0 → 0.86.0

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 (151) hide show
  1. package/README.md +48 -6
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +130 -86
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  6. package/dist/api/azure-openai-responses.js +17 -5
  7. package/dist/api/azure-openai-responses.js.map +1 -1
  8. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  9. package/dist/api/bedrock-converse-stream.js +13 -12
  10. package/dist/api/bedrock-converse-stream.js.map +1 -1
  11. package/dist/api/google-generative-ai.d.ts.map +1 -1
  12. package/dist/api/google-generative-ai.js +21 -73
  13. package/dist/api/google-generative-ai.js.map +1 -1
  14. package/dist/api/google-shared.d.ts +13 -4
  15. package/dist/api/google-shared.d.ts.map +1 -1
  16. package/dist/api/google-shared.js +55 -4
  17. package/dist/api/google-shared.js.map +1 -1
  18. package/dist/api/google-vertex.d.ts.map +1 -1
  19. package/dist/api/google-vertex.js +25 -65
  20. package/dist/api/google-vertex.js.map +1 -1
  21. package/dist/api/mistral-conversations.d.ts.map +1 -1
  22. package/dist/api/mistral-conversations.js +19 -12
  23. package/dist/api/mistral-conversations.js.map +1 -1
  24. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  25. package/dist/api/openai-codex-responses.js +26 -23
  26. package/dist/api/openai-codex-responses.js.map +1 -1
  27. package/dist/api/openai-completions.d.ts +6 -4
  28. package/dist/api/openai-completions.d.ts.map +1 -1
  29. package/dist/api/openai-completions.js +37 -55
  30. package/dist/api/openai-completions.js.map +1 -1
  31. package/dist/api/openai-responses-shared.d.ts +7 -5
  32. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  33. package/dist/api/openai-responses-shared.js +56 -56
  34. package/dist/api/openai-responses-shared.js.map +1 -1
  35. package/dist/api/openai-responses.d.ts.map +1 -1
  36. package/dist/api/openai-responses.js +26 -21
  37. package/dist/api/openai-responses.js.map +1 -1
  38. package/dist/api/pi-messages.d.ts +3 -3
  39. package/dist/api/pi-messages.d.ts.map +1 -1
  40. package/dist/api/pi-messages.js +2 -2
  41. package/dist/api/pi-messages.js.map +1 -1
  42. package/dist/api/simple-options.d.ts +3 -3
  43. package/dist/api/simple-options.d.ts.map +1 -1
  44. package/dist/api/simple-options.js.map +1 -1
  45. package/dist/api/transform-messages.d.ts.map +1 -1
  46. package/dist/api/transform-messages.js +21 -7
  47. package/dist/api/transform-messages.js.map +1 -1
  48. package/dist/compat.d.ts +3 -3
  49. package/dist/compat.d.ts.map +1 -1
  50. package/dist/compat.js +9 -6
  51. package/dist/compat.js.map +1 -1
  52. package/dist/image-models.generated.d.ts +60 -0
  53. package/dist/image-models.generated.d.ts.map +1 -1
  54. package/dist/image-models.generated.js +62 -2
  55. package/dist/image-models.generated.js.map +1 -1
  56. package/dist/index.d.ts +2 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/models.d.ts +4 -3
  61. package/dist/models.d.ts.map +1 -1
  62. package/dist/models.generated.d.ts +2 -0
  63. package/dist/models.generated.d.ts.map +1 -1
  64. package/dist/models.generated.js +2 -0
  65. package/dist/models.generated.js.map +1 -1
  66. package/dist/models.js +5 -2
  67. package/dist/models.js.map +1 -1
  68. package/dist/providers/data/.manifest.json +1 -1
  69. package/dist/providers/data/amazon-bedrock.json +1 -1
  70. package/dist/providers/data/anthropic.json +1 -1
  71. package/dist/providers/data/azure-openai-responses.json +1 -1
  72. package/dist/providers/data/baseten.json +1 -1
  73. package/dist/providers/data/cerebras.json +1 -1
  74. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  75. package/dist/providers/data/deepseek.json +1 -1
  76. package/dist/providers/data/fireworks.json +1 -1
  77. package/dist/providers/data/github-copilot.json +1 -1
  78. package/dist/providers/data/google-vertex.json +1 -1
  79. package/dist/providers/data/google.json +1 -1
  80. package/dist/providers/data/huggingface.json +1 -1
  81. package/dist/providers/data/kimi-coding.json +1 -1
  82. package/dist/providers/data/mistral.json +1 -1
  83. package/dist/providers/data/moonshotai-cn.json +1 -1
  84. package/dist/providers/data/moonshotai.json +1 -1
  85. package/dist/providers/data/nvidia.json +1 -1
  86. package/dist/providers/data/openai-codex.json +1 -1
  87. package/dist/providers/data/openai.json +1 -1
  88. package/dist/providers/data/opencode-go.json +1 -1
  89. package/dist/providers/data/opencode.json +1 -1
  90. package/dist/providers/data/openrouter.json +1 -1
  91. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  92. package/dist/providers/data/qwen-token-plan.json +1 -1
  93. package/dist/providers/data/radius.json +1 -0
  94. package/dist/providers/data/together.json +1 -1
  95. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  96. package/dist/providers/data/zai-coding-cn.json +1 -1
  97. package/dist/providers/data/zai.json +1 -1
  98. package/dist/providers/faux.d.ts +2 -2
  99. package/dist/providers/faux.d.ts.map +1 -1
  100. package/dist/providers/faux.js +11 -11
  101. package/dist/providers/faux.js.map +1 -1
  102. package/dist/providers/opencode-go.d.ts.map +1 -1
  103. package/dist/providers/opencode-go.js +4 -3
  104. package/dist/providers/opencode-go.js.map +1 -1
  105. package/dist/providers/opencode-headers.d.ts +4 -0
  106. package/dist/providers/opencode-headers.d.ts.map +1 -0
  107. package/dist/providers/opencode-headers.js +22 -0
  108. package/dist/providers/opencode-headers.js.map +1 -0
  109. package/dist/providers/opencode.d.ts.map +1 -1
  110. package/dist/providers/opencode.js +5 -4
  111. package/dist/providers/opencode.js.map +1 -1
  112. package/dist/providers/radius.d.ts.map +1 -1
  113. package/dist/providers/radius.js +19 -5
  114. package/dist/providers/radius.js.map +1 -1
  115. package/dist/providers/radius.models.d.ts +4 -0
  116. package/dist/providers/radius.models.d.ts.map +1 -0
  117. package/dist/providers/radius.models.js +6 -0
  118. package/dist/providers/radius.models.js.map +1 -0
  119. package/dist/types.d.ts +99 -28
  120. package/dist/types.d.ts.map +1 -1
  121. package/dist/types.js.map +1 -1
  122. package/dist/utils/diagnostics.d.ts +3 -2
  123. package/dist/utils/diagnostics.d.ts.map +1 -1
  124. package/dist/utils/diagnostics.js.map +1 -1
  125. package/dist/utils/estimate.d.ts +2 -2
  126. package/dist/utils/estimate.d.ts.map +1 -1
  127. package/dist/utils/estimate.js +8 -29
  128. package/dist/utils/estimate.js.map +1 -1
  129. package/dist/utils/event-stream.d.ts.map +1 -1
  130. package/dist/utils/event-stream.js +25 -7
  131. package/dist/utils/event-stream.js.map +1 -1
  132. package/dist/utils/overflow.d.ts.map +1 -1
  133. package/dist/utils/overflow.js +8 -3
  134. package/dist/utils/overflow.js.map +1 -1
  135. package/dist/utils/retry.d.ts +6 -1
  136. package/dist/utils/retry.d.ts.map +1 -1
  137. package/dist/utils/retry.js +9 -1
  138. package/dist/utils/retry.js.map +1 -1
  139. package/dist/utils/text.d.ts +9 -1
  140. package/dist/utils/text.d.ts.map +1 -1
  141. package/dist/utils/text.js +26 -0
  142. package/dist/utils/text.js.map +1 -1
  143. package/dist/utils/transcript.d.ts +84 -0
  144. package/dist/utils/transcript.d.ts.map +1 -0
  145. package/dist/utils/transcript.js +204 -0
  146. package/dist/utils/transcript.js.map +1 -0
  147. package/package.json +9 -9
  148. package/dist/utils/deferred-tools.d.ts +0 -9
  149. package/dist/utils/deferred-tools.d.ts.map +0 -1
  150. package/dist/utils/deferred-tools.js +0 -36
  151. package/dist/utils/deferred-tools.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"openai-codex-responses.d.ts","sourceRoot":"","sources":["../../src/api/openai-codex-responses.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,6BAA6B,EAG7B,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,EAOX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAEb,MAAM,aAAa,CAAC;AAkDrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IACnF,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,EAAE,6BAA6B,CAAC,cAAc,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;CAC1C;AAwJD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CAoQxF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAqBtF,CAAC;AA0VF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA0BD,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,8BAA8B,GAAG,SAAS,CAG/G;AAED,wBAAgB,mCAAmC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5E;AAED,wBAAgB,iCAAiC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAc1E","sourcesContent":["import type * as NodeZlib from \"node:zlib\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\n\nimport { clampThinkingLevel } from \"../models.ts\";\nimport { registerSessionResourceCleanup } from \"../session-resources.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tUsage,\n} from \"../types.ts\";\nimport { combineAbortSignals } from \"../utils/abort-signals.ts\";\nimport { splitDeferredTools } from \"../utils/deferred-tools.ts\";\nimport {\n\tappendAssistantMessageDiagnostic,\n\tcreateAssistantMessageDiagnostic,\n\tformatThrownValue,\n} from \"../utils/diagnostics.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { resolveHttpProxyUrlForTarget } from \"../utils/node-http-proxy.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\nimport { uuidv7 } from \"../utils/uuid.ts\";\nimport { createGrammarToolInputProperties } from \"./constrained-sampling.ts\";\nimport { clampOpenAIPromptCacheKey } from \"./openai-prompt-cache.ts\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\" as const;\nconst DEFAULT_MAX_RETRIES = 0;\nconst BASE_DELAY_MS = 1000;\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\nconst DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;\n// The Codex backend accepts zstd-compressed request bodies on the SSE responses\n// endpoint (the same endpoint the official Codex client compresses against).\nconst REQUEST_COMPRESSION_ZSTD_LEVEL = 3;\nconst CODEX_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\"]);\nconst WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;\nconst WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = \"websocket_connection_limit_reached\";\nconst PREVIOUS_RESPONSE_NOT_FOUND_CODE = \"previous_response_not_found\";\n\nconst CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([\n\t\"completed\",\n\t\"incomplete\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"queued\",\n\t\"in_progress\",\n]);\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OpenAICodexResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"concise\" | \"detailed\" | \"off\" | \"on\" | null;\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttextVerbosity?: \"low\" | \"medium\" | \"high\";\n\ttoolChoice?: \"auto\" | \"none\" | \"required\";\n}\n\ntype CodexResponseStatus = \"completed\" | \"incomplete\" | \"failed\" | \"cancelled\" | \"queued\" | \"in_progress\";\n\ninterface RequestBody {\n\tmodel: string;\n\tstore?: boolean;\n\tstream?: boolean;\n\tinstructions?: string;\n\tprevious_response_id?: string;\n\tinput?: ResponseInput;\n\ttools?: OpenAITool[];\n\ttool_choice?: OpenAICodexResponsesOptions[\"toolChoice\"];\n\tparallel_tool_calls?: boolean;\n\ttemperature?: number;\n\treasoning?: { effort?: string; summary?: string };\n\tservice_tier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttext?: { verbosity?: string };\n\tinclude?: string[];\n\tprompt_cache_key?: string;\n\t[key: string]: unknown;\n}\n\ntype SuccessfulAssistantMessage = AssistantMessage & { stopReason: \"stop\" | \"length\" | \"toolUse\" };\n\nfunction assertSuccessfulOutput(output: AssistantMessage): asserts output is SuccessfulAssistantMessage {\n\tif (output.stopReason === \"pending\") {\n\t\tthrow new Error(\"Codex stream ended without a stop reason\");\n\t}\n\tif (output.stopReason === \"error\" || output.stopReason === \"aborted\") {\n\t\tthrow new Error(output.errorMessage || \"An unknown error occurred\");\n\t}\n}\n\n// ============================================================================\n// Retry Helpers\n// ============================================================================\n\nfunction isTerminalRateLimitError(errorText: string): boolean {\n\treturn /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(\n\t\terrorText,\n\t);\n}\n\nfunction isRetryableError(status: number, errorText: string): boolean {\n\tif (status === 429 && isTerminalRateLimitError(errorText)) {\n\t\treturn false;\n\t}\n\tif (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {\n\t\treturn true;\n\t}\n\treturn /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);\n}\n\nfunction getRetryAfterDelayMs(headers: Headers): number | undefined {\n\tconst retryAfterMs = headers.get(\"retry-after-ms\");\n\tif (retryAfterMs !== null) {\n\t\tconst millis = Number(retryAfterMs);\n\t\tif (Number.isFinite(millis)) {\n\t\t\treturn Math.max(0, millis);\n\t\t}\n\t}\n\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (!retryAfter) {\n\t\treturn undefined;\n\t}\n\n\tconst seconds = Number(retryAfter);\n\tif (Number.isFinite(seconds)) {\n\t\treturn Math.max(0, seconds * 1000);\n\t}\n\n\tconst date = Date.parse(retryAfter);\n\tif (!Number.isNaN(date)) {\n\t\treturn Math.max(0, date - Date.now());\n\t}\n\n\treturn undefined;\n}\n\nclass RetryDelayExceededError extends Error {}\n\nfunction validateRetryDelayMs(delayMs: number, options?: StreamOptions): number {\n\tconst maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxRetryDelayMs > 0 && delayMs > maxRetryDelayMs) {\n\t\tthrow new RetryDelayExceededError(\n\t\t\t`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxRetryDelayMs / 1000)}s)`,\n\t\t);\n\t}\n\treturn delayMs;\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 Error(\"Request was aborted\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\"abort\", () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t});\n\t});\n}\n\nfunction normalizeTimeoutMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`Invalid timeoutMs: ${String(value)}`);\n\t}\n\treturn Math.floor(value);\n}\n\n// ============================================================================\n// Request Compression\n// ============================================================================\n\ntype ProcessWithBuiltinModule = typeof process & {\n\tgetBuiltinModule?: (id: \"node:zlib\") => typeof NodeZlib;\n};\n\nfunction loadNodeZlib(): typeof NodeZlib | null {\n\tif (typeof process === \"undefined\" || !(process.versions?.node || process.versions?.bun)) {\n\t\treturn null;\n\t}\n\treturn (process as ProcessWithBuiltinModule).getBuiltinModule?.(\"node:zlib\") ?? null;\n}\n\n// Returns the zstd-compressed body bytes, or null when compression is\n// unavailable (browser/Vite builds). Callers fall back to sending the\n// uncompressed JSON when this returns null.\nfunction compressRequestBodyZstd(bodyJson: string): Uint8Array | null {\n\tconst zlib = loadNodeZlib();\n\tif (!zlib || typeof zlib.zstdCompressSync !== \"function\") {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst compressed = zlib.zstdCompressSync(bodyJson, {\n\t\t\tparams: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },\n\t\t});\n\t\treturn new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ============================================================================\n// Main Stream Function\n// ============================================================================\n\nexport const stream: StreamFunction<\"openai-codex-responses\", OpenAICodexResponsesOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: OpenAICodexResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"openai-codex-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"pending\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst apiKey = options?.apiKey;\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t\t\t}\n\n\t\t\tconst accountId = extractAccountId(apiKey);\n\t\t\tconst grammarToolInputProperties = createGrammarToolInputProperties(\n\t\t\t\tcontext.tools,\n\t\t\t\tmodel.compat?.supportsOpenAIGrammarTools ?? false,\n\t\t\t);\n\t\t\tconst cacheSessionId = options?.cacheRetention === \"none\" ? undefined : options?.sessionId;\n\t\t\tconst codexSessionId = clampOpenAIPromptCacheKey(cacheSessionId);\n\t\t\tlet body = buildRequestBody(model, context, options, codexSessionId, grammarToolInputProperties);\n\t\t\tconst nextBody = await options?.onPayload?.(body, model);\n\t\t\tif (nextBody !== undefined) {\n\t\t\t\tbody = nextBody as RequestBody;\n\t\t\t}\n\t\t\tconst websocketRequestId = codexSessionId || uuidv7();\n\t\t\tconst sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);\n\t\t\tconst websocketHeaders = buildWebSocketHeaders(\n\t\t\t\tmodel.headers,\n\t\t\t\toptions?.headers,\n\t\t\t\taccountId,\n\t\t\t\tapiKey,\n\t\t\t\twebsocketRequestId,\n\t\t\t);\n\t\t\tconst bodyJson = JSON.stringify(body);\n\t\t\tconst httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);\n\t\t\tconst websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);\n\t\t\tconst transport = options?.transport || \"auto\";\n\t\t\tlet startEmitted = false;\n\t\t\tconst websocketDisabledForSession = transport !== \"sse\" && isWebSocketSseFallbackActive(cacheSessionId);\n\t\t\tif (websocketDisabledForSession) {\n\t\t\t\trecordWebSocketSseFallback(cacheSessionId);\n\t\t\t}\n\n\t\t\tif (transport !== \"sse\" && !websocketDisabledForSession) {\n\t\t\t\tlet websocketStarted = false;\n\t\t\t\tlet retriedWebSocketConnectionLimit = false;\n\t\t\t\tlet retriedMissingWebSocketContinuation = false;\n\t\t\t\twhile (true) {\n\t\t\t\t\twebsocketStarted = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait processWebSocketStream(\n\t\t\t\t\t\t\tresolveCodexWebSocketUrl(model.baseUrl),\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\twebsocketHeaders,\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\twebsocketStarted = true;\n\t\t\t\t\t\t\t\tif (!startEmitted) {\n\t\t\t\t\t\t\t\t\tstartEmitted = true;\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\thttpTimeoutMs,\n\t\t\t\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\t\t\t\tcacheSessionId,\n\t\t\t\t\t\t\taccountId,\n\t\t\t\t\t\t\tgrammarToolInputProperties,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassertSuccessfulOutput(output);\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"done\",\n\t\t\t\t\t\t\treason: output.stopReason,\n\t\t\t\t\t\t\tmessage: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tstream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst aborted = options?.signal?.aborted;\n\t\t\t\t\t\tconst connectionLimitBeforeStart = !websocketStarted && isWebSocketConnectionLimitReachedError(error);\n\t\t\t\t\t\tconst previousResponseNotFound = isPreviousResponseNotFoundError(error);\n\t\t\t\t\t\tif (!aborted && previousResponseNotFound && !retriedMissingWebSocketContinuation) {\n\t\t\t\t\t\t\tretriedMissingWebSocketContinuation = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!aborted && connectionLimitBeforeStart && !retriedWebSocketConnectionLimit) {\n\t\t\t\t\t\t\tretriedWebSocketConnectionLimit = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (aborted || (isCodexNonTransportError(error) && !connectionLimitBeforeStart)) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tappendAssistantMessageDiagnostic(\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tcreateAssistantMessageDiagnostic(\"provider_transport_failure\", error, {\n\t\t\t\t\t\t\t\tconfiguredTransport: transport,\n\t\t\t\t\t\t\t\tfallbackTransport: websocketStarted ? undefined : \"sse\",\n\t\t\t\t\t\t\t\teventsEmitted: websocketStarted,\n\t\t\t\t\t\t\t\tphase: websocketStarted ? \"after_message_stream_start\" : \"before_message_stream_start\",\n\t\t\t\t\t\t\t\trequestBytes: new TextEncoder().encode(bodyJson).byteLength,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\trecordWebSocketFailure(cacheSessionId, error);\n\t\t\t\t\t\tif (websocketStarted) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trecordWebSocketSseFallback(cacheSessionId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Compress the request body once for the SSE path. The Codex backend\n\t\t\t// decodes Content-Encoding: zstd; the WebSocket transport above sends the\n\t\t\t// uncompressed JSON frame, matching the official Codex client.\n\t\t\tconst compressedBody = compressRequestBodyZstd(bodyJson);\n\t\t\tif (compressedBody) {\n\t\t\t\tsseHeaders.set(\"content-encoding\", \"zstd\");\n\t\t\t}\n\t\t\tconst sseBody: Uint8Array | string = compressedBody ?? bodyJson;\n\n\t\t\t// Fetch with retry logic for rate limits and transient errors\n\t\t\tlet response: Response | undefined;\n\t\t\tlet lastError: Error | undefined;\n\t\t\tconst maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst headerTimeoutSignal =\n\t\t\t\t\t\thttpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;\n\t\t\t\t\tconst combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresponse = await (options?.fetch ?? globalThis.fetch)(resolveCodexUrl(model.baseUrl), {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: sseHeaders,\n\t\t\t\t\t\t\tbody: sseBody,\n\t\t\t\t\t\t\tsignal: combinedSignal.signal,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tcombinedSignal.cleanup();\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onResponse?.(\n\t\t\t\t\t\t{ status: response.status, headers: headersToRecord(response.headers) },\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorText = await response.text();\n\t\t\t\t\tif (attempt < maxRetries && isRetryableError(response.status, errorText)) {\n\t\t\t\t\t\tconst retryAfterDelayMs = getRetryAfterDelayMs(response.headers);\n\t\t\t\t\t\tconst delayMs =\n\t\t\t\t\t\t\tretryAfterDelayMs === undefined\n\t\t\t\t\t\t\t\t? BASE_DELAY_MS * 2 ** attempt\n\t\t\t\t\t\t\t\t: validateRetryDelayMs(retryAfterDelayMs, options);\n\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Parse error for friendly message on final attempt or non-retryable error\n\t\t\t\t\tconst fakeResponse = new Response(errorText, {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t});\n\t\t\t\t\tconst info = await parseErrorResponse(fakeResponse);\n\t\t\t\t\tthrow new Error(info.friendlyMessage || info.message);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\t\tif (error.name === \"AbortError\" || error.message === \"Request was aborted\") {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlastError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t// Network errors are retryable\n\t\t\t\t\tif (\n\t\t\t\t\t\tattempt < maxRetries &&\n\t\t\t\t\t\t!(lastError instanceof RetryDelayExceededError) &&\n\t\t\t\t\t\t!lastError.message.includes(\"usage limit\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst delayMs = BASE_DELAY_MS * 2 ** attempt;\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow lastError;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!response?.ok) {\n\t\t\t\tthrow lastError ?? new Error(\"Failed after retries\");\n\t\t\t}\n\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"No response body\");\n\t\t\t}\n\n\t\t\tif (!startEmitted) {\n\t\t\t\tstartEmitted = true;\n\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t}\n\t\t\tawait processStream(response, output, stream, model, grammarToolInputProperties, options);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tassertSuccessfulOutput(output);\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\t// Streaming scratch buffers are only used during parsing; never persist them.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t\tdelete (block as { customInput?: unknown }).customInput;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error));\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimple: StreamFunction<\"openai-codex-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey;\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = {\n\t\t...buildBaseOptions(model, context, options, apiKey),\n\t\ttoolChoice: options?.toolChoice,\n\t} satisfies OpenAICodexResponsesOptions;\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies OpenAICodexResponsesOptions);\n};\n\n// ============================================================================\n// Request Building\n// ============================================================================\n\nfunction buildRequestBody(\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions: OpenAICodexResponsesOptions | undefined,\n\tcacheSessionId: string | undefined,\n\tgrammarToolInputProperties: ReadonlyMap<string, string> = createGrammarToolInputProperties(\n\t\tcontext.tools,\n\t\tmodel.compat?.supportsOpenAIGrammarTools ?? false,\n\t),\n): RequestBody {\n\tconst supportsStrictMode = model.compat?.supportsStrictMode ?? true;\n\tconst supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;\n\tconst deferredToolsMode = model.compat?.supportsAdditionalTools\n\t\t? \"additional-tools\"\n\t\t: model.compat?.supportsToolSearch\n\t\t\t? \"tool-search\"\n\t\t\t: undefined;\n\tconst toolPlacement = splitDeferredTools(context, deferredToolsMode !== undefined);\n\tconst messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {\n\t\tincludeSystemPrompt: false,\n\t\tgrammarToolInputProperties,\n\t\tdeferredTools: toolPlacement.deferred,\n\t\tdeferredToolsMode,\n\t\ttoolOptions: {\n\t\t\tstrict: null,\n\t\t\tsupportsStrictMode,\n\t\t\tsupportsOpenAIGrammarTools,\n\t\t},\n\t});\n\n\tconst body: RequestBody = {\n\t\tmodel: model.id,\n\t\tstore: false,\n\t\tstream: true,\n\t\tinstructions: context.systemPrompt || \"You are a helpful assistant.\",\n\t\tinput: messages,\n\t\ttext: { verbosity: options?.textVerbosity || \"low\" },\n\t\tinclude: [\"reasoning.encrypted_content\"],\n\t\tprompt_cache_key: cacheSessionId,\n\t\ttool_choice: options?.toolChoice ?? \"auto\",\n\t\tparallel_tool_calls: true,\n\t};\n\n\tif (options?.temperature !== undefined) {\n\t\tbody.temperature = options.temperature;\n\t}\n\n\tif (options?.serviceTier !== undefined) {\n\t\tbody.service_tier = options.serviceTier;\n\t}\n\n\tif (toolPlacement.immediate.length > 0) {\n\t\tbody.tools = convertResponsesTools(toolPlacement.immediate, {\n\t\t\tstrict: null,\n\t\t\tsupportsStrictMode,\n\t\t\tsupportsOpenAIGrammarTools,\n\t\t});\n\t}\n\n\tif (options?.reasoningEffort !== undefined) {\n\t\tconst effort =\n\t\t\toptions.reasoningEffort === \"none\"\n\t\t\t\t? (model.thinkingLevelMap?.off ?? \"none\")\n\t\t\t\t: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);\n\t\tif (effort !== null) {\n\t\t\tbody.reasoning = {\n\t\t\t\teffort,\n\t\t\t\tsummary: options.reasoningSummary ?? \"auto\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn body;\n}\n\nfunction getServiceTierCostMultiplier(\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): number {\n\tswitch (serviceTier) {\n\t\tcase \"flex\":\n\t\t\treturn 0.5;\n\t\tcase \"priority\":\n\t\t\treturn model.id === \"gpt-5.5\" ? 2.5 : 2;\n\t\tdefault:\n\t\t\treturn 1;\n\t}\n}\n\nfunction applyServiceTierPricing(\n\tusage: Usage,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n) {\n\tconst multiplier = getServiceTierCostMultiplier(model, serviceTier);\n\tif (multiplier === 1) return;\n\n\tusage.cost.input *= multiplier;\n\tusage.cost.output *= multiplier;\n\tusage.cost.cacheRead *= multiplier;\n\tusage.cost.cacheWrite *= multiplier;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n}\n\nfunction resolveCodexServiceTier(\n\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): ResponseCreateParamsStreaming[\"service_tier\"] | undefined {\n\tif (responseServiceTier === \"default\" && (requestServiceTier === \"flex\" || requestServiceTier === \"priority\")) {\n\t\treturn requestServiceTier;\n\t}\n\treturn responseServiceTier ?? requestServiceTier;\n}\n\nfunction resolveCodexUrl(baseUrl?: string): string {\n\tconst raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;\n\tconst normalized = raw.replace(/\\/+$/, \"\");\n\tif (normalized.endsWith(\"/codex/responses\")) return normalized;\n\tif (normalized.endsWith(\"/codex\")) return `${normalized}/responses`;\n\treturn `${normalized}/codex/responses`;\n}\n\nfunction resolveCodexWebSocketUrl(baseUrl?: string): string {\n\tconst url = new URL(resolveCodexUrl(baseUrl));\n\tif (url.protocol === \"https:\") url.protocol = \"wss:\";\n\tif (url.protocol === \"http:\") url.protocol = \"ws:\";\n\treturn url.toString();\n}\n\n// ============================================================================\n// Response Processing\n// ============================================================================\n\nasync function processStream(\n\tresponse: Response,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tgrammarToolInputProperties: ReadonlyMap<string, string>,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tawait processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output), output, stream, model, {\n\t\tserviceTier: options?.serviceTier,\n\t\tgrammarToolInputProperties,\n\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t});\n}\n\nclass CodexApiError extends Error {\n\treadonly code?: string;\n\treadonly payload?: Record<string, unknown>;\n\n\tconstructor(message: string, options?: { code?: string; payload?: Record<string, unknown>; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexApiError\";\n\t\tthis.code = options?.code;\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nclass CodexProtocolError extends Error {\n\treadonly payload?: unknown;\n\n\tconstructor(message: string, options?: { payload?: unknown; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexProtocolError\";\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nfunction isCodexNonTransportError(error: unknown): boolean {\n\treturn error instanceof CodexApiError || error instanceof CodexProtocolError;\n}\n\nfunction isWebSocketConnectionLimitReachedError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE;\n}\n\nfunction isPreviousResponseNotFoundError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === PREVIOUS_RESPONSE_NOT_FOUND_CODE;\n}\n\nfunction extractCodexEventError(event: Record<string, unknown>): { code?: string; message?: string } {\n\tconst nested = event.error && typeof event.error === \"object\" ? (event.error as Record<string, unknown>) : undefined;\n\treturn {\n\t\tcode: typeof event.code === \"string\" ? event.code : typeof nested?.code === \"string\" ? nested.code : undefined,\n\t\tmessage:\n\t\t\ttypeof event.message === \"string\"\n\t\t\t\t? event.message\n\t\t\t\t: typeof nested?.message === \"string\"\n\t\t\t\t\t? nested.message\n\t\t\t\t\t: undefined,\n\t};\n}\n\nasync function* mapCodexEvents(\n\tevents: AsyncIterable<Record<string, unknown>>,\n\toutput: AssistantMessage,\n): AsyncGenerator<ResponseStreamEvent> {\n\tfor await (const event of events) {\n\t\tconst type = typeof event.type === \"string\" ? event.type : undefined;\n\t\tif (!type) continue;\n\n\t\tif (type === \"error\") {\n\t\t\tconst { code, message } = extractCodexEventError(event);\n\t\t\tthrow new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {\n\t\t\t\tcode,\n\t\t\t\tpayload: event,\n\t\t\t});\n\t\t}\n\n\t\tif (type === \"response.failed\") {\n\t\t\tconst response = (event as { response?: { error?: { code?: string; message?: string } } }).response;\n\t\t\tconst code = response?.error?.code;\n\t\t\tconst message = response?.error?.message;\n\t\t\tthrow new CodexApiError(message || \"Codex response failed\", { code, payload: event });\n\t\t}\n\n\t\tif (type === \"response.done\" || type === \"response.completed\" || type === \"response.incomplete\") {\n\t\t\tconst response = (event as { response?: { status?: unknown; end_turn?: unknown } }).response;\n\t\t\tif (typeof response?.end_turn === \"boolean\") {\n\t\t\t\toutput.endTurn = response.end_turn;\n\t\t\t}\n\t\t\tconst normalizedResponse = response\n\t\t\t\t? { ...response, status: normalizeCodexStatus(response.status) }\n\t\t\t\t: response;\n\t\t\tyield { ...event, type: \"response.completed\", response: normalizedResponse } as ResponseStreamEvent;\n\t\t\treturn;\n\t\t}\n\n\t\tyield event as unknown as ResponseStreamEvent;\n\t}\n}\n\nfunction normalizeCodexStatus(status: unknown): CodexResponseStatus | undefined {\n\tif (typeof status !== \"string\") return undefined;\n\treturn CODEX_RESPONSE_STATUSES.has(status as CodexResponseStatus) ? (status as CodexResponseStatus) : undefined;\n}\n\n// ============================================================================\n// SSE Parsing\n// ============================================================================\n\nasync function* parseSSE(response: Response, signal?: AbortSignal): AsyncGenerator<Record<string, unknown>> {\n\tif (!response.body) return;\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = \"\";\n\tconst onAbort = () => {\n\t\tvoid reader.cancel().catch(() => {});\n\t};\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tbuffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n\t\t\t// Treat EOF as terminating the residual SSE frame.\n\t\t\tif (done && buffer.trim()) buffer += \"\\n\\n\";\n\n\t\t\tlet idx = buffer.indexOf(\"\\n\\n\");\n\t\t\twhile (idx !== -1) {\n\t\t\t\tconst chunk = buffer.slice(0, idx);\n\t\t\t\tbuffer = buffer.slice(idx + 2);\n\n\t\t\t\tconst dataLines = chunk\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((l) => l.startsWith(\"data:\"))\n\t\t\t\t\t.map((l) => l.slice(5).trim());\n\t\t\t\tif (dataLines.length > 0) {\n\t\t\t\t\tconst data = dataLines.join(\"\\n\").trim();\n\t\t\t\t\tif (data && data !== \"[DONE]\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tyield JSON.parse(data) as Record<string, unknown>;\n\t\t\t\t\t\t} catch (cause) {\n\t\t\t\t\t\t\tthrow new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\tpayload: data,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tidx = buffer.indexOf(\"\\n\\n\");\n\t\t\t}\n\n\t\t\tif (done) break;\n\t\t}\n\t} finally {\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\ttry {\n\t\t\tawait reader.cancel();\n\t\t} catch {}\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {}\n\t}\n}\n\n// ============================================================================\n// WebSocket Parsing\n// ============================================================================\n\nconst OPENAI_BETA_RESPONSES_WEBSOCKETS = \"responses_websockets=2026-02-06\";\nconst SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;\nconst SESSION_WEBSOCKET_MAX_AGE_MS = 55 * 60 * 1000;\n\ntype WebSocketEventType = \"open\" | \"message\" | \"error\" | \"close\";\ntype WebSocketListener = (event: unknown) => void;\n\ninterface WebSocketLike {\n\tclose(code?: number, reason?: string): void;\n\tsend(data: string): void;\n\taddEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n\tremoveEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n}\n\ninterface CachedWebSocketContinuationState {\n\tlastRequestBody: RequestBody;\n\tlastResponseId: string;\n\tlastResponseItems: ResponseInput;\n}\n\ninterface CachedWebSocketConnection {\n\tsocket: WebSocketLike;\n\tbusy: boolean;\n\tcreatedAt: number;\n\tidleTimer?: ReturnType<typeof setTimeout>;\n\tcontinuation?: CachedWebSocketContinuationState;\n}\n\nexport interface OpenAICodexWebSocketDebugStats {\n\trequests: number;\n\tconnectionsCreated: number;\n\tconnectionsReused: number;\n\tcachedContextRequests: number;\n\tstoreTrueRequests: number;\n\tfullContextRequests: number;\n\tdeltaRequests: number;\n\tlastInputItems: number;\n\tlastDeltaInputItems?: number;\n\tlastPreviousResponseId?: string;\n\twebsocketFailures: number;\n\tsseFallbacks: number;\n\twebsocketFallbackActive?: boolean;\n\tlastWebSocketError?: string;\n}\n\nconst websocketSessionCache = new Map<string, Map<string, CachedWebSocketConnection>>();\nconst websocketDebugStats = new Map<string, OpenAICodexWebSocketDebugStats>();\nconst websocketSseFallbackSessions = new Set<string>();\n\nfunction getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats {\n\tlet stats = websocketDebugStats.get(sessionId);\n\tif (!stats) {\n\t\tstats = {\n\t\t\trequests: 0,\n\t\t\tconnectionsCreated: 0,\n\t\t\tconnectionsReused: 0,\n\t\t\tcachedContextRequests: 0,\n\t\t\tstoreTrueRequests: 0,\n\t\t\tfullContextRequests: 0,\n\t\t\tdeltaRequests: 0,\n\t\t\tlastInputItems: 0,\n\t\t\twebsocketFailures: 0,\n\t\t\tsseFallbacks: 0,\n\t\t};\n\t\twebsocketDebugStats.set(sessionId, stats);\n\t}\n\treturn stats;\n}\n\nexport function getOpenAICodexWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined {\n\tconst stats = websocketDebugStats.get(sessionId);\n\treturn stats ? { ...stats } : undefined;\n}\n\nexport function resetOpenAICodexWebSocketDebugStats(sessionId?: string): void {\n\tif (sessionId) {\n\t\twebsocketDebugStats.delete(sessionId);\n\t\twebsocketSseFallbackSessions.delete(sessionId);\n\t\treturn;\n\t}\n\twebsocketDebugStats.clear();\n\twebsocketSseFallbackSessions.clear();\n}\n\nexport function closeOpenAICodexWebSocketSessions(sessionId?: string): void {\n\tconst closeEntry = (entry: CachedWebSocketConnection) => {\n\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"debug_close\");\n\t};\n\tif (sessionId) {\n\t\tfor (const entry of websocketSessionCache.get(sessionId)?.values() ?? []) closeEntry(entry);\n\t\twebsocketSessionCache.delete(sessionId);\n\t\treturn;\n\t}\n\tfor (const accountEntries of websocketSessionCache.values()) {\n\t\tfor (const entry of accountEntries.values()) closeEntry(entry);\n\t}\n\twebsocketSessionCache.clear();\n}\n\nregisterSessionResourceCleanup(closeOpenAICodexWebSocketSessions);\n\nfunction isWebSocketSseFallbackActive(sessionId: string | undefined): boolean {\n\treturn sessionId ? websocketSseFallbackSessions.has(sessionId) : false;\n}\n\nfunction recordWebSocketSseFallback(sessionId: string | undefined): void {\n\tif (!sessionId) return;\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.sseFallbacks++;\n\tstats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);\n}\n\nfunction recordWebSocketFailure(sessionId: string | undefined, error: unknown): void {\n\tif (!sessionId) return;\n\twebsocketSseFallbackSessions.add(sessionId);\n\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.websocketFailures++;\n\tstats.lastWebSocketError = formatThrownValue(error);\n\tstats.websocketFallbackActive = true;\n}\n\ntype WebSocketConstructor = new (\n\turl: string,\n\tprotocols?: string | string[] | { headers?: Record<string, string> },\n) => WebSocketLike;\n\nlet _cachedWebsocket: WebSocketConstructor | null = null;\nasync function getWebSocketConstructor(env?: ProviderEnv): Promise<WebSocketConstructor | null> {\n\tif (!env && _cachedWebsocket) return _cachedWebsocket;\n\n\t// bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489\n\t// TODO: remove this when bun supports proxy envs in websocket.\n\tif (typeof process !== \"undefined\" && process.versions?.bun) {\n\t\tconst WebSocketWithProxy = class extends WebSocket {\n\t\t\tconstructor(url: string | URL, options?: string | string[] | Record<string, unknown>) {\n\t\t\t\tlet _opts: Record<string, unknown> = {};\n\t\t\t\tif (Array.isArray(options) || typeof options === \"string\") {\n\t\t\t\t\t_opts = { protocols: options };\n\t\t\t\t} else {\n\t\t\t\t\t_opts = { ...options };\n\t\t\t\t}\n\n\t\t\t\tconst proxyUrl = resolveHttpProxyUrlForTarget(\n\t\t\t\t\turl.toString().replace(/^wss:/, \"https:\").replace(/^ws:/, \"http:\"),\n\t\t\t\t\tenv,\n\t\t\t\t);\n\t\t\t\tsuper(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) } as any);\n\t\t\t}\n\t\t};\n\t\tif (!env) {\n\t\t\t_cachedWebsocket = WebSocketWithProxy;\n\t\t}\n\t\treturn WebSocketWithProxy;\n\t}\n\n\tconst ctor = (globalThis as { WebSocket?: unknown }).WebSocket;\n\tif (typeof ctor !== \"function\") return null;\n\treturn ctor as unknown as WebSocketConstructor;\n}\n\nclass WebSocketCloseError extends Error {\n\treadonly code?: number;\n\treadonly reason?: string;\n\treadonly wasClean?: boolean;\n\n\tconstructor(message: string, options?: { code?: number; reason?: string; wasClean?: boolean }) {\n\t\tsuper(message);\n\t\tthis.name = \"WebSocketCloseError\";\n\t\tthis.code = options?.code;\n\t\tthis.reason = options?.reason;\n\t\tthis.wasClean = options?.wasClean;\n\t}\n}\n\nfunction getWebSocketReadyState(socket: WebSocketLike): number | undefined {\n\tconst readyState = (socket as { readyState?: unknown }).readyState;\n\treturn typeof readyState === \"number\" ? readyState : undefined;\n}\n\nfunction isWebSocketReusable(socket: WebSocketLike): boolean {\n\tconst readyState = getWebSocketReadyState(socket);\n\t// If readyState is unavailable, assume the runtime keeps it open/reusable.\n\treturn readyState === undefined || readyState === 1;\n}\n\nfunction isWebSocketSessionExpired(entry: CachedWebSocketConnection): boolean {\n\treturn Date.now() - entry.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;\n}\n\nfunction closeWebSocketSilently(socket: WebSocketLike, code = 1000, reason = \"done\"): void {\n\ttry {\n\t\tsocket.close(code, reason);\n\t} catch {}\n}\n\nfunction scheduleSessionWebSocketExpiry(sessionId: string, accountId: string, entry: CachedWebSocketConnection): void {\n\tif (entry.idleTimer) {\n\t\tclearTimeout(entry.idleTimer);\n\t}\n\tentry.idleTimer = setTimeout(() => {\n\t\tif (entry.busy) return;\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"idle_timeout\");\n\t\tconst accountEntries = websocketSessionCache.get(sessionId);\n\t\tif (accountEntries?.get(accountId) === entry) accountEntries.delete(accountId);\n\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t}, SESSION_WEBSOCKET_CACHE_TTL_MS);\n}\n\nasync function connectWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS,\n\tenv?: ProviderEnv,\n): Promise<WebSocketLike> {\n\tconst WebSocketCtor = await getWebSocketConstructor(env);\n\tif (!WebSocketCtor) {\n\t\tthrow new Error(\"WebSocket transport is not available in this runtime\");\n\t}\n\n\tconst wsHeaders = headersToRecord(headers);\n\tdelete wsHeaders[\"OpenAI-Beta\"];\n\n\treturn new Promise<WebSocketLike>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tlet socket: WebSocketLike;\n\n\t\ttry {\n\t\t\tsocket = new WebSocketCtor(url, { headers: wsHeaders });\n\t\t} catch (error) {\n\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\treturn;\n\t\t}\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeout) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\ttimeout = undefined;\n\t\t\t}\n\t\t\tsocket.removeEventListener(\"open\", onOpen);\n\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst fail = (error: Error, closeReason?: string) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tif (closeReason) {\n\t\t\t\tcloseWebSocketSilently(socket, 1000, closeReason);\n\t\t\t}\n\t\t\treject(error);\n\t\t};\n\t\tconst onOpen: WebSocketListener = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tresolve(socket);\n\t\t};\n\t\tconst onError: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketError(event));\n\t\t};\n\t\tconst onClose: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketCloseError(event));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tfail(new Error(\"Request was aborted\"), \"aborted\");\n\t\t};\n\n\t\tsocket.addEventListener(\"open\", onOpen);\n\t\tsocket.addEventListener(\"error\", onError);\n\t\tsocket.addEventListener(\"close\", onClose);\n\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\tif (connectTimeoutMs > 0) {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\tfail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), \"connect_timeout\");\n\t\t\t}, connectTimeoutMs);\n\t\t}\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t}\n\t});\n}\n\nasync function acquireWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsessionId: string | undefined,\n\taccountId: string,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs?: number,\n\tenv?: ProviderEnv,\n): Promise<{\n\tsocket: WebSocketLike;\n\tentry?: CachedWebSocketConnection;\n\treused: boolean;\n\trelease: (options?: { keep?: boolean }) => void;\n}> {\n\tif (!sessionId) {\n\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\treturn {\n\t\t\tsocket,\n\t\t\treused: false,\n\t\t\trelease: () => closeWebSocketSilently(socket),\n\t\t};\n\t}\n\n\tlet accountEntries = websocketSessionCache.get(sessionId);\n\tconst cached = accountEntries?.get(accountId);\n\tif (cached) {\n\t\tif (cached.idleTimer) {\n\t\t\tclearTimeout(cached.idleTimer);\n\t\t\tcached.idleTimer = undefined;\n\t\t}\n\t\tif (!cached.busy && isWebSocketSessionExpired(cached)) {\n\t\t\tcloseWebSocketSilently(cached.socket, 1000, \"connection_age_limit\");\n\t\t\taccountEntries?.delete(accountId);\n\t\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t} else if (!cached.busy && isWebSocketReusable(cached.socket)) {\n\t\t\tcached.busy = true;\n\t\t\treturn {\n\t\t\t\tsocket: cached.socket,\n\t\t\t\tentry: cached,\n\t\t\t\treused: true,\n\t\t\t\trelease: ({ keep } = {}) => {\n\t\t\t\t\tif (!keep || !isWebSocketReusable(cached.socket)) {\n\t\t\t\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\t\t\t\tconst currentEntries = websocketSessionCache.get(sessionId);\n\t\t\t\t\t\tif (currentEntries?.get(accountId) === cached) currentEntries.delete(accountId);\n\t\t\t\t\t\tif (currentEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcached.busy = false;\n\t\t\t\t\tscheduleSessionWebSocketExpiry(sessionId, accountId, cached);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (cached.busy) {\n\t\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\t\treturn {\n\t\t\t\tsocket,\n\t\t\t\treused: false,\n\t\t\t\trelease: () => {\n\t\t\t\t\tcloseWebSocketSilently(socket);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (!isWebSocketReusable(cached.socket)) {\n\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\taccountEntries?.delete(accountId);\n\t\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t}\n\t}\n\n\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\tconst entry: CachedWebSocketConnection = { socket, busy: true, createdAt: Date.now() };\n\taccountEntries = websocketSessionCache.get(sessionId);\n\tif (!accountEntries) {\n\t\taccountEntries = new Map();\n\t\twebsocketSessionCache.set(sessionId, accountEntries);\n\t}\n\taccountEntries.set(accountId, entry);\n\treturn {\n\t\tsocket,\n\t\tentry,\n\t\treused: false,\n\t\trelease: ({ keep } = {}) => {\n\t\t\tif (!keep || !isWebSocketReusable(entry.socket)) {\n\t\t\t\tcloseWebSocketSilently(entry.socket);\n\t\t\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\t\t\tconst currentEntries = websocketSessionCache.get(sessionId);\n\t\t\t\tif (currentEntries?.get(accountId) === entry) currentEntries.delete(accountId);\n\t\t\t\tif (currentEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentry.busy = false;\n\t\t\tscheduleSessionWebSocketExpiry(sessionId, accountId, entry);\n\t\t},\n\t};\n}\n\nfunction extractWebSocketError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst message = \"message\" in event ? (event as { message?: unknown }).message : undefined;\n\t\tif (typeof message === \"string\" && message.length > 0) {\n\t\t\treturn new Error(message);\n\t\t}\n\n\t\tconst nestedError = \"error\" in event ? (event as { error?: unknown }).error : undefined;\n\t\tif (nestedError instanceof Error && nestedError.message.length > 0) {\n\t\t\treturn nestedError;\n\t\t}\n\t\tif (nestedError && typeof nestedError === \"object\" && \"message\" in nestedError) {\n\t\t\tconst nestedMessage = (nestedError as { message?: unknown }).message;\n\t\t\tif (typeof nestedMessage === \"string\" && nestedMessage.length > 0) {\n\t\t\t\treturn new Error(nestedMessage);\n\t\t\t}\n\t\t}\n\t}\n\treturn new Error(\"WebSocket error\");\n}\n\nfunction extractWebSocketCloseError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst code = \"code\" in event ? (event as { code?: unknown }).code : undefined;\n\t\tconst reason = \"reason\" in event ? (event as { reason?: unknown }).reason : undefined;\n\t\tconst wasClean = \"wasClean\" in event ? (event as { wasClean?: unknown }).wasClean : undefined;\n\t\tconst codeText = typeof code === \"number\" ? ` ${code}` : \"\";\n\t\tlet reasonText = typeof reason === \"string\" && reason.length > 0 ? ` ${reason}` : \"\";\n\t\tif (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {\n\t\t\treasonText = \" message too big\";\n\t\t}\n\t\treturn new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {\n\t\t\tcode: typeof code === \"number\" ? code : undefined,\n\t\t\treason: typeof reason === \"string\" && reason.length > 0 ? reason : undefined,\n\t\t\twasClean: typeof wasClean === \"boolean\" ? wasClean : undefined,\n\t\t});\n\t}\n\treturn new Error(\"WebSocket closed\");\n}\n\nasync function decodeWebSocketData(data: unknown): Promise<string | null> {\n\tif (typeof data === \"string\") return data;\n\tif (data instanceof ArrayBuffer) {\n\t\treturn new TextDecoder().decode(new Uint8Array(data));\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst view = data as ArrayBufferView;\n\t\treturn new TextDecoder().decode(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));\n\t}\n\tif (data && typeof data === \"object\" && \"arrayBuffer\" in data) {\n\t\tconst blobLike = data as { arrayBuffer: () => Promise<ArrayBuffer> };\n\t\tconst arrayBuffer = await blobLike.arrayBuffer();\n\t\treturn new TextDecoder().decode(new Uint8Array(arrayBuffer));\n\t}\n\treturn null;\n}\n\nasync function* parseWebSocket(\n\tsocket: WebSocketLike,\n\tsignal?: AbortSignal,\n\tidleTimeoutMs?: number,\n): AsyncGenerator<Record<string, unknown>> {\n\tconst queue: Record<string, unknown>[] = [];\n\tlet pending: (() => void) | null = null;\n\tlet done = false;\n\tlet failed: Error | null = null;\n\tlet sawCompletion = false;\n\n\tconst wake = () => {\n\t\tif (!pending) return;\n\t\tconst resolve = pending;\n\t\tpending = null;\n\t\tresolve();\n\t};\n\n\tconst onMessage: WebSocketListener = (event) => {\n\t\tvoid (async () => {\n\t\t\tlet text: string | null = null;\n\t\t\ttry {\n\t\t\t\tif (!event || typeof event !== \"object\" || !(\"data\" in event)) return;\n\t\t\t\ttext = await decodeWebSocketData((event as { data?: unknown }).data);\n\t\t\t\tif (!text) return;\n\t\t\t\tconst parsed = JSON.parse(text) as Record<string, unknown>;\n\t\t\t\tconst type = typeof parsed.type === \"string\" ? parsed.type : \"\";\n\t\t\t\tif (type === \"response.completed\" || type === \"response.done\" || type === \"response.incomplete\") {\n\t\t\t\t\tsawCompletion = true;\n\t\t\t\t\tdone = true;\n\t\t\t\t}\n\t\t\t\tqueue.push(parsed);\n\t\t\t\twake();\n\t\t\t} catch (cause) {\n\t\t\t\tfailed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\tcause,\n\t\t\t\t\tpayload: text,\n\t\t\t\t});\n\t\t\t\tdone = true;\n\t\t\t\twake();\n\t\t\t}\n\t\t})();\n\t};\n\n\tconst onError: WebSocketListener = (event) => {\n\t\tfailed = extractWebSocketError(event);\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onClose: WebSocketListener = (event) => {\n\t\tif (sawCompletion) {\n\t\t\tdone = true;\n\t\t\twake();\n\t\t\treturn;\n\t\t}\n\t\tif (!failed) {\n\t\t\tfailed = extractWebSocketCloseError(event);\n\t\t}\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onAbort = () => {\n\t\tfailed = new Error(\"Request was aborted\");\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tsocket.addEventListener(\"message\", onMessage);\n\tsocket.addEventListener(\"error\", onError);\n\tsocket.addEventListener(\"close\", onClose);\n\tsignal?.addEventListener(\"abort\", onAbort);\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (queue.length > 0) {\n\t\t\t\tyield queue.shift()!;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tpending = resolve;\n\t\t\t\tif (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {\n\t\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\t\tconst error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);\n\t\t\t\t\t\tfailed = error;\n\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\tpending = null;\n\t\t\t\t\t\tcloseWebSocketSilently(socket, 1000, \"idle_timeout\");\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}, idleTimeoutMs);\n\t\t\t\t}\n\t\t\t}).finally(() => {\n\t\t\t\tif (timeout) {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (failed) {\n\t\t\tthrow failed;\n\t\t}\n\t\tif (!sawCompletion) {\n\t\t\tthrow new Error(\"WebSocket stream closed before response.completed\");\n\t\t}\n\t} finally {\n\t\tsocket.removeEventListener(\"message\", onMessage);\n\t\tsocket.removeEventListener(\"error\", onError);\n\t\tsocket.removeEventListener(\"close\", onClose);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nfunction requestBodyWithoutInput(body: RequestBody): RequestBody {\n\tconst { input: _input, previous_response_id: _previousResponseId, ...rest } = body;\n\treturn rest;\n}\n\nfunction responseInputsEqual(a: ResponseInput | undefined, b: ResponseInput | undefined): boolean {\n\treturn JSON.stringify(a ?? []) === JSON.stringify(b ?? []);\n}\n\nfunction requestBodiesMatchExceptInput(a: RequestBody, b: RequestBody): boolean {\n\treturn JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));\n}\n\nfunction getCachedWebSocketInputDelta(\n\tbody: RequestBody,\n\tcontinuation: CachedWebSocketContinuationState,\n): ResponseInput | undefined {\n\tif (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {\n\t\treturn undefined;\n\t}\n\n\tconst currentInput = body.input ?? [];\n\tconst baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];\n\tif (currentInput.length < baseline.length) {\n\t\treturn undefined;\n\t}\n\n\tconst prefix = currentInput.slice(0, baseline.length);\n\tif (!responseInputsEqual(prefix, baseline)) {\n\t\treturn undefined;\n\t}\n\n\treturn currentInput.slice(baseline.length);\n}\n\nfunction buildCachedWebSocketRequestBody(entry: CachedWebSocketConnection, body: RequestBody): RequestBody {\n\tconst continuation = entry.continuation;\n\tif (!continuation) {\n\t\treturn body;\n\t}\n\n\tconst delta = getCachedWebSocketInputDelta(body, continuation);\n\tif (!delta || !continuation.lastResponseId) {\n\t\tentry.continuation = undefined;\n\t\treturn body;\n\t}\n\n\treturn {\n\t\t...body,\n\t\tprevious_response_id: continuation.lastResponseId,\n\t\tinput: delta,\n\t};\n}\n\nasync function* startWebSocketOutputOnFirstEvent(\n\tevents: AsyncIterable<ResponseStreamEvent>,\n\tonStart: () => void,\n): AsyncGenerator<ResponseStreamEvent> {\n\tlet started = false;\n\tfor await (const event of events) {\n\t\tif (!started) {\n\t\t\tstarted = true;\n\t\t\tonStart();\n\t\t}\n\t\tyield event;\n\t}\n}\n\nasync function processWebSocketStream(\n\turl: string,\n\tbody: RequestBody,\n\theaders: Headers,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tonStart: () => void,\n\tidleTimeoutMs: number | undefined,\n\twebsocketConnectTimeoutMs: number | undefined,\n\tcacheSessionId: string | undefined,\n\taccountId: string,\n\tgrammarToolInputProperties: ReadonlyMap<string, string>,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tconst { socket, entry, reused, release } = await acquireWebSocket(\n\t\turl,\n\t\theaders,\n\t\tcacheSessionId,\n\t\taccountId,\n\t\toptions?.signal,\n\t\twebsocketConnectTimeoutMs,\n\t\toptions?.env,\n\t);\n\tlet keepConnection = true;\n\tconst useCachedContext = options?.transport === \"websocket-cached\" || options?.transport === \"auto\";\n\t// ChatGPT Codex Responses rejects `store: true` (\"Store must be set to false\").\n\t// WebSocket continuation still works via connection-scoped previous_response_id state.\n\tconst fullBody = body;\n\tconst requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;\n\tconst stats = cacheSessionId ? getOrCreateWebSocketDebugStats(cacheSessionId) : undefined;\n\tif (stats) {\n\t\tstats.requests++;\n\t\tif (reused) stats.connectionsReused++;\n\t\telse stats.connectionsCreated++;\n\t\tif (useCachedContext) stats.cachedContextRequests++;\n\t\tif (requestBody.store === true) stats.storeTrueRequests++;\n\t\tstats.lastInputItems = requestBody.input?.length ?? 0;\n\t\tif (requestBody.previous_response_id) {\n\t\t\tstats.deltaRequests++;\n\t\t\tstats.lastDeltaInputItems = requestBody.input?.length ?? 0;\n\t\t\tstats.lastPreviousResponseId = requestBody.previous_response_id;\n\t\t} else {\n\t\t\tstats.fullContextRequests++;\n\t\t\tstats.lastDeltaInputItems = undefined;\n\t\t\tstats.lastPreviousResponseId = undefined;\n\t\t}\n\t}\n\ttry {\n\t\tsocket.send(JSON.stringify({ type: \"response.create\", ...requestBody }));\n\t\tawait processResponsesStream(\n\t\t\tstartWebSocketOutputOnFirstEvent(\n\t\t\t\tmapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output),\n\t\t\t\tonStart,\n\t\t\t),\n\t\t\toutput,\n\t\t\tstream,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tserviceTier: options?.serviceTier,\n\t\t\t\tgrammarToolInputProperties,\n\t\t\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\t\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t\t\t},\n\t\t);\n\t\tif (options?.signal?.aborted) {\n\t\t\tkeepConnection = false;\n\t\t} else if (useCachedContext && entry && output.responseId) {\n\t\t\tconst responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {\n\t\t\t\tincludeSystemPrompt: false,\n\t\t\t\tgrammarToolInputProperties,\n\t\t\t}).filter((item) => item.type !== \"function_call_output\" && item.type !== \"custom_tool_call_output\");\n\t\t\tentry.continuation = {\n\t\t\t\tlastRequestBody: fullBody,\n\t\t\t\tlastResponseId: output.responseId,\n\t\t\t\tlastResponseItems: responseItems,\n\t\t\t};\n\t\t}\n\t} catch (error) {\n\t\tif (entry) {\n\t\t\tentry.continuation = undefined;\n\t\t}\n\t\tkeepConnection = false;\n\t\tthrow error;\n\t} finally {\n\t\trelease({ keep: keepConnection });\n\t}\n}\n\n// ============================================================================\n// Error Handling\n// ============================================================================\n\nasync function parseErrorResponse(response: Response): Promise<{ message: string; friendlyMessage?: string }> {\n\tconst raw = await response.text();\n\tlet message = raw || response.statusText || \"Request failed\";\n\tlet friendlyMessage: string | undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as {\n\t\t\terror?: { code?: string; type?: string; message?: string; plan_type?: string; resets_at?: number };\n\t\t};\n\t\tconst err = parsed?.error;\n\t\tif (err) {\n\t\t\tconst code = err.code || err.type || \"\";\n\t\t\tif (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) {\n\t\t\t\tconst plan = err.plan_type ? ` (${err.plan_type.toLowerCase()} plan)` : \"\";\n\t\t\t\tconst mins = err.resets_at\n\t\t\t\t\t? Math.max(0, Math.round((err.resets_at * 1000 - Date.now()) / 60000))\n\t\t\t\t\t: undefined;\n\t\t\t\tconst when = mins !== undefined ? ` Try again in ~${mins} min.` : \"\";\n\t\t\t\tfriendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();\n\t\t\t}\n\t\t\tmessage = err.message || friendlyMessage || message;\n\t\t}\n\t} catch {}\n\n\treturn { message, friendlyMessage };\n}\n\n// ============================================================================\n// Auth & Headers\n// ============================================================================\n\nfunction extractAccountId(token: string): string {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) throw new Error(\"Invalid token\");\n\t\tconst payload = JSON.parse(atob(parts[1]));\n\t\tconst accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;\n\t\tif (!accountId) throw new Error(\"No account ID in token\");\n\t\treturn accountId;\n\t} catch {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n}\n\nfunction buildBaseCodexHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n): Headers {\n\tconst headers = new Headers(initHeaders);\n\tfor (const [key, value] of Object.entries(additionalHeaders || {})) {\n\t\tif (value === null) {\n\t\t\theaders.delete(key);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"chatgpt-account-id\", accountId);\n\theaders.set(\"originator\", \"pi\");\n\theaders.set(\"User-Agent\", getPiUserAgent());\n\treturn headers;\n}\n\nfunction buildSSEHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\tsessionId?: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.set(\"OpenAI-Beta\", \"responses=experimental\");\n\theaders.set(\"accept\", \"text/event-stream\");\n\theaders.set(\"content-type\", \"application/json\");\n\n\tif (sessionId) {\n\t\theaders.set(\"session-id\", sessionId);\n\t\theaders.set(\"x-client-request-id\", sessionId);\n\t}\n\n\treturn headers;\n}\n\nfunction buildWebSocketHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\trequestId: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.delete(\"accept\");\n\theaders.delete(\"content-type\");\n\theaders.delete(\"OpenAI-Beta\");\n\theaders.delete(\"openai-beta\");\n\theaders.set(\"OpenAI-Beta\", OPENAI_BETA_RESPONSES_WEBSOCKETS);\n\theaders.set(\"x-client-request-id\", requestId);\n\theaders.set(\"session-id\", requestId);\n\treturn headers;\n}\n"]}
1
+ {"version":3,"file":"openai-codex-responses.d.ts","sourceRoot":"","sources":["../../src/api/openai-codex-responses.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,6BAA6B,EAG7B,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,EAMX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAGb,MAAM,aAAa,CAAC;AAyDrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IACnF,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,EAAE,6BAA6B,CAAC,cAAc,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;CAC1C;AAwJD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CAqQxF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAqBtF,CAAC;AA0VF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA0BD,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,8BAA8B,GAAG,SAAS,CAG/G;AAED,wBAAgB,mCAAmC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5E;AAED,wBAAgB,iCAAiC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAc1E","sourcesContent":["import type * as NodeZlib from \"node:zlib\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\n\nimport { clampThinkingLevel } from \"../models.ts\";\nimport { registerSessionResourceCleanup } from \"../session-resources.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tTranscriptContext,\n\tUsage,\n} from \"../types.ts\";\nimport { combineAbortSignals } from \"../utils/abort-signals.ts\";\nimport {\n\tappendAssistantMessageDiagnostic,\n\tcreateAssistantMessageDiagnostic,\n\tformatThrownValue,\n} from \"../utils/diagnostics.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { resolveHttpProxyUrlForTarget } from \"../utils/node-http-proxy.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\nimport { getSystemMessageText } from \"../utils/text.ts\";\nimport {\n\tgetDeclaredTools,\n\tgetInitialSystemMessage,\n\tnormalizeContext,\n\tresolveTranscript,\n\tresolveTranscriptTools,\n} from \"../utils/transcript.ts\";\nimport { uuidv7 } from \"../utils/uuid.ts\";\nimport { createGrammarToolInputProperties } from \"./constrained-sampling.ts\";\nimport { clampOpenAIPromptCacheKey } from \"./openai-prompt-cache.ts\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\" as const;\nconst DEFAULT_MAX_RETRIES = 0;\nconst BASE_DELAY_MS = 1000;\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\nconst DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;\n// The Codex backend accepts zstd-compressed request bodies on the SSE responses\n// endpoint (the same endpoint the official Codex client compresses against).\nconst REQUEST_COMPRESSION_ZSTD_LEVEL = 3;\nconst CODEX_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\"]);\nconst WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;\nconst WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = \"websocket_connection_limit_reached\";\nconst PREVIOUS_RESPONSE_NOT_FOUND_CODE = \"previous_response_not_found\";\n\nconst CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([\n\t\"completed\",\n\t\"incomplete\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"queued\",\n\t\"in_progress\",\n]);\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OpenAICodexResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"concise\" | \"detailed\" | \"off\" | \"on\" | null;\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttextVerbosity?: \"low\" | \"medium\" | \"high\";\n\ttoolChoice?: \"auto\" | \"none\" | \"required\";\n}\n\ntype CodexResponseStatus = \"completed\" | \"incomplete\" | \"failed\" | \"cancelled\" | \"queued\" | \"in_progress\";\n\ninterface RequestBody {\n\tmodel: string;\n\tstore?: boolean;\n\tstream?: boolean;\n\tinstructions?: string;\n\tprevious_response_id?: string;\n\tinput?: ResponseInput;\n\ttools?: OpenAITool[];\n\ttool_choice?: OpenAICodexResponsesOptions[\"toolChoice\"];\n\tparallel_tool_calls?: boolean;\n\ttemperature?: number;\n\treasoning?: { effort?: string; summary?: string };\n\tservice_tier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttext?: { verbosity?: string };\n\tinclude?: string[];\n\tprompt_cache_key?: string;\n\t[key: string]: unknown;\n}\n\ntype SuccessfulAssistantMessage = AssistantMessage & { stopReason: \"stop\" | \"length\" | \"toolUse\" };\n\nfunction assertSuccessfulOutput(output: AssistantMessage): asserts output is SuccessfulAssistantMessage {\n\tif (output.stopReason === \"pending\") {\n\t\tthrow new Error(\"Codex stream ended without a stop reason\");\n\t}\n\tif (output.stopReason === \"error\" || output.stopReason === \"aborted\") {\n\t\tthrow new Error(output.errorMessage || \"An unknown error occurred\");\n\t}\n}\n\n// ============================================================================\n// Retry Helpers\n// ============================================================================\n\nfunction isTerminalRateLimitError(errorText: string): boolean {\n\treturn /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(\n\t\terrorText,\n\t);\n}\n\nfunction isRetryableError(status: number, errorText: string): boolean {\n\tif (status === 429 && isTerminalRateLimitError(errorText)) {\n\t\treturn false;\n\t}\n\tif (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {\n\t\treturn true;\n\t}\n\treturn /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);\n}\n\nfunction getRetryAfterDelayMs(headers: Headers): number | undefined {\n\tconst retryAfterMs = headers.get(\"retry-after-ms\");\n\tif (retryAfterMs !== null) {\n\t\tconst millis = Number(retryAfterMs);\n\t\tif (Number.isFinite(millis)) {\n\t\t\treturn Math.max(0, millis);\n\t\t}\n\t}\n\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (!retryAfter) {\n\t\treturn undefined;\n\t}\n\n\tconst seconds = Number(retryAfter);\n\tif (Number.isFinite(seconds)) {\n\t\treturn Math.max(0, seconds * 1000);\n\t}\n\n\tconst date = Date.parse(retryAfter);\n\tif (!Number.isNaN(date)) {\n\t\treturn Math.max(0, date - Date.now());\n\t}\n\n\treturn undefined;\n}\n\nclass RetryDelayExceededError extends Error {}\n\nfunction validateRetryDelayMs(delayMs: number, options?: StreamOptions): number {\n\tconst maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxRetryDelayMs > 0 && delayMs > maxRetryDelayMs) {\n\t\tthrow new RetryDelayExceededError(\n\t\t\t`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxRetryDelayMs / 1000)}s)`,\n\t\t);\n\t}\n\treturn delayMs;\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 Error(\"Request was aborted\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\"abort\", () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t});\n\t});\n}\n\nfunction normalizeTimeoutMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`Invalid timeoutMs: ${String(value)}`);\n\t}\n\treturn Math.floor(value);\n}\n\n// ============================================================================\n// Request Compression\n// ============================================================================\n\ntype ProcessWithBuiltinModule = typeof process & {\n\tgetBuiltinModule?: (id: \"node:zlib\") => typeof NodeZlib;\n};\n\nfunction loadNodeZlib(): typeof NodeZlib | null {\n\tif (typeof process === \"undefined\" || !(process.versions?.node || process.versions?.bun)) {\n\t\treturn null;\n\t}\n\treturn (process as ProcessWithBuiltinModule).getBuiltinModule?.(\"node:zlib\") ?? null;\n}\n\n// Returns the zstd-compressed body bytes, or null when compression is\n// unavailable (browser/Vite builds). Callers fall back to sending the\n// uncompressed JSON when this returns null.\nfunction compressRequestBodyZstd(bodyJson: string): Uint8Array | null {\n\tconst zlib = loadNodeZlib();\n\tif (!zlib || typeof zlib.zstdCompressSync !== \"function\") {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst compressed = zlib.zstdCompressSync(bodyJson, {\n\t\t\tparams: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },\n\t\t});\n\t\treturn new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ============================================================================\n// Main Stream Function\n// ============================================================================\n\nexport const stream: StreamFunction<\"openai-codex-responses\", OpenAICodexResponsesOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: TranscriptContext,\n\toptions?: OpenAICodexResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\tconst normalizedContext = resolveTranscript(context, model.compat?.supportsMidConvoSystemMessages);\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"openai-codex-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"pending\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst apiKey = options?.apiKey;\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t\t\t}\n\n\t\t\tconst accountId = extractAccountId(apiKey);\n\t\t\tconst grammarToolInputProperties = createGrammarToolInputProperties(\n\t\t\t\tgetDeclaredTools(normalizedContext.messages),\n\t\t\t\tmodel.compat?.supportsOpenAIGrammarTools ?? false,\n\t\t\t);\n\t\t\tconst cacheSessionId = options?.cacheRetention === \"none\" ? undefined : options?.sessionId;\n\t\t\tconst codexSessionId = clampOpenAIPromptCacheKey(cacheSessionId);\n\t\t\tlet body = buildRequestBody(model, normalizedContext, options, codexSessionId, grammarToolInputProperties);\n\t\t\tconst nextBody = await options?.onPayload?.(body, model);\n\t\t\tif (nextBody !== undefined) {\n\t\t\t\tbody = nextBody as RequestBody;\n\t\t\t}\n\t\t\tconst websocketRequestId = codexSessionId || uuidv7();\n\t\t\tconst sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);\n\t\t\tconst websocketHeaders = buildWebSocketHeaders(\n\t\t\t\tmodel.headers,\n\t\t\t\toptions?.headers,\n\t\t\t\taccountId,\n\t\t\t\tapiKey,\n\t\t\t\twebsocketRequestId,\n\t\t\t);\n\t\t\tconst bodyJson = JSON.stringify(body);\n\t\t\tconst httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);\n\t\t\tconst websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);\n\t\t\tconst transport = options?.transport || \"auto\";\n\t\t\tlet startEmitted = false;\n\t\t\tconst websocketDisabledForSession = transport !== \"sse\" && isWebSocketSseFallbackActive(cacheSessionId);\n\t\t\tif (websocketDisabledForSession) {\n\t\t\t\trecordWebSocketSseFallback(cacheSessionId);\n\t\t\t}\n\n\t\t\tif (transport !== \"sse\" && !websocketDisabledForSession) {\n\t\t\t\tlet websocketStarted = false;\n\t\t\t\tlet retriedWebSocketConnectionLimit = false;\n\t\t\t\tlet retriedMissingWebSocketContinuation = false;\n\t\t\t\twhile (true) {\n\t\t\t\t\twebsocketStarted = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait processWebSocketStream(\n\t\t\t\t\t\t\tresolveCodexWebSocketUrl(model.baseUrl),\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\twebsocketHeaders,\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\twebsocketStarted = true;\n\t\t\t\t\t\t\t\tif (!startEmitted) {\n\t\t\t\t\t\t\t\t\tstartEmitted = true;\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\thttpTimeoutMs,\n\t\t\t\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\t\t\t\tcacheSessionId,\n\t\t\t\t\t\t\taccountId,\n\t\t\t\t\t\t\tgrammarToolInputProperties,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassertSuccessfulOutput(output);\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"done\",\n\t\t\t\t\t\t\treason: output.stopReason,\n\t\t\t\t\t\t\tmessage: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tstream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst aborted = options?.signal?.aborted;\n\t\t\t\t\t\tconst connectionLimitBeforeStart = !websocketStarted && isWebSocketConnectionLimitReachedError(error);\n\t\t\t\t\t\tconst previousResponseNotFound = isPreviousResponseNotFoundError(error);\n\t\t\t\t\t\tif (!aborted && previousResponseNotFound && !retriedMissingWebSocketContinuation) {\n\t\t\t\t\t\t\tretriedMissingWebSocketContinuation = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!aborted && connectionLimitBeforeStart && !retriedWebSocketConnectionLimit) {\n\t\t\t\t\t\t\tretriedWebSocketConnectionLimit = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (aborted || (isCodexNonTransportError(error) && !connectionLimitBeforeStart)) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tappendAssistantMessageDiagnostic(\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tcreateAssistantMessageDiagnostic(\"provider_transport_failure\", error, {\n\t\t\t\t\t\t\t\tconfiguredTransport: transport,\n\t\t\t\t\t\t\t\t...(websocketStarted ? {} : { fallbackTransport: \"sse\" }),\n\t\t\t\t\t\t\t\teventsEmitted: websocketStarted,\n\t\t\t\t\t\t\t\tphase: websocketStarted ? \"after_message_stream_start\" : \"before_message_stream_start\",\n\t\t\t\t\t\t\t\trequestBytes: new TextEncoder().encode(bodyJson).byteLength,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\trecordWebSocketFailure(cacheSessionId, error);\n\t\t\t\t\t\tif (websocketStarted) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trecordWebSocketSseFallback(cacheSessionId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Compress the request body once for the SSE path. The Codex backend\n\t\t\t// decodes Content-Encoding: zstd; the WebSocket transport above sends the\n\t\t\t// uncompressed JSON frame, matching the official Codex client.\n\t\t\tconst compressedBody = compressRequestBodyZstd(bodyJson);\n\t\t\tif (compressedBody) {\n\t\t\t\tsseHeaders.set(\"content-encoding\", \"zstd\");\n\t\t\t}\n\t\t\tconst sseBody: Uint8Array | string = compressedBody ?? bodyJson;\n\n\t\t\t// Fetch with retry logic for rate limits and transient errors\n\t\t\tlet response: Response | undefined;\n\t\t\tlet lastError: Error | undefined;\n\t\t\tconst maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst headerTimeoutSignal =\n\t\t\t\t\t\thttpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;\n\t\t\t\t\tconst combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresponse = await (options?.fetch ?? globalThis.fetch)(resolveCodexUrl(model.baseUrl), {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: sseHeaders,\n\t\t\t\t\t\t\tbody: sseBody,\n\t\t\t\t\t\t\tsignal: combinedSignal.signal,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tcombinedSignal.cleanup();\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onResponse?.(\n\t\t\t\t\t\t{ status: response.status, headers: headersToRecord(response.headers) },\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorText = await response.text();\n\t\t\t\t\tif (attempt < maxRetries && isRetryableError(response.status, errorText)) {\n\t\t\t\t\t\tconst retryAfterDelayMs = getRetryAfterDelayMs(response.headers);\n\t\t\t\t\t\tconst delayMs =\n\t\t\t\t\t\t\tretryAfterDelayMs === undefined\n\t\t\t\t\t\t\t\t? BASE_DELAY_MS * 2 ** attempt\n\t\t\t\t\t\t\t\t: validateRetryDelayMs(retryAfterDelayMs, options);\n\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Parse error for friendly message on final attempt or non-retryable error\n\t\t\t\t\tconst fakeResponse = new Response(errorText, {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t});\n\t\t\t\t\tconst info = await parseErrorResponse(fakeResponse);\n\t\t\t\t\tthrow new Error(info.friendlyMessage || info.message);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\t\tif (error.name === \"AbortError\" || error.message === \"Request was aborted\") {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlastError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t// Network errors are retryable\n\t\t\t\t\tif (\n\t\t\t\t\t\tattempt < maxRetries &&\n\t\t\t\t\t\t!(lastError instanceof RetryDelayExceededError) &&\n\t\t\t\t\t\t!lastError.message.includes(\"usage limit\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst delayMs = BASE_DELAY_MS * 2 ** attempt;\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow lastError;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!response?.ok) {\n\t\t\t\tthrow lastError ?? new Error(\"Failed after retries\");\n\t\t\t}\n\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"No response body\");\n\t\t\t}\n\n\t\t\tif (!startEmitted) {\n\t\t\t\tstartEmitted = true;\n\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t}\n\t\t\tawait processStream(response, output, stream, model, grammarToolInputProperties, options);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tassertSuccessfulOutput(output);\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\t// Streaming scratch buffers are only used during parsing; never persist them.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t\tdelete (block as { customInput?: unknown }).customInput;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error));\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimple: StreamFunction<\"openai-codex-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: TranscriptContext,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey;\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = {\n\t\t...buildBaseOptions(model, context, options, apiKey),\n\t\ttoolChoice: options?.toolChoice,\n\t} satisfies OpenAICodexResponsesOptions;\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies OpenAICodexResponsesOptions);\n};\n\n// ============================================================================\n// Request Building\n// ============================================================================\n\nfunction buildRequestBody(\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: TranscriptContext,\n\toptions: OpenAICodexResponsesOptions | undefined,\n\tcacheSessionId: string | undefined,\n\tgrammarToolInputProperties: ReadonlyMap<string, string> = createGrammarToolInputProperties(\n\t\tgetDeclaredTools(context.messages),\n\t\tmodel.compat?.supportsOpenAIGrammarTools ?? false,\n\t),\n): RequestBody {\n\tconst supportsStrictMode = model.compat?.supportsStrictMode ?? true;\n\tconst supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;\n\tconst supportsAdditionalTools = model.compat?.supportsAdditionalTools ?? false;\n\tconst supportsToolSearch = model.compat?.supportsToolSearch ?? false;\n\tconst transcriptTools = resolveTranscriptTools(context.messages, supportsAdditionalTools || supportsToolSearch);\n\tconst messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {\n\t\tincludeSystemPrompt: false,\n\t\tgrammarToolInputProperties,\n\t\tsupportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,\n\t\tsupportsAdditionalTools,\n\t\tsupportsToolSearch,\n\t\ttoolOptions: { strict: null, supportsStrictMode, supportsOpenAIGrammarTools },\n\t});\n\n\tconst initialSystemMessage = getInitialSystemMessage(context.messages);\n\tconst instructions = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : \"\";\n\tconst body: RequestBody = {\n\t\tmodel: model.id,\n\t\tstore: false,\n\t\tstream: true,\n\t\tinstructions: instructions || \"You are a helpful assistant.\",\n\t\tinput: messages,\n\t\ttext: { verbosity: options?.textVerbosity || \"low\" },\n\t\tinclude: [\"reasoning.encrypted_content\"],\n\t\tprompt_cache_key: cacheSessionId,\n\t\ttool_choice: options?.toolChoice ?? \"auto\",\n\t\tparallel_tool_calls: true,\n\t};\n\n\tif (options?.temperature !== undefined) {\n\t\tbody.temperature = options.temperature;\n\t}\n\n\tif (options?.serviceTier !== undefined) {\n\t\tbody.service_tier = options.serviceTier;\n\t}\n\n\tif (transcriptTools.requestTools.length > 0) {\n\t\tbody.tools = convertResponsesTools(transcriptTools.requestTools, {\n\t\t\tstrict: null,\n\t\t\tsupportsStrictMode,\n\t\t\tsupportsOpenAIGrammarTools,\n\t\t});\n\t}\n\n\tif (options?.reasoningEffort !== undefined) {\n\t\tconst effort =\n\t\t\toptions.reasoningEffort === \"none\"\n\t\t\t\t? model.thinkingLevelMap?.off === undefined\n\t\t\t\t\t? \"none\"\n\t\t\t\t\t: model.thinkingLevelMap.off\n\t\t\t\t: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);\n\t\tif (effort !== null) {\n\t\t\tbody.reasoning = {\n\t\t\t\teffort,\n\t\t\t\tsummary: options.reasoningSummary ?? \"auto\",\n\t\t\t};\n\t\t}\n\t} else if (model.reasoning && model.thinkingLevelMap?.off !== null) {\n\t\tbody.reasoning = { effort: model.thinkingLevelMap?.off ?? \"none\" };\n\t}\n\n\treturn body;\n}\n\nfunction getServiceTierCostMultiplier(\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): number {\n\tswitch (serviceTier) {\n\t\tcase \"flex\":\n\t\t\treturn 0.5;\n\t\tcase \"priority\":\n\t\t\treturn model.id === \"gpt-5.5\" ? 2.5 : 2;\n\t\tdefault:\n\t\t\treturn 1;\n\t}\n}\n\nfunction applyServiceTierPricing(\n\tusage: Usage,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n) {\n\tconst multiplier = getServiceTierCostMultiplier(model, serviceTier);\n\tif (multiplier === 1) return;\n\n\tusage.cost.input *= multiplier;\n\tusage.cost.output *= multiplier;\n\tusage.cost.cacheRead *= multiplier;\n\tusage.cost.cacheWrite *= multiplier;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n}\n\nfunction resolveCodexServiceTier(\n\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): ResponseCreateParamsStreaming[\"service_tier\"] | undefined {\n\tif (responseServiceTier === \"default\" && (requestServiceTier === \"flex\" || requestServiceTier === \"priority\")) {\n\t\treturn requestServiceTier;\n\t}\n\treturn responseServiceTier ?? requestServiceTier;\n}\n\nfunction resolveCodexUrl(baseUrl?: string): string {\n\tconst raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;\n\tconst normalized = raw.replace(/\\/+$/, \"\");\n\tif (normalized.endsWith(\"/codex/responses\")) return normalized;\n\tif (normalized.endsWith(\"/codex\")) return `${normalized}/responses`;\n\treturn `${normalized}/codex/responses`;\n}\n\nfunction resolveCodexWebSocketUrl(baseUrl?: string): string {\n\tconst url = new URL(resolveCodexUrl(baseUrl));\n\tif (url.protocol === \"https:\") url.protocol = \"wss:\";\n\tif (url.protocol === \"http:\") url.protocol = \"ws:\";\n\treturn url.toString();\n}\n\n// ============================================================================\n// Response Processing\n// ============================================================================\n\nasync function processStream(\n\tresponse: Response,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tgrammarToolInputProperties: ReadonlyMap<string, string>,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tawait processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output), output, stream, model, {\n\t\tserviceTier: options?.serviceTier,\n\t\tgrammarToolInputProperties,\n\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t});\n}\n\nclass CodexApiError extends Error {\n\treadonly code?: string;\n\treadonly payload?: Record<string, unknown>;\n\n\tconstructor(message: string, options?: { code?: string; payload?: Record<string, unknown>; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexApiError\";\n\t\tthis.code = options?.code;\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nclass CodexProtocolError extends Error {\n\treadonly payload?: unknown;\n\n\tconstructor(message: string, options?: { payload?: unknown; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexProtocolError\";\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nfunction isCodexNonTransportError(error: unknown): boolean {\n\treturn error instanceof CodexApiError || error instanceof CodexProtocolError;\n}\n\nfunction isWebSocketConnectionLimitReachedError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE;\n}\n\nfunction isPreviousResponseNotFoundError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === PREVIOUS_RESPONSE_NOT_FOUND_CODE;\n}\n\nfunction extractCodexEventError(event: Record<string, unknown>): { code?: string; message?: string } {\n\tconst nested = event.error && typeof event.error === \"object\" ? (event.error as Record<string, unknown>) : undefined;\n\treturn {\n\t\tcode: typeof event.code === \"string\" ? event.code : typeof nested?.code === \"string\" ? nested.code : undefined,\n\t\tmessage:\n\t\t\ttypeof event.message === \"string\"\n\t\t\t\t? event.message\n\t\t\t\t: typeof nested?.message === \"string\"\n\t\t\t\t\t? nested.message\n\t\t\t\t\t: undefined,\n\t};\n}\n\nasync function* mapCodexEvents(\n\tevents: AsyncIterable<Record<string, unknown>>,\n\toutput: AssistantMessage,\n): AsyncGenerator<ResponseStreamEvent> {\n\tfor await (const event of events) {\n\t\tconst type = typeof event.type === \"string\" ? event.type : undefined;\n\t\tif (!type) continue;\n\n\t\tif (type === \"error\") {\n\t\t\tconst { code, message } = extractCodexEventError(event);\n\t\t\tthrow new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {\n\t\t\t\tcode,\n\t\t\t\tpayload: event,\n\t\t\t});\n\t\t}\n\n\t\tif (type === \"response.failed\") {\n\t\t\tconst response = (event as { response?: { error?: { code?: string; message?: string } } }).response;\n\t\t\tconst code = response?.error?.code;\n\t\t\tconst message = response?.error?.message;\n\t\t\tthrow new CodexApiError(message || \"Codex response failed\", { code, payload: event });\n\t\t}\n\n\t\tif (type === \"response.done\" || type === \"response.completed\" || type === \"response.incomplete\") {\n\t\t\tconst response = (event as { response?: { status?: unknown; end_turn?: unknown } }).response;\n\t\t\tif (typeof response?.end_turn === \"boolean\") {\n\t\t\t\toutput.endTurn = response.end_turn;\n\t\t\t}\n\t\t\tconst normalizedResponse = response\n\t\t\t\t? { ...response, status: normalizeCodexStatus(response.status) }\n\t\t\t\t: response;\n\t\t\tyield { ...event, type: \"response.completed\", response: normalizedResponse } as ResponseStreamEvent;\n\t\t\treturn;\n\t\t}\n\n\t\tyield event as unknown as ResponseStreamEvent;\n\t}\n}\n\nfunction normalizeCodexStatus(status: unknown): CodexResponseStatus | undefined {\n\tif (typeof status !== \"string\") return undefined;\n\treturn CODEX_RESPONSE_STATUSES.has(status as CodexResponseStatus) ? (status as CodexResponseStatus) : undefined;\n}\n\n// ============================================================================\n// SSE Parsing\n// ============================================================================\n\nasync function* parseSSE(response: Response, signal?: AbortSignal): AsyncGenerator<Record<string, unknown>> {\n\tif (!response.body) return;\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = \"\";\n\tconst onAbort = () => {\n\t\tvoid reader.cancel().catch(() => {});\n\t};\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tbuffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n\t\t\t// Treat EOF as terminating the residual SSE frame.\n\t\t\tif (done && buffer.trim()) buffer += \"\\n\\n\";\n\n\t\t\tlet idx = buffer.indexOf(\"\\n\\n\");\n\t\t\twhile (idx !== -1) {\n\t\t\t\tconst chunk = buffer.slice(0, idx);\n\t\t\t\tbuffer = buffer.slice(idx + 2);\n\n\t\t\t\tconst dataLines = chunk\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((l) => l.startsWith(\"data:\"))\n\t\t\t\t\t.map((l) => l.slice(5).trim());\n\t\t\t\tif (dataLines.length > 0) {\n\t\t\t\t\tconst data = dataLines.join(\"\\n\").trim();\n\t\t\t\t\tif (data && data !== \"[DONE]\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tyield JSON.parse(data) as Record<string, unknown>;\n\t\t\t\t\t\t} catch (cause) {\n\t\t\t\t\t\t\tthrow new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\tpayload: data,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tidx = buffer.indexOf(\"\\n\\n\");\n\t\t\t}\n\n\t\t\tif (done) break;\n\t\t}\n\t} finally {\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\ttry {\n\t\t\tawait reader.cancel();\n\t\t} catch {}\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {}\n\t}\n}\n\n// ============================================================================\n// WebSocket Parsing\n// ============================================================================\n\nconst OPENAI_BETA_RESPONSES_WEBSOCKETS = \"responses_websockets=2026-02-06\";\nconst SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;\nconst SESSION_WEBSOCKET_MAX_AGE_MS = 55 * 60 * 1000;\n\ntype WebSocketEventType = \"open\" | \"message\" | \"error\" | \"close\";\ntype WebSocketListener = (event: unknown) => void;\n\ninterface WebSocketLike {\n\tclose(code?: number, reason?: string): void;\n\tsend(data: string): void;\n\taddEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n\tremoveEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n}\n\ninterface CachedWebSocketContinuationState {\n\tlastRequestBody: RequestBody;\n\tlastResponseId: string;\n\tlastResponseItems: ResponseInput;\n}\n\ninterface CachedWebSocketConnection {\n\tsocket: WebSocketLike;\n\tbusy: boolean;\n\tcreatedAt: number;\n\tidleTimer?: ReturnType<typeof setTimeout>;\n\tcontinuation?: CachedWebSocketContinuationState;\n}\n\nexport interface OpenAICodexWebSocketDebugStats {\n\trequests: number;\n\tconnectionsCreated: number;\n\tconnectionsReused: number;\n\tcachedContextRequests: number;\n\tstoreTrueRequests: number;\n\tfullContextRequests: number;\n\tdeltaRequests: number;\n\tlastInputItems: number;\n\tlastDeltaInputItems?: number;\n\tlastPreviousResponseId?: string;\n\twebsocketFailures: number;\n\tsseFallbacks: number;\n\twebsocketFallbackActive?: boolean;\n\tlastWebSocketError?: string;\n}\n\nconst websocketSessionCache = new Map<string, Map<string, CachedWebSocketConnection>>();\nconst websocketDebugStats = new Map<string, OpenAICodexWebSocketDebugStats>();\nconst websocketSseFallbackSessions = new Set<string>();\n\nfunction getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats {\n\tlet stats = websocketDebugStats.get(sessionId);\n\tif (!stats) {\n\t\tstats = {\n\t\t\trequests: 0,\n\t\t\tconnectionsCreated: 0,\n\t\t\tconnectionsReused: 0,\n\t\t\tcachedContextRequests: 0,\n\t\t\tstoreTrueRequests: 0,\n\t\t\tfullContextRequests: 0,\n\t\t\tdeltaRequests: 0,\n\t\t\tlastInputItems: 0,\n\t\t\twebsocketFailures: 0,\n\t\t\tsseFallbacks: 0,\n\t\t};\n\t\twebsocketDebugStats.set(sessionId, stats);\n\t}\n\treturn stats;\n}\n\nexport function getOpenAICodexWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined {\n\tconst stats = websocketDebugStats.get(sessionId);\n\treturn stats ? { ...stats } : undefined;\n}\n\nexport function resetOpenAICodexWebSocketDebugStats(sessionId?: string): void {\n\tif (sessionId) {\n\t\twebsocketDebugStats.delete(sessionId);\n\t\twebsocketSseFallbackSessions.delete(sessionId);\n\t\treturn;\n\t}\n\twebsocketDebugStats.clear();\n\twebsocketSseFallbackSessions.clear();\n}\n\nexport function closeOpenAICodexWebSocketSessions(sessionId?: string): void {\n\tconst closeEntry = (entry: CachedWebSocketConnection) => {\n\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"debug_close\");\n\t};\n\tif (sessionId) {\n\t\tfor (const entry of websocketSessionCache.get(sessionId)?.values() ?? []) closeEntry(entry);\n\t\twebsocketSessionCache.delete(sessionId);\n\t\treturn;\n\t}\n\tfor (const accountEntries of websocketSessionCache.values()) {\n\t\tfor (const entry of accountEntries.values()) closeEntry(entry);\n\t}\n\twebsocketSessionCache.clear();\n}\n\nregisterSessionResourceCleanup(closeOpenAICodexWebSocketSessions);\n\nfunction isWebSocketSseFallbackActive(sessionId: string | undefined): boolean {\n\treturn sessionId ? websocketSseFallbackSessions.has(sessionId) : false;\n}\n\nfunction recordWebSocketSseFallback(sessionId: string | undefined): void {\n\tif (!sessionId) return;\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.sseFallbacks++;\n\tstats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);\n}\n\nfunction recordWebSocketFailure(sessionId: string | undefined, error: unknown): void {\n\tif (!sessionId) return;\n\twebsocketSseFallbackSessions.add(sessionId);\n\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.websocketFailures++;\n\tstats.lastWebSocketError = formatThrownValue(error);\n\tstats.websocketFallbackActive = true;\n}\n\ntype WebSocketConstructor = new (\n\turl: string,\n\tprotocols?: string | string[] | { headers?: Record<string, string> },\n) => WebSocketLike;\n\nlet _cachedWebsocket: WebSocketConstructor | null = null;\nasync function getWebSocketConstructor(env?: ProviderEnv): Promise<WebSocketConstructor | null> {\n\tif (!env && _cachedWebsocket) return _cachedWebsocket;\n\n\t// bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489\n\t// TODO: remove this when bun supports proxy envs in websocket.\n\tif (typeof process !== \"undefined\" && process.versions?.bun) {\n\t\tconst WebSocketWithProxy = class extends WebSocket {\n\t\t\tconstructor(url: string | URL, options?: string | string[] | Record<string, unknown>) {\n\t\t\t\tlet _opts: Record<string, unknown> = {};\n\t\t\t\tif (Array.isArray(options) || typeof options === \"string\") {\n\t\t\t\t\t_opts = { protocols: options };\n\t\t\t\t} else {\n\t\t\t\t\t_opts = { ...options };\n\t\t\t\t}\n\n\t\t\t\tconst proxyUrl = resolveHttpProxyUrlForTarget(\n\t\t\t\t\turl.toString().replace(/^wss:/, \"https:\").replace(/^ws:/, \"http:\"),\n\t\t\t\t\tenv,\n\t\t\t\t);\n\t\t\t\tsuper(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) } as any);\n\t\t\t}\n\t\t};\n\t\tif (!env) {\n\t\t\t_cachedWebsocket = WebSocketWithProxy;\n\t\t}\n\t\treturn WebSocketWithProxy;\n\t}\n\n\tconst ctor = (globalThis as { WebSocket?: unknown }).WebSocket;\n\tif (typeof ctor !== \"function\") return null;\n\treturn ctor as unknown as WebSocketConstructor;\n}\n\nclass WebSocketCloseError extends Error {\n\treadonly code?: number;\n\treadonly reason?: string;\n\treadonly wasClean?: boolean;\n\n\tconstructor(message: string, options?: { code?: number; reason?: string; wasClean?: boolean }) {\n\t\tsuper(message);\n\t\tthis.name = \"WebSocketCloseError\";\n\t\tthis.code = options?.code;\n\t\tthis.reason = options?.reason;\n\t\tthis.wasClean = options?.wasClean;\n\t}\n}\n\nfunction getWebSocketReadyState(socket: WebSocketLike): number | undefined {\n\tconst readyState = (socket as { readyState?: unknown }).readyState;\n\treturn typeof readyState === \"number\" ? readyState : undefined;\n}\n\nfunction isWebSocketReusable(socket: WebSocketLike): boolean {\n\tconst readyState = getWebSocketReadyState(socket);\n\t// If readyState is unavailable, assume the runtime keeps it open/reusable.\n\treturn readyState === undefined || readyState === 1;\n}\n\nfunction isWebSocketSessionExpired(entry: CachedWebSocketConnection): boolean {\n\treturn Date.now() - entry.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;\n}\n\nfunction closeWebSocketSilently(socket: WebSocketLike, code = 1000, reason = \"done\"): void {\n\ttry {\n\t\tsocket.close(code, reason);\n\t} catch {}\n}\n\nfunction scheduleSessionWebSocketExpiry(sessionId: string, accountId: string, entry: CachedWebSocketConnection): void {\n\tif (entry.idleTimer) {\n\t\tclearTimeout(entry.idleTimer);\n\t}\n\tentry.idleTimer = setTimeout(() => {\n\t\tif (entry.busy) return;\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"idle_timeout\");\n\t\tconst accountEntries = websocketSessionCache.get(sessionId);\n\t\tif (accountEntries?.get(accountId) === entry) accountEntries.delete(accountId);\n\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t}, SESSION_WEBSOCKET_CACHE_TTL_MS);\n}\n\nasync function connectWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS,\n\tenv?: ProviderEnv,\n): Promise<WebSocketLike> {\n\tconst WebSocketCtor = await getWebSocketConstructor(env);\n\tif (!WebSocketCtor) {\n\t\tthrow new Error(\"WebSocket transport is not available in this runtime\");\n\t}\n\n\tconst wsHeaders = headersToRecord(headers);\n\tdelete wsHeaders[\"OpenAI-Beta\"];\n\n\treturn new Promise<WebSocketLike>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tlet socket: WebSocketLike;\n\n\t\ttry {\n\t\t\tsocket = new WebSocketCtor(url, { headers: wsHeaders });\n\t\t} catch (error) {\n\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\treturn;\n\t\t}\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeout) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\ttimeout = undefined;\n\t\t\t}\n\t\t\tsocket.removeEventListener(\"open\", onOpen);\n\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst fail = (error: Error, closeReason?: string) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tif (closeReason) {\n\t\t\t\tcloseWebSocketSilently(socket, 1000, closeReason);\n\t\t\t}\n\t\t\treject(error);\n\t\t};\n\t\tconst onOpen: WebSocketListener = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tresolve(socket);\n\t\t};\n\t\tconst onError: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketError(event));\n\t\t};\n\t\tconst onClose: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketCloseError(event));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tfail(new Error(\"Request was aborted\"), \"aborted\");\n\t\t};\n\n\t\tsocket.addEventListener(\"open\", onOpen);\n\t\tsocket.addEventListener(\"error\", onError);\n\t\tsocket.addEventListener(\"close\", onClose);\n\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\tif (connectTimeoutMs > 0) {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\tfail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), \"connect_timeout\");\n\t\t\t}, connectTimeoutMs);\n\t\t}\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t}\n\t});\n}\n\nasync function acquireWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsessionId: string | undefined,\n\taccountId: string,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs?: number,\n\tenv?: ProviderEnv,\n): Promise<{\n\tsocket: WebSocketLike;\n\tentry?: CachedWebSocketConnection;\n\treused: boolean;\n\trelease: (options?: { keep?: boolean }) => void;\n}> {\n\tif (!sessionId) {\n\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\treturn {\n\t\t\tsocket,\n\t\t\treused: false,\n\t\t\trelease: () => closeWebSocketSilently(socket),\n\t\t};\n\t}\n\n\tlet accountEntries = websocketSessionCache.get(sessionId);\n\tconst cached = accountEntries?.get(accountId);\n\tif (cached) {\n\t\tif (cached.idleTimer) {\n\t\t\tclearTimeout(cached.idleTimer);\n\t\t\tcached.idleTimer = undefined;\n\t\t}\n\t\tif (!cached.busy && isWebSocketSessionExpired(cached)) {\n\t\t\tcloseWebSocketSilently(cached.socket, 1000, \"connection_age_limit\");\n\t\t\taccountEntries?.delete(accountId);\n\t\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t} else if (!cached.busy && isWebSocketReusable(cached.socket)) {\n\t\t\tcached.busy = true;\n\t\t\treturn {\n\t\t\t\tsocket: cached.socket,\n\t\t\t\tentry: cached,\n\t\t\t\treused: true,\n\t\t\t\trelease: ({ keep } = {}) => {\n\t\t\t\t\tif (!keep || !isWebSocketReusable(cached.socket)) {\n\t\t\t\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\t\t\t\tconst currentEntries = websocketSessionCache.get(sessionId);\n\t\t\t\t\t\tif (currentEntries?.get(accountId) === cached) currentEntries.delete(accountId);\n\t\t\t\t\t\tif (currentEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcached.busy = false;\n\t\t\t\t\tscheduleSessionWebSocketExpiry(sessionId, accountId, cached);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (cached.busy) {\n\t\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\t\treturn {\n\t\t\t\tsocket,\n\t\t\t\treused: false,\n\t\t\t\trelease: () => {\n\t\t\t\t\tcloseWebSocketSilently(socket);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (!isWebSocketReusable(cached.socket)) {\n\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\taccountEntries?.delete(accountId);\n\t\t\tif (accountEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t}\n\t}\n\n\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\tconst entry: CachedWebSocketConnection = { socket, busy: true, createdAt: Date.now() };\n\taccountEntries = websocketSessionCache.get(sessionId);\n\tif (!accountEntries) {\n\t\taccountEntries = new Map();\n\t\twebsocketSessionCache.set(sessionId, accountEntries);\n\t}\n\taccountEntries.set(accountId, entry);\n\treturn {\n\t\tsocket,\n\t\tentry,\n\t\treused: false,\n\t\trelease: ({ keep } = {}) => {\n\t\t\tif (!keep || !isWebSocketReusable(entry.socket)) {\n\t\t\t\tcloseWebSocketSilently(entry.socket);\n\t\t\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\t\t\tconst currentEntries = websocketSessionCache.get(sessionId);\n\t\t\t\tif (currentEntries?.get(accountId) === entry) currentEntries.delete(accountId);\n\t\t\t\tif (currentEntries?.size === 0) websocketSessionCache.delete(sessionId);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentry.busy = false;\n\t\t\tscheduleSessionWebSocketExpiry(sessionId, accountId, entry);\n\t\t},\n\t};\n}\n\nfunction extractWebSocketError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst message = \"message\" in event ? (event as { message?: unknown }).message : undefined;\n\t\tif (typeof message === \"string\" && message.length > 0) {\n\t\t\treturn new Error(message);\n\t\t}\n\n\t\tconst nestedError = \"error\" in event ? (event as { error?: unknown }).error : undefined;\n\t\tif (nestedError instanceof Error && nestedError.message.length > 0) {\n\t\t\treturn nestedError;\n\t\t}\n\t\tif (nestedError && typeof nestedError === \"object\" && \"message\" in nestedError) {\n\t\t\tconst nestedMessage = (nestedError as { message?: unknown }).message;\n\t\t\tif (typeof nestedMessage === \"string\" && nestedMessage.length > 0) {\n\t\t\t\treturn new Error(nestedMessage);\n\t\t\t}\n\t\t}\n\t}\n\treturn new Error(\"WebSocket error\");\n}\n\nfunction extractWebSocketCloseError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst code = \"code\" in event ? (event as { code?: unknown }).code : undefined;\n\t\tconst reason = \"reason\" in event ? (event as { reason?: unknown }).reason : undefined;\n\t\tconst wasClean = \"wasClean\" in event ? (event as { wasClean?: unknown }).wasClean : undefined;\n\t\tconst codeText = typeof code === \"number\" ? ` ${code}` : \"\";\n\t\tlet reasonText = typeof reason === \"string\" && reason.length > 0 ? ` ${reason}` : \"\";\n\t\tif (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {\n\t\t\treasonText = \" message too big\";\n\t\t}\n\t\treturn new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {\n\t\t\tcode: typeof code === \"number\" ? code : undefined,\n\t\t\treason: typeof reason === \"string\" && reason.length > 0 ? reason : undefined,\n\t\t\twasClean: typeof wasClean === \"boolean\" ? wasClean : undefined,\n\t\t});\n\t}\n\treturn new Error(\"WebSocket closed\");\n}\n\nasync function decodeWebSocketData(data: unknown): Promise<string | null> {\n\tif (typeof data === \"string\") return data;\n\tif (data instanceof ArrayBuffer) {\n\t\treturn new TextDecoder().decode(new Uint8Array(data));\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst view = data as ArrayBufferView;\n\t\treturn new TextDecoder().decode(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));\n\t}\n\tif (data && typeof data === \"object\" && \"arrayBuffer\" in data) {\n\t\tconst blobLike = data as { arrayBuffer: () => Promise<ArrayBuffer> };\n\t\tconst arrayBuffer = await blobLike.arrayBuffer();\n\t\treturn new TextDecoder().decode(new Uint8Array(arrayBuffer));\n\t}\n\treturn null;\n}\n\nasync function* parseWebSocket(\n\tsocket: WebSocketLike,\n\tsignal?: AbortSignal,\n\tidleTimeoutMs?: number,\n): AsyncGenerator<Record<string, unknown>> {\n\tconst queue: Record<string, unknown>[] = [];\n\tlet pending: (() => void) | null = null;\n\tlet done = false;\n\tlet failed: Error | null = null;\n\tlet sawCompletion = false;\n\n\tconst wake = () => {\n\t\tif (!pending) return;\n\t\tconst resolve = pending;\n\t\tpending = null;\n\t\tresolve();\n\t};\n\n\tconst onMessage: WebSocketListener = (event) => {\n\t\tvoid (async () => {\n\t\t\tlet text: string | null = null;\n\t\t\ttry {\n\t\t\t\tif (!event || typeof event !== \"object\" || !(\"data\" in event)) return;\n\t\t\t\ttext = await decodeWebSocketData((event as { data?: unknown }).data);\n\t\t\t\tif (!text) return;\n\t\t\t\tconst parsed = JSON.parse(text) as Record<string, unknown>;\n\t\t\t\tconst type = typeof parsed.type === \"string\" ? parsed.type : \"\";\n\t\t\t\tif (type === \"response.completed\" || type === \"response.done\" || type === \"response.incomplete\") {\n\t\t\t\t\tsawCompletion = true;\n\t\t\t\t\tdone = true;\n\t\t\t\t}\n\t\t\t\tqueue.push(parsed);\n\t\t\t\twake();\n\t\t\t} catch (cause) {\n\t\t\t\tfailed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\tcause,\n\t\t\t\t\tpayload: text,\n\t\t\t\t});\n\t\t\t\tdone = true;\n\t\t\t\twake();\n\t\t\t}\n\t\t})();\n\t};\n\n\tconst onError: WebSocketListener = (event) => {\n\t\tfailed = extractWebSocketError(event);\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onClose: WebSocketListener = (event) => {\n\t\tif (sawCompletion) {\n\t\t\tdone = true;\n\t\t\twake();\n\t\t\treturn;\n\t\t}\n\t\tif (!failed) {\n\t\t\tfailed = extractWebSocketCloseError(event);\n\t\t}\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onAbort = () => {\n\t\tfailed = new Error(\"Request was aborted\");\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tsocket.addEventListener(\"message\", onMessage);\n\tsocket.addEventListener(\"error\", onError);\n\tsocket.addEventListener(\"close\", onClose);\n\tsignal?.addEventListener(\"abort\", onAbort);\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (queue.length > 0) {\n\t\t\t\tyield queue.shift()!;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tpending = resolve;\n\t\t\t\tif (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {\n\t\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\t\tconst error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);\n\t\t\t\t\t\tfailed = error;\n\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\tpending = null;\n\t\t\t\t\t\tcloseWebSocketSilently(socket, 1000, \"idle_timeout\");\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}, idleTimeoutMs);\n\t\t\t\t}\n\t\t\t}).finally(() => {\n\t\t\t\tif (timeout) {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (failed) {\n\t\t\tthrow failed;\n\t\t}\n\t\tif (!sawCompletion) {\n\t\t\tthrow new Error(\"WebSocket stream closed before response.completed\");\n\t\t}\n\t} finally {\n\t\tsocket.removeEventListener(\"message\", onMessage);\n\t\tsocket.removeEventListener(\"error\", onError);\n\t\tsocket.removeEventListener(\"close\", onClose);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nfunction requestBodyWithoutInput(body: RequestBody): RequestBody {\n\tconst { input: _input, previous_response_id: _previousResponseId, ...rest } = body;\n\treturn rest;\n}\n\nfunction responseInputsEqual(a: ResponseInput | undefined, b: ResponseInput | undefined): boolean {\n\treturn JSON.stringify(a ?? []) === JSON.stringify(b ?? []);\n}\n\nfunction requestBodiesMatchExceptInput(a: RequestBody, b: RequestBody): boolean {\n\treturn JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));\n}\n\nfunction getCachedWebSocketInputDelta(\n\tbody: RequestBody,\n\tcontinuation: CachedWebSocketContinuationState,\n): ResponseInput | undefined {\n\tif (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {\n\t\treturn undefined;\n\t}\n\n\tconst currentInput = body.input ?? [];\n\tconst baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];\n\tif (currentInput.length < baseline.length) {\n\t\treturn undefined;\n\t}\n\n\tconst prefix = currentInput.slice(0, baseline.length);\n\tif (!responseInputsEqual(prefix, baseline)) {\n\t\treturn undefined;\n\t}\n\n\treturn currentInput.slice(baseline.length);\n}\n\nfunction buildCachedWebSocketRequestBody(entry: CachedWebSocketConnection, body: RequestBody): RequestBody {\n\tconst continuation = entry.continuation;\n\tif (!continuation) {\n\t\treturn body;\n\t}\n\n\tconst delta = getCachedWebSocketInputDelta(body, continuation);\n\tif (!delta || !continuation.lastResponseId) {\n\t\tentry.continuation = undefined;\n\t\treturn body;\n\t}\n\n\treturn {\n\t\t...body,\n\t\tprevious_response_id: continuation.lastResponseId,\n\t\tinput: delta,\n\t};\n}\n\nasync function* startWebSocketOutputOnFirstEvent(\n\tevents: AsyncIterable<ResponseStreamEvent>,\n\tonStart: () => void,\n): AsyncGenerator<ResponseStreamEvent> {\n\tlet started = false;\n\tfor await (const event of events) {\n\t\tif (!started) {\n\t\t\tstarted = true;\n\t\t\tonStart();\n\t\t}\n\t\tyield event;\n\t}\n}\n\nasync function processWebSocketStream(\n\turl: string,\n\tbody: RequestBody,\n\theaders: Headers,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tonStart: () => void,\n\tidleTimeoutMs: number | undefined,\n\twebsocketConnectTimeoutMs: number | undefined,\n\tcacheSessionId: string | undefined,\n\taccountId: string,\n\tgrammarToolInputProperties: ReadonlyMap<string, string>,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tconst { socket, entry, reused, release } = await acquireWebSocket(\n\t\turl,\n\t\theaders,\n\t\tcacheSessionId,\n\t\taccountId,\n\t\toptions?.signal,\n\t\twebsocketConnectTimeoutMs,\n\t\toptions?.env,\n\t);\n\tlet keepConnection = true;\n\tconst useCachedContext = options?.transport === \"websocket-cached\" || options?.transport === \"auto\";\n\t// ChatGPT Codex Responses rejects `store: true` (\"Store must be set to false\").\n\t// WebSocket continuation still works via connection-scoped previous_response_id state.\n\tconst fullBody = body;\n\tconst requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;\n\tconst stats = cacheSessionId ? getOrCreateWebSocketDebugStats(cacheSessionId) : undefined;\n\tif (stats) {\n\t\tstats.requests++;\n\t\tif (reused) stats.connectionsReused++;\n\t\telse stats.connectionsCreated++;\n\t\tif (useCachedContext) stats.cachedContextRequests++;\n\t\tif (requestBody.store === true) stats.storeTrueRequests++;\n\t\tstats.lastInputItems = requestBody.input?.length ?? 0;\n\t\tif (requestBody.previous_response_id) {\n\t\t\tstats.deltaRequests++;\n\t\t\tstats.lastDeltaInputItems = requestBody.input?.length ?? 0;\n\t\t\tstats.lastPreviousResponseId = requestBody.previous_response_id;\n\t\t} else {\n\t\t\tstats.fullContextRequests++;\n\t\t\tstats.lastDeltaInputItems = undefined;\n\t\t\tstats.lastPreviousResponseId = undefined;\n\t\t}\n\t}\n\ttry {\n\t\tsocket.send(JSON.stringify({ type: \"response.create\", ...requestBody }));\n\t\tawait processResponsesStream(\n\t\t\tstartWebSocketOutputOnFirstEvent(\n\t\t\t\tmapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output),\n\t\t\t\tonStart,\n\t\t\t),\n\t\t\toutput,\n\t\t\tstream,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tserviceTier: options?.serviceTier,\n\t\t\t\tgrammarToolInputProperties,\n\t\t\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\t\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t\t\t},\n\t\t);\n\t\tif (options?.signal?.aborted) {\n\t\t\tkeepConnection = false;\n\t\t} else if (useCachedContext && entry && output.responseId) {\n\t\t\tconst responseItems = convertResponsesMessages(\n\t\t\t\tmodel,\n\t\t\t\tnormalizeContext({ messages: [output] }),\n\t\t\t\tCODEX_TOOL_CALL_PROVIDERS,\n\t\t\t\t{\n\t\t\t\t\tincludeSystemPrompt: false,\n\t\t\t\t\tgrammarToolInputProperties,\n\t\t\t\t},\n\t\t\t).filter((item) => item.type !== \"function_call_output\" && item.type !== \"custom_tool_call_output\");\n\t\t\tentry.continuation = {\n\t\t\t\tlastRequestBody: fullBody,\n\t\t\t\tlastResponseId: output.responseId,\n\t\t\t\tlastResponseItems: responseItems,\n\t\t\t};\n\t\t}\n\t} catch (error) {\n\t\tif (entry) {\n\t\t\tentry.continuation = undefined;\n\t\t}\n\t\tkeepConnection = false;\n\t\tthrow error;\n\t} finally {\n\t\trelease({ keep: keepConnection });\n\t}\n}\n\n// ============================================================================\n// Error Handling\n// ============================================================================\n\nasync function parseErrorResponse(response: Response): Promise<{ message: string; friendlyMessage?: string }> {\n\tconst raw = await response.text();\n\tlet message = raw || response.statusText || \"Request failed\";\n\tlet friendlyMessage: string | undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as {\n\t\t\terror?: { code?: string; type?: string; message?: string; plan_type?: string; resets_at?: number };\n\t\t};\n\t\tconst err = parsed?.error;\n\t\tif (err) {\n\t\t\tconst code = err.code || err.type || \"\";\n\t\t\tif (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) {\n\t\t\t\tconst plan = err.plan_type ? ` (${err.plan_type.toLowerCase()} plan)` : \"\";\n\t\t\t\tconst mins = err.resets_at\n\t\t\t\t\t? Math.max(0, Math.round((err.resets_at * 1000 - Date.now()) / 60000))\n\t\t\t\t\t: undefined;\n\t\t\t\tconst when = mins !== undefined ? ` Try again in ~${mins} min.` : \"\";\n\t\t\t\tfriendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();\n\t\t\t}\n\t\t\tmessage = err.message || friendlyMessage || message;\n\t\t}\n\t} catch {}\n\n\treturn { message, friendlyMessage };\n}\n\n// ============================================================================\n// Auth & Headers\n// ============================================================================\n\nfunction extractAccountId(token: string): string {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) throw new Error(\"Invalid token\");\n\t\tconst payload = JSON.parse(atob(parts[1]));\n\t\tconst accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;\n\t\tif (!accountId) throw new Error(\"No account ID in token\");\n\t\treturn accountId;\n\t} catch {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n}\n\nfunction buildBaseCodexHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n): Headers {\n\tconst headers = new Headers(initHeaders);\n\tfor (const [key, value] of Object.entries(additionalHeaders || {})) {\n\t\tif (value === null) {\n\t\t\theaders.delete(key);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"chatgpt-account-id\", accountId);\n\theaders.set(\"originator\", \"pi\");\n\theaders.set(\"User-Agent\", getPiUserAgent());\n\treturn headers;\n}\n\nfunction buildSSEHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\tsessionId?: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.set(\"OpenAI-Beta\", \"responses=experimental\");\n\theaders.set(\"accept\", \"text/event-stream\");\n\theaders.set(\"content-type\", \"application/json\");\n\n\tif (sessionId) {\n\t\theaders.set(\"session-id\", sessionId);\n\t\theaders.set(\"x-client-request-id\", sessionId);\n\t}\n\n\treturn headers;\n}\n\nfunction buildWebSocketHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\trequestId: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.delete(\"accept\");\n\theaders.delete(\"content-type\");\n\theaders.delete(\"OpenAI-Beta\");\n\theaders.delete(\"openai-beta\");\n\theaders.set(\"OpenAI-Beta\", OPENAI_BETA_RESPONSES_WEBSOCKETS);\n\theaders.set(\"x-client-request-id\", requestId);\n\theaders.set(\"session-id\", requestId);\n\treturn headers;\n}\n"]}
@@ -1,13 +1,14 @@
1
1
  import { clampThinkingLevel } from "../models.js";
2
2
  import { registerSessionResourceCleanup } from "../session-resources.js";
3
3
  import { combineAbortSignals } from "../utils/abort-signals.js";
4
- import { splitDeferredTools } from "../utils/deferred-tools.js";
5
4
  import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
6
5
  import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
7
6
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
7
  import { headersToRecord } from "../utils/headers.js";
9
8
  import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
10
9
  import { getPiUserAgent } from "../utils/pi-user-agent.js";
10
+ import { getSystemMessageText } from "../utils/text.js";
11
+ import { getDeclaredTools, getInitialSystemMessage, normalizeContext, resolveTranscript, resolveTranscriptTools, } from "../utils/transcript.js";
11
12
  import { uuidv7 } from "../utils/uuid.js";
12
13
  import { createGrammarToolInputProperties } from "./constrained-sampling.js";
13
14
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
@@ -141,6 +142,7 @@ function compressRequestBodyZstd(bodyJson) {
141
142
  // ============================================================================
142
143
  export const stream = (model, context, options) => {
143
144
  const stream = new AssistantMessageEventStream();
145
+ const normalizedContext = resolveTranscript(context, model.compat?.supportsMidConvoSystemMessages);
144
146
  (async () => {
145
147
  const output = {
146
148
  role: "assistant",
@@ -165,10 +167,10 @@ export const stream = (model, context, options) => {
165
167
  throw new Error(`No API key for provider: ${model.provider}`);
166
168
  }
167
169
  const accountId = extractAccountId(apiKey);
168
- const grammarToolInputProperties = createGrammarToolInputProperties(context.tools, model.compat?.supportsOpenAIGrammarTools ?? false);
170
+ const grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(normalizedContext.messages), model.compat?.supportsOpenAIGrammarTools ?? false);
169
171
  const cacheSessionId = options?.cacheRetention === "none" ? undefined : options?.sessionId;
170
172
  const codexSessionId = clampOpenAIPromptCacheKey(cacheSessionId);
171
- let body = buildRequestBody(model, context, options, codexSessionId, grammarToolInputProperties);
173
+ let body = buildRequestBody(model, normalizedContext, options, codexSessionId, grammarToolInputProperties);
172
174
  const nextBody = await options?.onPayload?.(body, model);
173
175
  if (nextBody !== undefined) {
174
176
  body = nextBody;
@@ -228,7 +230,7 @@ export const stream = (model, context, options) => {
228
230
  }
229
231
  appendAssistantMessageDiagnostic(output, createAssistantMessageDiagnostic("provider_transport_failure", error, {
230
232
  configuredTransport: transport,
231
- fallbackTransport: websocketStarted ? undefined : "sse",
233
+ ...(websocketStarted ? {} : { fallbackTransport: "sse" }),
232
234
  eventsEmitted: websocketStarted,
233
235
  phase: websocketStarted ? "after_message_stream_start" : "before_message_stream_start",
234
236
  requestBytes: new TextEncoder().encode(bodyJson).byteLength,
@@ -368,31 +370,27 @@ export const streamSimple = (model, context, options) => {
368
370
  // ============================================================================
369
371
  // Request Building
370
372
  // ============================================================================
371
- function buildRequestBody(model, context, options, cacheSessionId, grammarToolInputProperties = createGrammarToolInputProperties(context.tools, model.compat?.supportsOpenAIGrammarTools ?? false)) {
373
+ function buildRequestBody(model, context, options, cacheSessionId, grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(context.messages), model.compat?.supportsOpenAIGrammarTools ?? false)) {
372
374
  const supportsStrictMode = model.compat?.supportsStrictMode ?? true;
373
375
  const supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;
374
- const deferredToolsMode = model.compat?.supportsAdditionalTools
375
- ? "additional-tools"
376
- : model.compat?.supportsToolSearch
377
- ? "tool-search"
378
- : undefined;
379
- const toolPlacement = splitDeferredTools(context, deferredToolsMode !== undefined);
376
+ const supportsAdditionalTools = model.compat?.supportsAdditionalTools ?? false;
377
+ const supportsToolSearch = model.compat?.supportsToolSearch ?? false;
378
+ const transcriptTools = resolveTranscriptTools(context.messages, supportsAdditionalTools || supportsToolSearch);
380
379
  const messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {
381
380
  includeSystemPrompt: false,
382
381
  grammarToolInputProperties,
383
- deferredTools: toolPlacement.deferred,
384
- deferredToolsMode,
385
- toolOptions: {
386
- strict: null,
387
- supportsStrictMode,
388
- supportsOpenAIGrammarTools,
389
- },
382
+ supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
383
+ supportsAdditionalTools,
384
+ supportsToolSearch,
385
+ toolOptions: { strict: null, supportsStrictMode, supportsOpenAIGrammarTools },
390
386
  });
387
+ const initialSystemMessage = getInitialSystemMessage(context.messages);
388
+ const instructions = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : "";
391
389
  const body = {
392
390
  model: model.id,
393
391
  store: false,
394
392
  stream: true,
395
- instructions: context.systemPrompt || "You are a helpful assistant.",
393
+ instructions: instructions || "You are a helpful assistant.",
396
394
  input: messages,
397
395
  text: { verbosity: options?.textVerbosity || "low" },
398
396
  include: ["reasoning.encrypted_content"],
@@ -406,8 +404,8 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
406
404
  if (options?.serviceTier !== undefined) {
407
405
  body.service_tier = options.serviceTier;
408
406
  }
409
- if (toolPlacement.immediate.length > 0) {
410
- body.tools = convertResponsesTools(toolPlacement.immediate, {
407
+ if (transcriptTools.requestTools.length > 0) {
408
+ body.tools = convertResponsesTools(transcriptTools.requestTools, {
411
409
  strict: null,
412
410
  supportsStrictMode,
413
411
  supportsOpenAIGrammarTools,
@@ -415,7 +413,9 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
415
413
  }
416
414
  if (options?.reasoningEffort !== undefined) {
417
415
  const effort = options.reasoningEffort === "none"
418
- ? (model.thinkingLevelMap?.off ?? "none")
416
+ ? model.thinkingLevelMap?.off === undefined
417
+ ? "none"
418
+ : model.thinkingLevelMap.off
419
419
  : (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);
420
420
  if (effort !== null) {
421
421
  body.reasoning = {
@@ -424,6 +424,9 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
424
424
  };
425
425
  }
426
426
  }
427
+ else if (model.reasoning && model.thinkingLevelMap?.off !== null) {
428
+ body.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" };
429
+ }
427
430
  return body;
428
431
  }
429
432
  function getServiceTierCostMultiplier(model, serviceTier) {
@@ -1193,7 +1196,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
1193
1196
  keepConnection = false;
1194
1197
  }
1195
1198
  else if (useCachedContext && entry && output.responseId) {
1196
- const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
1199
+ const responseItems = convertResponsesMessages(model, normalizeContext({ messages: [output] }), CODEX_TOOL_CALL_PROVIDERS, {
1197
1200
  includeSystemPrompt: false,
1198
1201
  grammarToolInputProperties,
1199
1202
  }).filter((item) => item.type !== "function_call_output" && item.type !== "custom_tool_call_output");