@gajae-code/ai 0.18.1 → 0.18.6

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 (38) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/types/auth-storage.d.ts +8 -1
  3. package/dist/types/index.d.ts +0 -1
  4. package/dist/types/providers/cursor.d.ts +6 -3
  5. package/dist/types/providers/kiro-api-key.d.ts +1 -0
  6. package/dist/types/providers/openai-codex-responses.d.ts +2 -0
  7. package/dist/types/types.d.ts +1 -1
  8. package/dist/types/utils/fallback-transport.d.ts +6 -0
  9. package/dist/types/utils/oauth/glm-zcode.d.ts +5 -0
  10. package/dist/types/utils/oauth/index.d.ts +4 -0
  11. package/package.json +3 -3
  12. package/src/auth-storage.ts +115 -39
  13. package/src/index.ts +0 -1
  14. package/src/model-pricing.ts +10 -0
  15. package/src/model-thinking.ts +8 -0
  16. package/src/models.json +210 -0
  17. package/src/providers/anthropic.ts +25 -1
  18. package/src/providers/aws-eventstream.ts +2 -1
  19. package/src/providers/cursor.d.ts +6 -3
  20. package/src/providers/cursor.ts +73 -64
  21. package/src/providers/kiro-api-key.d.ts +1 -0
  22. package/src/providers/kiro-api-key.ts +1 -1
  23. package/src/providers/kiro-codewhisperer.ts +142 -34
  24. package/src/providers/openai-codex-responses.d.ts +2 -0
  25. package/src/providers/openai-codex-responses.ts +438 -40
  26. package/src/providers/openai-completions.ts +19 -1
  27. package/src/stream.ts +20 -0
  28. package/src/types.d.ts +1 -1
  29. package/src/types.ts +3 -0
  30. package/src/utils/fallback-transport.d.ts +6 -0
  31. package/src/utils/fallback-transport.ts +6 -0
  32. package/src/utils/idle-iterator.ts +10 -3
  33. package/src/utils/oauth/glm-zcode.d.ts +5 -0
  34. package/src/utils/oauth/glm-zcode.ts +8 -1
  35. package/src/utils/oauth/index.ts +11 -1
  36. package/src/utils/validation.ts +5 -0
  37. package/dist/types/utils/h2-fetch.d.ts +0 -35
  38. package/src/utils/h2-fetch.ts +0 -133
@@ -52,7 +52,11 @@ import {
52
52
  sanitizeOpenAIResponsesHistoryItemsForReplay,
53
53
  } from "../utils";
54
54
  import { AssistantMessageEventStream } from "../utils/event-stream";
55
- import { STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE, transportFailureFacts } from "../utils/fallback-transport";
55
+ import {
56
+ SERVER_OVERLOADED_PROVIDER_CODE,
57
+ STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE,
58
+ transportFailureFacts,
59
+ } from "../utils/fallback-transport";
56
60
  import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
57
61
  import {
58
62
  getOpenAIStreamIdleTimeoutMs,
@@ -114,8 +118,12 @@ const CODEX_MAX_RETRIES = 5;
114
118
  const CODEX_RETRY_DELAY_MS = 500;
115
119
  const CODEX_WEBSOCKET_CONNECT_TIMEOUT_MS = 10000;
116
120
  const CODEX_WEBSOCKET_IDLE_TIMEOUT_MS = 300000;
121
+ /** Longest delay setTimeout honors; larger delays fire after 1 ms, so waits are capped here. */
122
+ const CODEX_MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
117
123
  const CODEX_WEBSOCKET_RETRY_BUDGET = CODEX_MAX_RETRIES;
118
124
  const CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX = "Codex websocket transport error";
125
+ const CODEX_SALVAGED_STREAM_CLOSE_ERROR_CODE = "codex_stream_closed_after_finalized_tool_calls";
126
+ const CODEX_SALVAGED_COMPLETE_TOOL_ARGUMENTS_ERROR_CODE = "codex_stream_closed_after_complete_tool_arguments";
119
127
  const CODEX_PREVIOUS_RESPONSE_STALE_CODES = new Set(["previous_response_not_found", "codex_previous_response_stale"]);
120
128
  // Some Codex deployments reject a stale continuation anchor with a generic
121
129
  // `invalid_request_error` code and name the anchor only in the message
@@ -157,7 +165,17 @@ const CODEX_PREVIOUS_RESPONSE_STALE_PROSE_MESSAGE = new RegExp(
157
165
  `|${CODEX_PREVIOUS_RESPONSE_PROSE_TOKEN}(?:(?!${CODEX_PREVIOUS_RESPONSE_STALE_SUBFIELD_GUARD})[^\\n]){0,48}?(?:${CODEX_ANCHOR_STALE_QUALIFIER})(?![^\\n]{0,48}(?:${CODEX_PREVIOUS_RESPONSE_STALE_SUBFIELD_GUARD}))`,
158
166
  "i",
159
167
  );
160
- const CODEX_RETRYABLE_EVENT_CODES = new Set(["model_error", "server_error", "internal_error"]);
168
+ const CODEX_RETRYABLE_EVENT_CODES = new Set([
169
+ "model_error",
170
+ "server_error",
171
+ "internal_error",
172
+ SERVER_OVERLOADED_PROVIDER_CODE,
173
+ ]);
174
+ const CODEX_TYPED_TRANSPORT_PROVIDER_CODES = new Set([
175
+ SERVER_OVERLOADED_PROVIDER_CODE,
176
+ "server_error",
177
+ "internal_error",
178
+ ]);
161
179
  const CODEX_NON_RETRYABLE_EVENT_CODES = new Set([
162
180
  "invalid_function_parameters",
163
181
  "invalid_request_error",
@@ -176,11 +194,31 @@ const CODEX_RETRYABLE_EVENT_MESSAGE =
176
194
  /processing your request|retry your request|temporar(?:y|ily)|overloaded|service.?unavailable|internal error|server error/i;
177
195
  const CODEX_ACCOUNT_MODEL_UNAVAILABLE_MESSAGE = /\bnot supported when using codex with a chatgpt account\b/i;
178
196
  const CODEX_PROVIDER_SESSION_STATE_KEY = "openai-codex-responses";
197
+ const providerSessionStateIdentities = new WeakMap<Map<string, ProviderSessionState>, number>();
198
+ const disabledCodexPublicSessionKeys = new Set<string>();
199
+ let nextProviderSessionStateIdentity = 1;
200
+
201
+ function getProviderSessionStateIdentity(
202
+ providerSessionState: Map<string, ProviderSessionState> | undefined,
203
+ ): number | undefined {
204
+ if (!providerSessionState) return undefined;
205
+ let identity = providerSessionStateIdentities.get(providerSessionState);
206
+ if (identity === undefined) {
207
+ identity = nextProviderSessionStateIdentity++;
208
+ providerSessionStateIdentities.set(providerSessionState, identity);
209
+ }
210
+ return identity;
211
+ }
179
212
  const X_CODEX_TURN_STATE_HEADER = "x-codex-turn-state";
180
213
  const X_MODELS_ETAG_HEADER = "x-models-etag";
181
214
  const X_REASONING_INCLUDED_HEADER = "x-reasoning-included";
182
215
  /** Connection-level websocket failures that should immediately fall back to SSE without retrying. */
183
- const CODEX_WEBSOCKET_FATAL_PATTERNS = ["websocket error:", "websocket closed before open", "connection timeout"];
216
+ const CODEX_WEBSOCKET_FATAL_PATTERNS = [
217
+ "websocket error:",
218
+ "websocket closed before open",
219
+ "websocket is closed before the connection is established",
220
+ "connection timeout",
221
+ ];
184
222
  /** Max total time to spend retrying 429s with server-provided delays (5 minutes). */
185
223
  const CODEX_RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
186
224
 
@@ -207,10 +245,7 @@ const CODEX_PROGRESS_EVENT_TYPES = new Set([
207
245
  ]);
208
246
 
209
247
  /**
210
- * A progress event must carry real semantic payload, matching the Anthropic
211
- * predicate: a recognized envelope whose `delta` is absent or not a non-empty
212
- * string is NOT progress — otherwise repeated malformed/no-op deltas reset the
213
- * idle watchdog indefinitely and a managed attempt need never terminate.
248
+ * A progress event must carry a non-empty delta for delta events.
214
249
  * Non-delta envelope types (lifecycle, item boundaries, terminal events) count
215
250
  * as progress by type alone, as before.
216
251
  */
@@ -223,13 +258,100 @@ function isCodexStreamProgressEvent(event: unknown): boolean {
223
258
  return typeof delta === "string" && delta.length > 0;
224
259
  }
225
260
 
261
+ type CodexArgumentBuffer = { value: string };
262
+
263
+ /** @internal Exported for tests. */
264
+ export function createCodexStreamProgressClassifier(): (event: unknown) => boolean {
265
+ const argumentBuffers = new Map<string, CodexArgumentBuffer>();
266
+
267
+ const getArgumentKey = (event: Record<string, unknown>): string | undefined => {
268
+ if (typeof event.item_id === "string") return `item:${event.item_id}`;
269
+ if (typeof event.output_index === "number") return `output:${event.output_index}`;
270
+ return undefined;
271
+ };
272
+
273
+ const rememberFunctionCallItem = (event: Record<string, unknown>): void => {
274
+ if (event.type !== "response.output_item.added" || !event.item || typeof event.item !== "object") return;
275
+ const item = event.item as Record<string, unknown>;
276
+ if (item.type !== "function_call" || typeof item.arguments !== "string") return;
277
+ const buffer = { value: item.arguments };
278
+ if (typeof item.id === "string") argumentBuffers.set(`item:${item.id}`, buffer);
279
+ if (typeof event.output_index === "number") argumentBuffers.set(`output:${event.output_index}`, buffer);
280
+ };
281
+
282
+ // Reads `type` and `delta` exactly once per event: the stream handler reads the
283
+ // same parsed event afterwards, and a second read here could observe a
284
+ // different value than the one that is classified and assembled.
285
+ return event => {
286
+ if (!event || typeof event !== "object") return false;
287
+ const record = event as Record<string, unknown>;
288
+ const type = record.type;
289
+ if (typeof type !== "string" || !CODEX_PROGRESS_EVENT_TYPES.has(type)) return false;
290
+ if (type.endsWith(".delta")) {
291
+ const delta = record.delta;
292
+ if (typeof delta !== "string" || delta.length === 0) return false;
293
+ if (type !== "response.function_call_arguments.delta") return true;
294
+ return classifyArgumentDelta(record, delta);
295
+ }
296
+ rememberFunctionCallItem(record);
297
+ if (type === "response.function_call_arguments.done") {
298
+ const key = getArgumentKey(record);
299
+ const buffer = key ? argumentBuffers.get(key) : undefined;
300
+ const argumentsValue = record.arguments;
301
+ if (buffer && typeof argumentsValue === "string") buffer.value = argumentsValue;
302
+ }
303
+ return true;
304
+ };
305
+
306
+ function classifyArgumentDelta(record: Record<string, unknown>, delta: string): boolean {
307
+ const key = getArgumentKey(record);
308
+ if (!key) return true;
309
+ let buffer = argumentBuffers.get(key);
310
+ if (!buffer) {
311
+ buffer = { value: "" };
312
+ argumentBuffers.set(key, buffer);
313
+ }
314
+ const isWhitespaceOnly = delta.trim().length === 0;
315
+ const wasComplete =
316
+ isWhitespaceOnly &&
317
+ (() => {
318
+ try {
319
+ JSON.parse(buffer.value);
320
+ return true;
321
+ } catch {
322
+ return false;
323
+ }
324
+ })();
325
+ buffer.value += delta;
326
+ return !wasComplete;
327
+ }
328
+ }
329
+
226
330
  function codexOutputHasMeaningfulProgress(output: AssistantMessage): boolean {
227
331
  return output.content.some(
228
332
  block =>
229
- (block.type === "text" && block.text.length > 0) || (block.type === "thinking" && block.thinking.length > 0),
333
+ (block.type === "text" && block.text.length > 0) ||
334
+ (block.type === "thinking" && block.thinking.length > 0) ||
335
+ (block.type === "toolCall" &&
336
+ typeof block.arguments === "object" &&
337
+ block.arguments !== null &&
338
+ !Array.isArray(block.arguments) &&
339
+ Object.keys(block.arguments).length > 0),
230
340
  );
231
341
  }
232
342
 
343
+ function isPlainJsonObject(value: unknown): value is Record<string, unknown> {
344
+ return typeof value === "object" && value !== null && !Array.isArray(value);
345
+ }
346
+
347
+ function isCompleteJsonObject(value: string): boolean {
348
+ try {
349
+ return isPlainJsonObject(JSON.parse(value));
350
+ } catch {
351
+ return false;
352
+ }
353
+ }
354
+
233
355
  type CodexTransport = "sse" | "websocket";
234
356
  interface CodexInitialTransport {
235
357
  eventStream: AsyncGenerator<Record<string, unknown>>;
@@ -241,7 +363,15 @@ interface CodexInitialTransport {
241
363
  }
242
364
  type CodexEventItem = ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
243
365
  type CodexThinkingBlock = ThinkingContent & { summaryBuffer: string; rawBuffer: string; summaryStarted: boolean };
244
- type CodexOutputBlock = CodexThinkingBlock | TextContent | (ToolCall & { partialJson: string; doneInput?: string });
366
+ type CodexOutputBlock =
367
+ | CodexThinkingBlock
368
+ | TextContent
369
+ | (ToolCall & {
370
+ partialJson: string;
371
+ doneInput?: string;
372
+ argumentsComplete?: boolean;
373
+ argumentsAuthoritative?: boolean;
374
+ });
245
375
  export interface OpenAICodexWebSocketDebugStats {
246
376
  fullContextRequests: number;
247
377
  deltaRequests: number;
@@ -270,6 +400,7 @@ type CodexWebSocketSessionState = {
270
400
  interface CodexProviderSessionState extends ProviderSessionState {
271
401
  webSocketSessions: Map<string, CodexWebSocketSessionState>;
272
402
  webSocketPublicToPrivate: Map<string, string>;
403
+ disabledWebSocketPublicSessions: Set<string>;
273
404
  }
274
405
 
275
406
  interface CodexRequestContext {
@@ -336,6 +467,7 @@ interface CodexStreamRuntime {
336
467
  transport: CodexTransport;
337
468
  websocketState?: CodexWebSocketSessionState;
338
469
  currentItem: CodexEventItem | null;
470
+ currentItemOutputIndex?: number;
339
471
  currentBlock: CodexOutputBlock | null;
340
472
  nativeOutputItems: Array<Record<string, unknown>>;
341
473
  websocketStreamRetries: number;
@@ -362,6 +494,8 @@ interface CodexStreamRuntime {
362
494
  finalizedToolCallIds: Set<string>;
363
495
  /** Event types whose degraded non-string increment was already diagnosed. */
364
496
  degradedIncrementDiagnostics: Set<string>;
497
+ /** An argument event identified a different active item; never salvage this response. */
498
+ toolArgumentCorrelationFailed: boolean;
365
499
  }
366
500
 
367
501
  interface CodexStreamProcessingContext {
@@ -429,12 +563,14 @@ function createCodexProviderSessionState(): CodexProviderSessionState {
429
563
  const state: CodexProviderSessionState = {
430
564
  webSocketSessions: new Map(),
431
565
  webSocketPublicToPrivate: new Map(),
566
+ disabledWebSocketPublicSessions: new Set(),
432
567
  close: () => {
433
568
  for (const session of state.webSocketSessions.values()) {
434
569
  session.connection?.close("session_disposed");
435
570
  }
436
571
  state.webSocketSessions.clear();
437
572
  state.webSocketPublicToPrivate.clear();
573
+ state.disabledWebSocketPublicSessions.clear();
438
574
  },
439
575
  };
440
576
  return state;
@@ -680,6 +816,8 @@ function removeTransientBlockIndices(output: AssistantMessage): void {
680
816
  if (block.type === "toolCall") {
681
817
  delete (block as { partialJson?: string }).partialJson;
682
818
  delete (block as { doneInput?: string }).doneInput;
819
+ delete (block as { argumentsComplete?: boolean }).argumentsComplete;
820
+ delete (block as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
683
821
  }
684
822
  }
685
823
  }
@@ -702,7 +840,7 @@ function createRequestSetup(options: OpenAICodexResponsesOptions | undefined): C
702
840
  onIdle: () => requestAbortController.abort(),
703
841
  onFirstItemTimeout: () => requestAbortController.abort(),
704
842
  abortSignal: options?.signal,
705
- isProgressItem: isCodexStreamProgressEvent,
843
+ isProgressItem: createCodexStreamProgressClassifier(),
706
844
  });
707
845
  return { requestAbortController, requestSignal, firstEventTimeoutMs, wrapCodexSseStream };
708
846
  }
@@ -746,11 +884,27 @@ async function buildCodexRequestContext(
746
884
  const providerSessionState = getCodexProviderSessionState(options?.providerSessionState);
747
885
  const sessionKey = getCodexWebSocketSessionKey(promptCacheKey, model, accountId, baseUrl);
748
886
  const publicSessionKey = getCodexPublicSessionKey(promptCacheKey, model, baseUrl);
749
- if (sessionKey && publicSessionKey) {
750
- providerSessionState?.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
751
- }
887
+ const effectiveSessionKey =
888
+ providerSessionState && sessionKey
889
+ ? resolveCodexWebSocketSessionKey(sessionKey, publicSessionKey, providerSessionState)
890
+ : undefined;
891
+ logCodexDebug("build request context session keys", {
892
+ publicSessionKey,
893
+ effectiveSessionKey,
894
+ providerSessionStateMapIdentity: getProviderSessionStateIdentity(options?.providerSessionState),
895
+ });
752
896
  const websocketState =
753
- sessionKey && providerSessionState ? getCodexWebSocketSessionState(sessionKey, providerSessionState) : undefined;
897
+ providerSessionState && effectiveSessionKey
898
+ ? getCodexWebSocketSessionState(effectiveSessionKey, providerSessionState)
899
+ : undefined;
900
+ if (
901
+ websocketState &&
902
+ publicSessionKey &&
903
+ (disabledCodexPublicSessionKeys.has(publicSessionKey) ||
904
+ providerSessionState?.disabledWebSocketPublicSessions.has(publicSessionKey))
905
+ ) {
906
+ websocketState.disableWebsocket = true;
907
+ }
754
908
 
755
909
  return {
756
910
  apiKey,
@@ -1063,6 +1217,7 @@ function createCodexStreamRuntime(initial: {
1063
1217
  transport: initial.transport,
1064
1218
  websocketState: initial.websocketState,
1065
1219
  currentItem: null,
1220
+ currentItemOutputIndex: undefined,
1066
1221
  currentBlock: null,
1067
1222
  nativeOutputItems: [],
1068
1223
  websocketStreamRetries: 0,
@@ -1077,6 +1232,7 @@ function createCodexStreamRuntime(initial: {
1077
1232
  canSafelyReplayWebsocketOverSse: true,
1078
1233
  finalizedToolCallIds: new Set<string>(),
1079
1234
  degradedIncrementDiagnostics: new Set<string>(),
1235
+ toolArgumentCorrelationFailed: false,
1080
1236
  };
1081
1237
  }
1082
1238
 
@@ -1086,10 +1242,10 @@ async function processCodexResponseStream(
1086
1242
  ): Promise<CodexStreamCompletion> {
1087
1243
  const { output, stream } = context;
1088
1244
  stream.push({ type: "start", partial: output });
1245
+ let firstTokenTime = context.firstTokenTime;
1089
1246
 
1090
1247
  while (true) {
1091
1248
  try {
1092
- let firstTokenTime = context.firstTokenTime;
1093
1249
  for await (const rawEvent of runtime.eventStream) {
1094
1250
  firstTokenTime = handleCodexStreamEvent({
1095
1251
  ...context,
@@ -1101,6 +1257,9 @@ async function processCodexResponseStream(
1101
1257
  }
1102
1258
  return { firstTokenTime };
1103
1259
  } catch (error) {
1260
+ if (trySalvageCodexFinalizedToolCalls(context, runtime, error)) {
1261
+ return { firstTokenTime };
1262
+ }
1104
1263
  const recovered = await recoverCodexStreamError(context, runtime, error);
1105
1264
  if (!recovered) {
1106
1265
  throw error;
@@ -1109,6 +1268,118 @@ async function processCodexResponseStream(
1109
1268
  }
1110
1269
  }
1111
1270
 
1271
+ function trySalvageCodexFinalizedToolCalls(
1272
+ context: CodexStreamProcessingContext,
1273
+ runtime: CodexStreamRuntime,
1274
+ error: unknown,
1275
+ ): boolean {
1276
+ const toolCalls = context.output.content.filter((block): block is ToolCall => block.type === "toolCall");
1277
+ const activeToolCall =
1278
+ runtime.currentItem?.type === "function_call" && runtime.currentBlock?.type === "toolCall"
1279
+ ? runtime.currentBlock
1280
+ : undefined;
1281
+ const completeActiveToolCall =
1282
+ activeToolCall?.argumentsComplete === true &&
1283
+ (activeToolCall.argumentsAuthoritative === true || Object.keys(activeToolCall.arguments).length > 0);
1284
+ const hasCompleteArguments = toolCalls.every(
1285
+ toolCall =>
1286
+ runtime.finalizedToolCallIds.has(toolCall.id) || (toolCall === activeToolCall && completeActiveToolCall),
1287
+ );
1288
+ const isIdleStall =
1289
+ error instanceof Error && error.message === "OpenAI Codex SSE stream stalled while waiting for the next event";
1290
+ const canSalvageFinalizedCall =
1291
+ isCodexTransientStreamClose(error) &&
1292
+ !runtime.toolArgumentCorrelationFailed &&
1293
+ toolCalls.length > 0 &&
1294
+ (runtime.currentBlock === null ||
1295
+ (runtime.currentItem?.type === "reasoning" && runtime.currentBlock?.type === "thinking")) &&
1296
+ context.output.content.every(block => block.type === "thinking" || block.type === "toolCall") &&
1297
+ toolCalls.every(toolCall => runtime.finalizedToolCallIds.has(toolCall.id));
1298
+ const canSalvageCompleteArguments =
1299
+ (isCodexTransientStreamClose(error) || isIdleStall) &&
1300
+ !runtime.toolArgumentCorrelationFailed &&
1301
+ toolCalls.length > 0 &&
1302
+ context.output.content.every(block => block.type === "thinking" || block.type === "toolCall") &&
1303
+ hasCompleteArguments;
1304
+ if (!canSalvageFinalizedCall && !canSalvageCompleteArguments) {
1305
+ return false;
1306
+ }
1307
+
1308
+ if (canSalvageFinalizedCall && runtime.currentBlock?.type === "thinking") {
1309
+ context.stream.push({
1310
+ type: "thinking_end",
1311
+ contentIndex: context.output.content.length - 1,
1312
+ content: runtime.currentBlock.thinking,
1313
+ partial: context.output,
1314
+ });
1315
+ runtime.currentItem = null;
1316
+ runtime.currentItemOutputIndex = undefined;
1317
+ runtime.currentBlock = null;
1318
+ }
1319
+
1320
+ if (canSalvageCompleteArguments && activeToolCall && !runtime.finalizedToolCallIds.has(activeToolCall.id)) {
1321
+ const item = runtime.currentItem;
1322
+ const toolCall: ToolCall = {
1323
+ type: "toolCall",
1324
+ id: activeToolCall.id,
1325
+ name: activeToolCall.name,
1326
+ arguments: activeToolCall.arguments,
1327
+ };
1328
+ const rawPartialJson = activeToolCall.partialJson;
1329
+ captureUnicodeEscapeEvidence(toolCall, rawPartialJson);
1330
+ Object.assign(activeToolCall, toolCall);
1331
+ captureUnicodeEscapeEvidence(activeToolCall, rawPartialJson);
1332
+ delete (activeToolCall as { partialJson?: string }).partialJson;
1333
+ delete (activeToolCall as { argumentsComplete?: boolean }).argumentsComplete;
1334
+ delete (activeToolCall as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
1335
+ runtime.finalizedToolCallIds.add(toolCall.id);
1336
+ runtime.nativeOutputItems.push({
1337
+ ...item,
1338
+ arguments: JSON.stringify(toolCall.arguments),
1339
+ status: "completed",
1340
+ } as Record<string, unknown>);
1341
+ context.stream.push({
1342
+ type: "toolcall_end",
1343
+ contentIndex: context.output.content.length - 1,
1344
+ toolCall,
1345
+ partial: context.output,
1346
+ });
1347
+ runtime.currentItem = null;
1348
+ runtime.currentBlock = null;
1349
+ }
1350
+
1351
+ context.output.stopReason = "toolUse";
1352
+ context.output.errorCode = canSalvageFinalizedCall
1353
+ ? CODEX_SALVAGED_STREAM_CLOSE_ERROR_CODE
1354
+ : CODEX_SALVAGED_COMPLETE_TOOL_ARGUMENTS_ERROR_CODE;
1355
+ runtime.sawTerminalEvent = true;
1356
+ logCodexDebug("codex stream closed after complete tool arguments; salvaging tool-use response", {
1357
+ error: error instanceof Error ? error.message : String(error),
1358
+ toolCallCount: toolCalls.length,
1359
+ transport: runtime.transport,
1360
+ });
1361
+ if (context.requestContext.websocketState) {
1362
+ resetCodexWebSocketAppendState(context.requestContext.websocketState);
1363
+ }
1364
+ return true;
1365
+ }
1366
+
1367
+ function isCodexTransientStreamClose(error: unknown): boolean {
1368
+ if (!(error instanceof Error)) return false;
1369
+ const providerMessage = (error as CodexProviderStreamError).providerMessage;
1370
+ const message = (providerMessage || error.message).toLowerCase();
1371
+ const providerCode =
1372
+ (error as CodexProviderStreamError & { providerCode?: string }).code?.toLowerCase() ??
1373
+ (error as { providerCode?: string }).providerCode?.toLowerCase();
1374
+ if (providerCode && CODEX_NON_RETRYABLE_EVENT_CODES.has(providerCode)) return false;
1375
+ const hasRequestTimeout = providerCode === "request_timeout" || message.includes("request_timeout");
1376
+ const hasClosedStreamMessage =
1377
+ message.includes("stream disconnected before completion") ||
1378
+ message.includes("stream closed before response.completed") ||
1379
+ message.includes("websocket closed before response completion");
1380
+ return hasRequestTimeout && hasClosedStreamMessage;
1381
+ }
1382
+
1112
1383
  function handleCodexStreamEvent(args: {
1113
1384
  model: Model<"openai-codex-responses">;
1114
1385
  output: AssistantMessage;
@@ -1129,6 +1400,7 @@ function handleCodexStreamEvent(args: {
1129
1400
  if (!firstTokenTime) firstTokenTime = Date.now();
1130
1401
  const item = rawEvent.item as CodexEventItem;
1131
1402
  runtime.currentItem = item;
1403
+ runtime.currentItemOutputIndex = typeof rawEvent.output_index === "number" ? rawEvent.output_index : undefined;
1132
1404
  runtime.currentBlock = createOutputBlockForItem(item);
1133
1405
  if (!runtime.currentBlock) return firstTokenTime;
1134
1406
  const currentBlock = runtime.currentBlock;
@@ -1215,12 +1487,20 @@ function handleCodexStreamEvent(args: {
1215
1487
  }
1216
1488
 
1217
1489
  if (eventType === "response.function_call_arguments.delta") {
1490
+ if (!isActiveToolArgumentEvent(runtime, rawEvent)) {
1491
+ runtime.toolArgumentCorrelationFailed = true;
1492
+ return firstTokenTime;
1493
+ }
1218
1494
  const delta = assertStringToolArgumentIncrement(rawEvent, "response.function_call_arguments.delta");
1219
1495
  handleToolCallArgumentsDelta(runtime.currentItem, runtime.currentBlock, delta, stream, output, blockIndex);
1220
1496
  return firstTokenTime;
1221
1497
  }
1222
1498
 
1223
1499
  if (eventType === "response.function_call_arguments.done") {
1500
+ if (!isActiveToolArgumentEvent(runtime, rawEvent)) {
1501
+ runtime.toolArgumentCorrelationFailed = true;
1502
+ return firstTokenTime;
1503
+ }
1224
1504
  handleToolCallArgumentsDone(runtime.currentItem, runtime.currentBlock, rawEvent);
1225
1505
  return firstTokenTime;
1226
1506
  }
@@ -1265,13 +1545,31 @@ function createOutputBlockForItem(item: CodexEventItem): CodexOutputBlock | null
1265
1545
  return { type: "text", text: "" };
1266
1546
  }
1267
1547
  if (item.type === "function_call") {
1268
- return {
1548
+ const initialArguments = item.arguments || "";
1549
+ let parsedArguments: Record<string, unknown> = parseStreamingJson(initialArguments);
1550
+ try {
1551
+ const parsed = JSON.parse(initialArguments);
1552
+ if (isPlainJsonObject(parsed)) parsedArguments = parsed;
1553
+ } catch {
1554
+ // Keep the streaming parser's partial object for deltas, but do not
1555
+ // treat an incomplete snapshot as executable arguments.
1556
+ }
1557
+ const hasValidInitialArguments = isCompleteJsonObject(initialArguments);
1558
+ const block: ToolCall & {
1559
+ partialJson: string;
1560
+ argumentsComplete: boolean;
1561
+ argumentsAuthoritative: boolean;
1562
+ } = {
1269
1563
  type: "toolCall",
1270
1564
  id: encodeResponsesToolCallId(item.call_id, item.id),
1271
1565
  name: codexToolCanonicalName(item.name),
1272
- arguments: {},
1273
- partialJson: item.arguments || "",
1566
+ arguments: hasValidInitialArguments ? parsedArguments : {},
1567
+ partialJson: initialArguments,
1568
+ argumentsComplete: hasValidInitialArguments && Object.keys(parsedArguments).length > 0,
1569
+ argumentsAuthoritative: false,
1274
1570
  };
1571
+ captureUnicodeEscapeEvidence(block, initialArguments);
1572
+ return block;
1275
1573
  }
1276
1574
  if (item.type === "custom_tool_call") {
1277
1575
  const initialInput: unknown = item.input;
@@ -1440,8 +1738,14 @@ function handleToolCallArgumentsDelta(
1440
1738
  blockIndex: () => number,
1441
1739
  ): void {
1442
1740
  if (currentItem?.type !== "function_call" || currentBlock?.type !== "toolCall") return;
1741
+ if (currentBlock.argumentsComplete === true && delta.trim().length > 0) {
1742
+ currentBlock.argumentsComplete = false;
1743
+ }
1443
1744
  currentBlock.partialJson += delta;
1444
1745
  currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
1746
+ currentBlock.argumentsComplete = isCompleteJsonObject(currentBlock.partialJson);
1747
+ currentBlock.argumentsAuthoritative =
1748
+ currentBlock.argumentsAuthoritative || (currentBlock.argumentsComplete && delta.trim().length > 0);
1445
1749
  stream.push({ type: "toolcall_delta", contentIndex: blockIndex(), delta, partial: output });
1446
1750
  }
1447
1751
 
@@ -1455,10 +1759,25 @@ function handleToolCallArgumentsDone(
1455
1759
  if (typeof args === "string") {
1456
1760
  currentBlock.partialJson = args;
1457
1761
  currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
1762
+ currentBlock.argumentsComplete = isCompleteJsonObject(currentBlock.partialJson);
1763
+ currentBlock.argumentsAuthoritative = currentBlock.argumentsComplete;
1458
1764
  captureUnicodeEscapeEvidence(currentBlock, args);
1459
1765
  }
1460
1766
  }
1461
1767
 
1768
+ function isActiveToolArgumentEvent(runtime: CodexStreamRuntime, rawEvent: Record<string, unknown>): boolean {
1769
+ if (runtime.currentItem?.type !== "function_call" || runtime.currentBlock?.type !== "toolCall") return false;
1770
+ const itemId = rawEvent.item_id;
1771
+ if (typeof itemId === "string" && itemId !== runtime.currentItem.id) return false;
1772
+ const callId = rawEvent.call_id;
1773
+ if (typeof callId === "string" && callId !== runtime.currentItem.call_id) return false;
1774
+ const outputIndex = rawEvent.output_index;
1775
+ if (typeof outputIndex === "number" && outputIndex !== runtime.currentItemOutputIndex) {
1776
+ return false;
1777
+ }
1778
+ return true;
1779
+ }
1780
+
1462
1781
  function handleCustomToolCallInputDelta(
1463
1782
  currentItem: CodexEventItem | null,
1464
1783
  currentBlock: CodexOutputBlock | null,
@@ -1598,6 +1917,8 @@ function handleOutputItemDone(
1598
1917
  Object.assign(runtime.currentBlock, toolCall);
1599
1918
  captureUnicodeEscapeEvidence(runtime.currentBlock, item.arguments);
1600
1919
  delete (runtime.currentBlock as { partialJson?: string }).partialJson;
1920
+ delete (runtime.currentBlock as { argumentsComplete?: boolean }).argumentsComplete;
1921
+ delete (runtime.currentBlock as { argumentsAuthoritative?: boolean }).argumentsAuthoritative;
1601
1922
  delete (runtime.currentBlock as { doneInput?: string }).doneInput;
1602
1923
  runtime.canSafelyReplayWebsocketOverSse = false;
1603
1924
  stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
@@ -1738,6 +2059,7 @@ async function recoverCodexStreamError(
1738
2059
  if (isCodexFirstEventTimeout(error)) return false;
1739
2060
  // A post-progress idle timeout has already consumed the stream's silence budget.
1740
2061
  // Replaying it over another transport can leave the turn silent for another full window.
2062
+ // A tool-call start is progress even without reasoning, usage, or output_item.done.
1741
2063
  if (
1742
2064
  (codexOutputHasMeaningfulProgress(context.output) ||
1743
2065
  (context.output.content.some(block => block.type === "thinking") &&
@@ -1805,6 +2127,7 @@ async function tryRetryWithoutForcedToolChoice(
1805
2127
  runtime.sawTerminalEvent = false;
1806
2128
  runtime.nativeOutputItems.length = 0;
1807
2129
  runtime.finalizedToolCallIds.clear();
2130
+ runtime.toolArgumentCorrelationFailed = false;
1808
2131
  resetOutputState(context.output);
1809
2132
  context.firstTokenTime = undefined;
1810
2133
 
@@ -1903,6 +2226,7 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
1903
2226
  runtime.currentBlock = null;
1904
2227
  runtime.nativeOutputItems.length = 0;
1905
2228
  runtime.finalizedToolCallIds.clear();
2229
+ runtime.toolArgumentCorrelationFailed = false;
1906
2230
  resetOutputState(context.output);
1907
2231
  context.firstTokenTime = undefined;
1908
2232
  recordCodexWebSocketFailure(websocketState, true);
@@ -1940,14 +2264,14 @@ async function tryRecoverCodexPreviousResponseNotFound(
1940
2264
  // validation fault: clearing session metadata and resending the identical
1941
2265
  // body cannot fix it.
1942
2266
  !runtime.sentPreviousResponseId ||
2267
+ // Managed fallback forces streamMaxRetries to 0; this replay is exempt
2268
+ // from that budget and bounded by previousResponseRecoveryAttempted.
1943
2269
  runtime.previousResponseRecoveryAttempted ||
1944
2270
  !websocketState ||
1945
- context.options?.fallbackManaged ||
1946
2271
  context.options?.disableProviderRetries ||
1947
2272
  runtime.transport !== "websocket" ||
1948
2273
  context.output.content.length > 0 ||
1949
- context.options?.signal?.aborted ||
1950
- runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES)
2274
+ context.options?.signal?.aborted
1951
2275
  ) {
1952
2276
  return false;
1953
2277
  }
@@ -1961,6 +2285,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
1961
2285
  runtime.sawTerminalEvent = false;
1962
2286
  runtime.nativeOutputItems.length = 0;
1963
2287
  runtime.finalizedToolCallIds.clear();
2288
+ runtime.toolArgumentCorrelationFailed = false;
1964
2289
  resetOutputState(context.output);
1965
2290
  context.firstTokenTime = undefined;
1966
2291
 
@@ -2021,6 +2346,7 @@ async function tryReplayWebsocketFailureOverSse(
2021
2346
  runtime.currentBlock = null;
2022
2347
  runtime.nativeOutputItems.length = 0;
2023
2348
  runtime.finalizedToolCallIds.clear();
2349
+ runtime.toolArgumentCorrelationFailed = false;
2024
2350
  resetOutputState(context.output);
2025
2351
  context.firstTokenTime = undefined;
2026
2352
  }
@@ -2064,6 +2390,7 @@ async function tryRetryCodexProviderError(
2064
2390
  runtime.sawTerminalEvent = false;
2065
2391
  runtime.nativeOutputItems.length = 0;
2066
2392
  runtime.finalizedToolCallIds.clear();
2393
+ runtime.toolArgumentCorrelationFailed = false;
2067
2394
  resetOutputState(context.output);
2068
2395
  context.firstTokenTime = undefined;
2069
2396
  await scheduler.wait(CODEX_RETRY_DELAY_MS * runtime.providerRetryAttempt, {
@@ -2127,7 +2454,18 @@ async function handleCodexStreamFailure(
2127
2454
  }
2128
2455
  output.stopReason = context.options?.signal?.aborted ? "aborted" : "error";
2129
2456
  output.errorStatus = extractHttpStatusFromError(error);
2130
- output.transportFailure = transportFailureFacts(error);
2457
+ const transportFailure = transportFailureFacts(error);
2458
+ const typedProviderCode =
2459
+ error instanceof CodexProviderStreamError && CODEX_TYPED_TRANSPORT_PROVIDER_CODES.has(error.code ?? "")
2460
+ ? error.code
2461
+ : undefined;
2462
+ output.transportFailure = typedProviderCode
2463
+ ? {
2464
+ ...(transportFailure ?? { kind: "transport" as const }),
2465
+ providerCode: typedProviderCode,
2466
+ ...(error instanceof CodexProviderStreamError && error.deterministicVeto ? { retryMaxAttempts: 1 } : {}),
2467
+ }
2468
+ : transportFailure;
2131
2469
  output.errorMessage = await finalizeErrorMessage(error, context.requestContext.rawRequestDump);
2132
2470
  output.duration = Date.now() - context.startTime;
2133
2471
  if (context.firstTokenTime) {
@@ -2245,11 +2583,14 @@ export async function prewarmOpenAICodexResponses(
2245
2583
  const providerSessionState = getCodexProviderSessionState(options?.providerSessionState);
2246
2584
  const sessionKey = getCodexWebSocketSessionKey(promptCacheKey, model, accountId, baseUrl);
2247
2585
  const publicSessionKey = getCodexPublicSessionKey(promptCacheKey, model, baseUrl);
2248
- if (publicSessionKey && sessionKey) {
2249
- providerSessionState?.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
2250
- }
2251
2586
  if (!sessionKey || !providerSessionState) return;
2252
- const state = getCodexWebSocketSessionState(sessionKey, providerSessionState);
2587
+ const effectiveSessionKey = resolveCodexWebSocketSessionKey(sessionKey, publicSessionKey, providerSessionState);
2588
+ logCodexDebug("prewarm session keys", {
2589
+ publicSessionKey,
2590
+ effectiveSessionKey,
2591
+ providerSessionStateMapIdentity: getProviderSessionStateIdentity(options?.providerSessionState),
2592
+ });
2593
+ const state = getCodexWebSocketSessionState(effectiveSessionKey, providerSessionState);
2253
2594
  if (!shouldUseCodexWebSocket(model, state, options?.preferWebsockets)) return;
2254
2595
  const headers = logger.time(
2255
2596
  "prewarmCodex:createHeaders",
@@ -2261,7 +2602,7 @@ export async function prewarmOpenAICodexResponses(
2261
2602
  "websocket",
2262
2603
  state,
2263
2604
  );
2264
- await logger.time(
2605
+ const connectionPromise = logger.time(
2265
2606
  "prewarmCodex:establishWs",
2266
2607
  getOrCreateCodexWebSocketConnection,
2267
2608
  state,
@@ -2269,7 +2610,35 @@ export async function prewarmOpenAICodexResponses(
2269
2610
  headers,
2270
2611
  options?.signal,
2271
2612
  );
2272
- state.prewarmed = true;
2613
+ const attemptedConnection = state.connection;
2614
+ try {
2615
+ await connectionPromise;
2616
+ } catch (error) {
2617
+ const websocketError = error instanceof Error ? error : new Error(String(error));
2618
+ const superseded =
2619
+ attemptedConnection?.wasClosedLocally() === true ||
2620
+ (attemptedConnection !== undefined && state.connection !== attemptedConnection);
2621
+ const fatalPrewarmFailure =
2622
+ isCodexWebSocketFatalError(websocketError) &&
2623
+ !websocketError.message.toLowerCase().includes("connection timeout") &&
2624
+ !/^.*websocket closed before open \((?:1000|1001|1005)\)$/i.test(websocketError.message) &&
2625
+ !superseded &&
2626
+ state.connection === attemptedConnection;
2627
+ if (fatalPrewarmFailure) {
2628
+ recordCodexWebSocketFailure(state, true);
2629
+ if (publicSessionKey) providerSessionState.disabledWebSocketPublicSessions.add(publicSessionKey);
2630
+ if (publicSessionKey) disabledCodexPublicSessionKeys.add(publicSessionKey);
2631
+ } else if (superseded) {
2632
+ logCodexDebug("ignoring superseded Codex websocket prewarm failure", {
2633
+ error: websocketError.message,
2634
+ publicSessionKey,
2635
+ });
2636
+ }
2637
+ throw error;
2638
+ }
2639
+ if (state.connection === attemptedConnection) {
2640
+ state.prewarmed = true;
2641
+ }
2273
2642
  }
2274
2643
 
2275
2644
  function getCodexWebSocketSessionKey(
@@ -2283,6 +2652,17 @@ function getCodexWebSocketSessionKey(
2283
2652
  return `${accountId ?? "opaque"}:${baseUrl}:${model.id}:${promptCacheKey}`;
2284
2653
  }
2285
2654
 
2655
+ function resolveCodexWebSocketSessionKey(
2656
+ sessionKey: string,
2657
+ publicSessionKey: string | undefined,
2658
+ providerSessionState: CodexProviderSessionState,
2659
+ ): string {
2660
+ if (publicSessionKey) {
2661
+ providerSessionState.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
2662
+ }
2663
+ return sessionKey;
2664
+ }
2665
+
2286
2666
  function getCodexPublicSessionKey(
2287
2667
  sessionId: string | undefined,
2288
2668
  model: Model<"openai-codex-responses">,
@@ -2548,6 +2928,7 @@ class CodexWebSocketConnection {
2548
2928
  #waiters: Array<() => void> = [];
2549
2929
  #connectPromise?: Promise<void>;
2550
2930
  #activeRequest = false;
2931
+ #localCloseReason?: string;
2551
2932
 
2552
2933
  constructor(url: string, headers: Record<string, string>, options: CodexWebSocketConnectionOptions) {
2553
2934
  this.#url = url;
@@ -2560,11 +2941,16 @@ class CodexWebSocketConnection {
2560
2941
  return this.#socket?.readyState === WebSocket.OPEN;
2561
2942
  }
2562
2943
 
2944
+ wasClosedLocally(): boolean {
2945
+ return this.#localCloseReason !== undefined;
2946
+ }
2947
+
2563
2948
  matchesAuth(headers: Record<string, string>): boolean {
2564
2949
  return this.#headers.authorization === headers.authorization;
2565
2950
  }
2566
2951
 
2567
2952
  close(reason = "done"): void {
2953
+ this.#localCloseReason = reason;
2568
2954
  if (
2569
2955
  this.#socket &&
2570
2956
  (this.#socket.readyState === WebSocket.OPEN || this.#socket.readyState === WebSocket.CONNECTING)
@@ -2790,14 +3176,17 @@ class CodexWebSocketConnection {
2790
3176
  let timedOut = false;
2791
3177
  let timeout: NodeJS.Timeout | undefined;
2792
3178
  if (timeoutMs !== undefined && timeoutMs > 0) {
2793
- timeout = setTimeout(() => {
2794
- timedOut = true;
2795
- const waiterIndex = this.#waiters.indexOf(resolve);
2796
- if (waiterIndex >= 0) {
2797
- this.#waiters.splice(waiterIndex, 1);
2798
- }
2799
- resolve();
2800
- }, timeoutMs);
3179
+ timeout = setTimeout(
3180
+ () => {
3181
+ timedOut = true;
3182
+ const waiterIndex = this.#waiters.indexOf(resolve);
3183
+ if (waiterIndex >= 0) {
3184
+ this.#waiters.splice(waiterIndex, 1);
3185
+ }
3186
+ resolve();
3187
+ },
3188
+ Math.min(timeoutMs, CODEX_MAX_TIMER_DELAY_MS),
3189
+ );
2801
3190
  }
2802
3191
  await promise;
2803
3192
  if (timeout) clearTimeout(timeout);
@@ -3236,6 +3625,7 @@ function getCodexEventErrorMessage(rawEvent: Record<string, unknown>): string {
3236
3625
 
3237
3626
  class CodexProviderStreamError extends Error {
3238
3627
  readonly retryable: boolean;
3628
+ readonly deterministicVeto: boolean;
3239
3629
  readonly code?: string;
3240
3630
  readonly credentialModelUnavailable: boolean;
3241
3631
  /**
@@ -3249,6 +3639,7 @@ class CodexProviderStreamError extends Error {
3249
3639
  constructor(
3250
3640
  message: string,
3251
3641
  retryable: boolean,
3642
+ deterministicVeto: boolean,
3252
3643
  code: string | undefined,
3253
3644
  providerMessage: string,
3254
3645
  credentialModelUnavailable: boolean,
@@ -3256,6 +3647,7 @@ class CodexProviderStreamError extends Error {
3256
3647
  super(message);
3257
3648
  this.name = "CodexProviderStreamError";
3258
3649
  this.retryable = retryable;
3650
+ this.deterministicVeto = deterministicVeto;
3259
3651
  this.code = code;
3260
3652
  this.providerMessage = providerMessage;
3261
3653
  this.credentialModelUnavailable = credentialModelUnavailable;
@@ -3265,10 +3657,7 @@ class CodexProviderStreamError extends Error {
3265
3657
  function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolean {
3266
3658
  const code = getCodexEventErrorCode(rawEvent).toLowerCase();
3267
3659
  const message = getCodexEventErrorMessage(rawEvent);
3268
- if (
3269
- (code && CODEX_NON_RETRYABLE_EVENT_CODES.has(code)) ||
3270
- (!!message && CODEX_NON_RETRYABLE_EVENT_MESSAGE.test(message))
3271
- ) {
3660
+ if (isCodexDeterministicVeto(code, message)) {
3272
3661
  return false;
3273
3662
  }
3274
3663
  if (code && CODEX_RETRYABLE_EVENT_CODES.has(code)) {
@@ -3277,9 +3666,17 @@ function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolea
3277
3666
  return !!message && CODEX_RETRYABLE_EVENT_MESSAGE.test(message);
3278
3667
  }
3279
3668
 
3669
+ function isCodexDeterministicVeto(code: string, message: string): boolean {
3670
+ return (
3671
+ (code && CODEX_NON_RETRYABLE_EVENT_CODES.has(code)) ||
3672
+ (!!message && CODEX_NON_RETRYABLE_EVENT_MESSAGE.test(message))
3673
+ );
3674
+ }
3675
+
3280
3676
  function createCodexProviderStreamError(rawEvent: Record<string, unknown>): CodexProviderStreamError {
3281
3677
  const code = getCodexEventErrorCode(rawEvent);
3282
3678
  const message = getCodexEventErrorMessage(rawEvent);
3679
+ const deterministicVeto = isCodexDeterministicVeto(code.toLowerCase(), message);
3283
3680
  const formattedMessage =
3284
3681
  typeof rawEvent.type === "string" && rawEvent.type === "error"
3285
3682
  ? formatCodexErrorEvent(rawEvent, code, message)
@@ -3287,6 +3684,7 @@ function createCodexProviderStreamError(rawEvent: Record<string, unknown>): Code
3287
3684
  return new CodexProviderStreamError(
3288
3685
  formattedMessage,
3289
3686
  isRetryableCodexFailureEvent(rawEvent),
3687
+ deterministicVeto,
3290
3688
  code || undefined,
3291
3689
  message,
3292
3690
  isCodexAccountModelUnavailable(message, code),