@bitkyc08/opencodex 2.7.30 → 2.7.31

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.
@@ -196,36 +196,42 @@ const ALIBABA_TOKEN_PLAN_QWEN_MODELS = [
196
196
  ];
197
197
  const ALIBABA_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
198
198
  "qwen3.8-max-preview": ["text", "image"],
199
- "qwen3.7-max": ["text"],
199
+ "qwen3.7-max": ["text", "image"],
200
200
  "qwen3.7-plus": ["text", "image"],
201
201
  "qwen3.6-flash": ["text", "image"],
202
202
  "glm-5.2": ["text"],
203
203
  "deepseek-v4-pro": ["text"],
204
204
  };
205
205
 
206
- // 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore).
206
+ // 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore, hardened 260721).
207
207
  // Multi-vendor lineup distinct from Beijing — includes DeepSeek V4 flash, Kimi K2.7, MiniMax.
208
208
  // Evidence: https://www.alibabacloud.com/help/en/model-studio/token-plan-overview
209
+ // https://qwencloud.com/pricing/token-plan (qwen3.8 metadata)
209
210
  const ALIBABA_INTL_TOKEN_PLAN_MODELS = [
210
- "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
211
+ "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
211
212
  "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2",
212
- "kimi-k2.7-code",
213
- "glm-5.2",
213
+ "kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5",
214
+ "glm-5.2", "glm-5.1", "glm-5",
214
215
  "MiniMax-M2.5",
215
216
  ];
216
217
  const ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS = [
217
- "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
218
+ "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
218
219
  ];
219
220
  const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
220
- "qwen3.7-max": ["text"],
221
+ "qwen3.8-max-preview": ["text", "image"],
222
+ "qwen3.7-max": ["text", "image"],
221
223
  "qwen3.7-plus": ["text", "image"],
222
224
  "qwen3.6-plus": ["text", "image"],
223
225
  "qwen3.6-flash": ["text", "image"],
224
226
  "deepseek-v4-pro": ["text"],
225
227
  "deepseek-v4-flash": ["text"],
226
228
  "deepseek-v3.2": ["text"],
227
- "kimi-k2.7-code": ["text"],
229
+ "kimi-k2.7-code": ["text", "image"],
230
+ "kimi-k2.6": ["text", "image"],
231
+ "kimi-k2.5": ["text", "image"],
228
232
  "glm-5.2": ["text"],
233
+ "glm-5.1": ["text"],
234
+ "glm-5": ["text"],
229
235
  "MiniMax-M2.5": ["text"],
230
236
  };
231
237
 
@@ -632,9 +638,11 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
632
638
  // devlog/_plan/260710_provider_hardening/001_research_frontier.md.
633
639
  {
634
640
  id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
635
- dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.5-flash", "gemini-3.1-pro-preview"],
636
- modelContextWindows: { "gemini-3.5-flash": 1_000_000 },
641
+ dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.1-pro-preview"],
642
+ modelContextWindows: { "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000 },
643
+ modelInputModalities: { "gemini-3.6-flash": ["text", "image"] },
637
644
  modelReasoningEfforts: {
645
+ "gemini-3.6-flash": ["minimal", "low", "medium", "high"],
638
646
  "gemini-3.5-flash": ["minimal", "low", "medium", "high"],
639
647
  "gemini-3.1-pro-preview": ["low", "medium", "high"],
640
648
  },
@@ -643,7 +651,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
643
651
  // 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
644
652
  // evidence from ai.google.dev does not establish Vertex publisher availability.
645
653
  { id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
646
- { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.5-flash-low", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
654
+ { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.6-flash-medium", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
647
655
  { id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
648
656
  { id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
649
657
  { id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
@@ -762,6 +770,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
762
770
  liveModels: false,
763
771
  note: "Token Plan Personal Edition · China (Beijing)",
764
772
  modelInputModalities: ALIBABA_TOKEN_PLAN_INPUT_MODALITIES,
773
+ modelContextWindows: {
774
+ "qwen3.8-max-preview": 983_616, "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000,
775
+ "qwen3.6-flash": 1_000_000, "glm-5.2": 1_000_000, "deepseek-v4-pro": 1_000_000,
776
+ },
765
777
  modelReasoningEfforts: {
766
778
  ...Object.fromEntries(ALIBABA_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
767
779
  "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
@@ -769,7 +781,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
769
781
  },
770
782
  modelReasoningEffortMap: { "deepseek-v4-pro": DEEPSEEK_THINKING_REASONING_MAP },
771
783
  thinkingBudgetModels: ALIBABA_TOKEN_PLAN_QWEN_MODELS,
772
- preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "qwen3.8-max-preview"],
784
+ preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash"],
785
+ noVisionModels: ["glm-5.2", "deepseek-v4-pro"],
773
786
  },
774
787
  {
775
788
  id: "alibaba-token-plan-intl",
@@ -786,9 +799,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
786
799
  note: "Token Plan Team Edition · Singapore (ap-southeast-1)",
787
800
  metadataModelIdNormalize: "case-insensitive",
788
801
  modelInputModalities: ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES,
789
- modelContextWindows: { "deepseek-v4-pro": 1_000_000, "deepseek-v4-flash": 1_000_000, "glm-5.2": 1_000_000 },
802
+ modelContextWindows: {
803
+ "qwen3.8-max-preview": 983_616,
804
+ "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000, "qwen3.6-plus": 1_000_000, "qwen3.6-flash": 1_000_000,
805
+ "deepseek-v4-pro": 1_000_000, "deepseek-v4-flash": 1_000_000, "deepseek-v3.2": 131_072,
806
+ "kimi-k2.7-code": 262_144, "kimi-k2.6": 262_144, "kimi-k2.5": 262_144,
807
+ "glm-5.2": 1_000_000, "glm-5.1": 1_000_000, "glm-5": 1_000_000,
808
+ "MiniMax-M2.5": 204_800,
809
+ },
790
810
  modelReasoningEfforts: {
791
811
  ...Object.fromEntries(ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
812
+ "qwen3.8-max-preview": ["low", "high", "xhigh"],
792
813
  "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
793
814
  "deepseek-v4-pro": DEEPSEEK_THINKING_EFFORTS,
794
815
  "deepseek-v4-flash": DEEPSEEK_THINKING_EFFORTS,
@@ -798,9 +819,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
798
819
  "deepseek-v4-flash": DEEPSEEK_THINKING_REASONING_MAP,
799
820
  },
800
821
  thinkingBudgetModels: ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS,
801
- preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash", "qwen3.7-max"],
802
- noVisionModels: ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2", "kimi-k2.7-code", "glm-5.2", "MiniMax-M2.5", "qwen3.7-max"],
803
- noReasoningModels: ["kimi-k2.7-code", "deepseek-v3.2", "MiniMax-M2.5"],
822
+ preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash", "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash"],
823
+ noVisionModels: ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2", "glm-5.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
824
+ noReasoningModels: ["kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "deepseek-v3.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
825
+ modelDefaultReasoningEfforts: { "qwen3.8-max-preview": "xhigh" },
804
826
  },
805
827
  // NEEDS_HUMAN 2026-07-10: kept for config compatibility, but this is a dashboard URL,
806
828
  // no /models endpoint is documented, and tools are silently ignored upstream per docs.parallel.ai.
@@ -537,7 +537,7 @@ export function startServer(port?: number) {
537
537
  void (async () => {
538
538
  const start = Date.now();
539
539
  const requestId = nextRequestLogId(start);
540
- const logCtx = { model: "unknown", provider: "unknown" };
540
+ const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
541
541
  let logged = false;
542
542
  const finalizeLog = (
543
543
  status: number,
@@ -556,7 +556,7 @@ export function startServer(port?: number) {
556
556
  body: JSON.stringify({ ...payload, stream: true }),
557
557
  });
558
558
  try {
559
- let terminalRecorder: ((status: ResponsesTerminalStatus) => void) | undefined;
559
+ let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
560
560
  const response = await handleResponses(req, config, logCtx, {
561
561
  forceEmptyResponseId: true,
562
562
  abortSignal: turnAbort.signal,
@@ -570,7 +570,7 @@ export function startServer(port?: number) {
570
570
  await sendResponseToWebSocket(ws, response, isCurrent, {
571
571
  onSsePayload: payload => inspectResponseLogSsePayload(logCtx, payload),
572
572
  onTerminal: status => {
573
- terminalRecorder?.(status);
573
+ terminalRecorder?.(status, logCtx.terminalHttpStatus);
574
574
  finalizeLog(httpStatusForRequestLogTerminal(status, logCtx), {
575
575
  terminalStatus: status,
576
576
  closeReason: "terminal",
@@ -406,7 +406,7 @@ export function relaySseWithHeartbeat(
406
406
  */
407
407
  export function consumeForInspection(
408
408
  body: ReadableStream<Uint8Array>,
409
- onTerminal: (status: ResponsesTerminalStatus) => void,
409
+ onTerminal: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void,
410
410
  signal?: AbortSignal,
411
411
  onDone?: () => void,
412
412
  logCtx?: RequestLogContext,
@@ -451,14 +451,24 @@ export function consumeForInspection(
451
451
  reportFirstOutput(payload);
452
452
  if (payload) {
453
453
  const status = terminalStatusFromSsePayload(payload);
454
- if (status) { reported = true; onTerminal(status); }
454
+ if (status) {
455
+ reported = true;
456
+ if (logCtx) {
457
+ logCtx.transportPhase = "terminal_sse";
458
+ logCtx.terminalSource = "upstream";
459
+ }
460
+ onTerminal(status);
461
+ }
455
462
  if (onCompletedResponse) {
456
463
  const response = completedResponseFromSsePayload(payload);
457
464
  if (response) onCompletedResponse(response);
458
465
  }
459
466
  }
460
467
  }
461
- if (!reported && !cancelled) onTerminal("incomplete");
468
+ if (!reported && !cancelled) {
469
+ if (logCtx) logCtx.terminalSource = "synthetic";
470
+ onTerminal("incomplete");
471
+ }
462
472
  return;
463
473
  }
464
474
  buffer += decoder.decode(value, { stream: true });
@@ -472,7 +482,14 @@ export function consumeForInspection(
472
482
  if (!payload) continue;
473
483
  if (!reported) {
474
484
  const status = terminalStatusFromSsePayload(payload);
475
- if (status) { reported = true; onTerminal(status); }
485
+ if (status) {
486
+ reported = true;
487
+ if (logCtx) {
488
+ logCtx.transportPhase = "terminal_sse";
489
+ logCtx.terminalSource = "upstream";
490
+ }
491
+ onTerminal(status);
492
+ }
476
493
  }
477
494
  if (onCompletedResponse) {
478
495
  const response = completedResponseFromSsePayload(payload);
@@ -481,7 +498,16 @@ export function consumeForInspection(
481
498
  }
482
499
  }
483
500
  } catch {
484
- if (!reported && !cancelled) onTerminal("incomplete");
501
+ // Upstream read failure after HTTP 200 (mid-stream socket reset) is not a
502
+ // protocol `response.incomplete` terminal. Report a synthetic 502 so account
503
+ // health treats it as transient; abort-driven client cancellation still wins.
504
+ if (!reported && !cancelled) {
505
+ if (logCtx) {
506
+ logCtx.transportPhase = "mid_stream";
507
+ logCtx.terminalSource = "synthetic";
508
+ }
509
+ onTerminal("failed", 502);
510
+ }
485
511
  } finally {
486
512
  onDone?.();
487
513
  }
@@ -62,6 +62,9 @@ export interface RequestLogContext {
62
62
  upstreamError?: string;
63
63
  /** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
64
64
  terminalHttpStatus?: number;
65
+ affinity?: "reused" | "new_bind" | "rebound" | "cleared";
66
+ transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
67
+ terminalSource?: "upstream" | "synthetic";
65
68
  }
66
69
 
67
70
  export interface RequestLogEntry {
@@ -92,6 +95,12 @@ export interface RequestLogEntry {
92
95
  usage?: OcxUsage;
93
96
  totalTokens?: number;
94
97
  attempts?: PersistedUsageAttempt[];
98
+ /** Codex pool affinity decision for this request (diagnostics for #186). */
99
+ affinity?: "reused" | "new_bind" | "rebound" | "cleared";
100
+ /** Where the upstream terminal/failure was observed. */
101
+ transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
102
+ /** Whether the terminal came from a real upstream SSE event or a proxy synthetic tail. */
103
+ terminalSource?: "upstream" | "synthetic";
95
104
  }
96
105
 
97
106
  const requestLog: RequestLogEntry[] = [];
@@ -587,6 +596,9 @@ export function addFinalRequestLog(
587
596
  ...(loggedUsage ? { usage: loggedUsage } : {}),
588
597
  ...(totalTokens !== undefined ? { totalTokens } : {}),
589
598
  ...(attempts?.length ? { attempts } : {}),
599
+ ...(logCtx.affinity ? { affinity: logCtx.affinity } : {}),
600
+ ...(logCtx.transportPhase ? { transportPhase: logCtx.transportPhase } : {}),
601
+ ...(logCtx.terminalSource ? { terminalSource: logCtx.terminalSource } : {}),
590
602
  });
591
603
  if (isUsageDebugEnabled()) {
592
604
  appendUsageDebug({
@@ -55,7 +55,7 @@ import {
55
55
  recordCodexUpstreamOutcome,
56
56
  type CodexUpstreamOutcome,
57
57
  } from "../codex/routing";
58
- import { fetchWithResetRetry, fetchWithTransientRetry } from "../lib/upstream-retry";
58
+ import { fetchWithResetRetry, fetchWithTransientRetry, applyUpstreamRecoveryInit } from "../lib/upstream-retry";
59
59
  import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "./auth-cors";
60
60
  import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
61
61
  import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../providers/openai-virtual-models";
@@ -69,6 +69,7 @@ import type { WsData } from "./ws-bridge";
69
69
  import { registerTurn, trackStreamLifetime, unregisterTurn } from "./lifecycle";
70
70
  import { redactSecretString } from "../lib/redact";
71
71
  import { readBoundedResponseBody } from "../lib/bounded-body";
72
+ import { supportedLadderFor } from "./effort-policy";
72
73
  import {
73
74
  beginRequestAttempt,
74
75
  catalogModelSupportsServiceTier,
@@ -395,9 +396,13 @@ export function sanitizeEncryptedContentInPlace(input: unknown): number {
395
396
  return rewritten;
396
397
  }
397
398
 
398
- export function sidecarOutcomeRecorder(config: OcxConfig, authCtx: CodexAuthContext): ((outcome: CodexUpstreamOutcome) => void) | undefined {
399
+ export function sidecarOutcomeRecorder(
400
+ config: OcxConfig,
401
+ authCtx: CodexAuthContext,
402
+ threadId?: string | null,
403
+ ): ((outcome: CodexUpstreamOutcome) => void) | undefined {
399
404
  return authCtx.kind === "pool" || authCtx.kind === "main-pool"
400
- ? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome)
405
+ ? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, { threadId })
401
406
  : undefined;
402
407
  }
403
408
 
@@ -418,9 +423,32 @@ export function codexForwardTerminalOutcomeRecorder(
418
423
  config: OcxConfig,
419
424
  authCtx: CodexAuthContext,
420
425
  provider: OcxProviderConfig,
421
- ): ((status: ResponsesTerminalStatus) => void) | undefined {
426
+ logCtx?: RequestLogContext,
427
+ threadId?: string | null,
428
+ ): ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined {
422
429
  if (!usesCodexForwardPoolAuth(authCtx, provider)) return undefined;
423
- return status => recordCodexUpstreamOutcome(config, authCtx.accountId, status === "completed" ? 200 : 502);
430
+ return (status, httpStatusOverride) => {
431
+ if (status === "incomplete") {
432
+ // Normal limit/content-filter/stall terminal — the account served the
433
+ // request. Don't penalize account health; record success to clear any
434
+ // prior soft-avoid so a healthy account isn't stuck avoided.
435
+ recordCodexUpstreamOutcome(config, authCtx.accountId, 200, { threadId });
436
+ return;
437
+ }
438
+ // status === "completed" or "failed": use the semantic HTTP status derived
439
+ // from the terminal SSE error payload (httpStatusFromTerminalError in
440
+ // request-log inspection) instead of collapsing every non-completed terminal
441
+ // to 502. A 400 invalid_request_error must not soft-avoid the account or
442
+ // rebind threads — only genuine transport/5xx failures should trigger
443
+ // transient health recording.
444
+ // httpStatusOverride: the combo WS path inspects SSE payloads into the parent
445
+ // logCtx, but this recorder closes over the child logCtx. The caller passes
446
+ // the parent's terminalHttpStatus so the semantic status is not lost.
447
+ const outcome = status === "completed"
448
+ ? 200
449
+ : (httpStatusOverride ?? logCtx?.terminalHttpStatus ?? 502);
450
+ recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, { threadId });
451
+ };
424
452
  }
425
453
 
426
454
  /**
@@ -466,7 +494,7 @@ interface HandleResponsesOptions {
466
494
  onFirstOutput?: () => void;
467
495
  onCodexAuthContextResolved?: (context: CodexAuthContext | undefined) => void;
468
496
  recordTerminalOutcomes?: boolean;
469
- setTerminalOutcomeRecorder?: (recorder: ((status: ResponsesTerminalStatus) => void) | undefined) => void;
497
+ setTerminalOutcomeRecorder?: (recorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined) => void;
470
498
  onNativePassthroughTerminal?: (status: ResponsesTerminalStatus) => void;
471
499
  onNativePassthroughCancel?: () => void;
472
500
  /** Internal recursion guard; callers outside this module must not set it. */
@@ -595,10 +623,12 @@ async function handleComboResponses(
595
623
  model: pick.target.model,
596
624
  provider: pick.target.provider,
597
625
  };
626
+ const targetRoute = routeModel(config, `${pick.target.provider}/${pick.target.model}`);
598
627
  const childBody = concreteComboRequestBody(
599
628
  rawBody,
600
629
  pick.target,
601
630
  comboDefaultEffort(config, comboId),
631
+ supportedLadderFor({ provider: targetRoute.provider, modelId: targetRoute.modelId }),
602
632
  );
603
633
  const childHeaders = new Headers(req.headers);
604
634
  childHeaders.delete("content-length");
@@ -608,7 +638,7 @@ async function handleComboResponses(
608
638
  body: JSON.stringify(childBody),
609
639
  });
610
640
  let resolvedAuth: CodexAuthContext | undefined;
611
- let terminalRecorder: ((status: ResponsesTerminalStatus) => void) | undefined;
641
+ let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
612
642
  const started = Date.now();
613
643
  const attempt = beginRequestAttempt(
614
644
  (logCtx.attempts?.length ?? 0) + 1,
@@ -1092,11 +1122,11 @@ export async function handleResponses(
1092
1122
  upstreamResponse = await fetchWithTransientRetry(
1093
1123
  recovery => {
1094
1124
  noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery);
1095
- return fetchWithHeaderTimeout(request.url, {
1125
+ return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
1096
1126
  method: request.method,
1097
1127
  headers: request.headers,
1098
1128
  body: request.body,
1099
- }, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
1129
+ }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
1100
1130
  },
1101
1131
  { abortSignal: upstream.signal, label: safeHostLabel(request.url) },
1102
1132
  );
@@ -1104,7 +1134,11 @@ export async function handleResponses(
1104
1134
  upstream.abort();
1105
1135
  if (options.abortSignal?.aborted) return clientCancelledResponse();
1106
1136
  const outcome = err instanceof Error && err.name === "TimeoutError" ? "timeout" : "connect_error";
1107
- if (usesCodexForwardPoolAuth(authCtx, route.provider)) recordCodexUpstreamOutcome(config, authCtx.accountId, outcome);
1137
+ if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
1138
+ recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
1139
+ threadId: req.headers.get("x-codex-parent-thread-id"),
1140
+ });
1141
+ }
1108
1142
  const msg = outcome === "timeout"
1109
1143
  ? `Provider connect timeout after ${connectMs}ms`
1110
1144
  : `Provider unreachable: ${err instanceof Error ? err.message : String(err)}`;
@@ -1122,7 +1156,13 @@ export async function handleResponses(
1122
1156
  const passthroughCt = headers.get("content-type")?.toLowerCase();
1123
1157
  const isEventStream = passthroughCt?.includes("text/event-stream")
1124
1158
  || (upstreamResponse.ok && !!upstreamResponse.body && !passthroughCt && parsed.stream);
1125
- const terminalRecorder = codexForwardTerminalOutcomeRecorder(config, authCtx, route.provider);
1159
+ const terminalRecorder = codexForwardTerminalOutcomeRecorder(
1160
+ config,
1161
+ authCtx,
1162
+ route.provider,
1163
+ logCtx,
1164
+ req.headers.get("x-codex-parent-thread-id"),
1165
+ );
1126
1166
  const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
1127
1167
  // Capture quota from upstream response for multi-account tracking
1128
1168
  if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
@@ -1148,14 +1188,15 @@ export async function handleResponses(
1148
1188
  );
1149
1189
  }
1150
1190
  if (terminalBodyWillRecord) {
1151
- options.setTerminalOutcomeRecorder?.(status => {
1152
- terminalRecorder(status);
1191
+ options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
1192
+ terminalRecorder(status, httpStatusOverride);
1153
1193
  options.onNativePassthroughTerminal?.(status);
1154
1194
  });
1155
1195
  } else {
1156
1196
  recordCodexUpstreamOutcome(config, authCtx.accountId, upstreamResponse.status, {
1157
1197
  retryAfter: retryAfterRaw,
1158
1198
  resetAt: [primaryResetRaw, secondaryResetRaw, monthlyResetRaw].filter(Boolean),
1199
+ threadId: req.headers.get("x-codex-parent-thread-id"),
1159
1200
  });
1160
1201
  }
1161
1202
  }
@@ -1174,8 +1215,8 @@ export async function handleResponses(
1174
1215
  // even if the client has already disconnected: the turn genuinely reached that terminal, so
1175
1216
  // it must log as completed/failed, not be dropped or downgraded to a cancel (#44). A pure
1176
1217
  // client-cancel (no terminal seen) is finalized separately via consumeForInspection's onCancel.
1177
- const reportNativeTerminal = (status: ResponsesTerminalStatus) => {
1178
- terminalRecorder?.(status);
1218
+ const reportNativeTerminal = (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
1219
+ terminalRecorder?.(status, httpStatusOverride);
1179
1220
  options.onNativePassthroughTerminal?.(status);
1180
1221
  };
1181
1222
  consumeForInspection(
@@ -1390,9 +1431,11 @@ export async function handleResponses(
1390
1431
  upstreamResponse = await fetchWithResetRetry(
1391
1432
  recovery => {
1392
1433
  noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate, recovery);
1393
- return fetchWithHeaderTimeout(request.url, {
1394
- method: request.method, headers: request.headers, body: request.body,
1395
- }, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
1434
+ return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
1435
+ method: request.method,
1436
+ headers: request.headers,
1437
+ body: request.body,
1438
+ }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
1396
1439
  },
1397
1440
  { abortSignal: upstream.signal, label: safeHostLabel(request.url) },
1398
1441
  );
@@ -1710,10 +1753,11 @@ export async function handleResponsesCompact(
1710
1753
  // headers would run compaction on the wrong account (or 401) whenever a pool account is
1711
1754
  // active for this thread while normal turns succeed.
1712
1755
  let compactProvider = route.provider;
1756
+ let authCtx: CodexAuthContext = { kind: "main", accountId: null };
1713
1757
  const headers = new Headers({ "content-type": "application/json" });
1714
1758
  try {
1715
1759
  if (route.codexAccountMode) {
1716
- const authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode);
1760
+ authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode);
1717
1761
  const selected = headersForCodexAuthContext(req.headers, authCtx);
1718
1762
  compactProvider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
1719
1763
  for (const name of FORWARD_HEADERS) {
@@ -1744,19 +1788,59 @@ export async function handleResponsesCompact(
1744
1788
  const base = (compactProvider.baseUrl ?? "").replace(/\/$/, "");
1745
1789
  if (compactProvider.apiKey) headers.set("authorization", `Bearer ${resolveEnvValue(compactProvider.apiKey)}`);
1746
1790
  const { reasoning: _reasoning, ...compactBody } = raw as typeof raw & { reasoning?: unknown };
1791
+ const compactUrl = `${base}/responses/compact`;
1792
+ const compactThreadId = req.headers.get("x-codex-parent-thread-id");
1793
+ const connectMs = config.connectTimeoutMs ?? 200_000;
1794
+ const recordCompactPoolOutcome = (outcome: CodexUpstreamOutcome, meta: { retryAfter?: string | null } = {}) => {
1795
+ if (!usesCodexForwardPoolAuth(authCtx, route.provider)) return;
1796
+ recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
1797
+ ...meta,
1798
+ threadId: compactThreadId,
1799
+ });
1800
+ };
1747
1801
  let upstream: Response;
1748
1802
  try {
1749
- upstream = await fetch(`${base}/responses/compact`, {
1750
- method: "POST",
1751
- headers,
1752
- body: JSON.stringify({ ...compactBody, model: route.modelId }),
1753
- signal: req.signal,
1754
- });
1755
- } catch {
1803
+ // Same connect timeout + keep-alive reset + transient-5xx recovery as /v1/responses
1804
+ // compact hits the same ChatGPT host and must soft-avoid / clear affinity (#186).
1805
+ upstream = await fetchWithTransientRetry(
1806
+ recovery => fetchWithHeaderTimeout(
1807
+ compactUrl,
1808
+ applyUpstreamRecoveryInit({
1809
+ method: "POST",
1810
+ headers,
1811
+ body: JSON.stringify({ ...compactBody, model: route.modelId }),
1812
+ }, recovery),
1813
+ req.signal,
1814
+ connectMs,
1815
+ false,
1816
+ providerFetch(compactProvider),
1817
+ ),
1818
+ { abortSignal: req.signal, label: safeHostLabel(compactUrl) },
1819
+ );
1820
+ } catch (err) {
1756
1821
  if (req.signal.aborted) return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
1822
+ const outcome = err instanceof Error && err.name === "TimeoutError" ? "timeout" : "connect_error";
1823
+ recordCompactPoolOutcome(outcome);
1757
1824
  return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
1758
1825
  }
1759
- return bufferCompactResponse(upstream, req.signal);
1826
+ const retryAfter = upstream.headers.get("retry-after");
1827
+ const buffered = await bufferCompactResponse(upstream, req.signal);
1828
+ // Record pool health only after the body is fully delivered (or definitively failed).
1829
+ // A premature 200 would clear soft-avoid while the client still sees a buffer 502.
1830
+ if (buffered.status === 499) {
1831
+ return buffered;
1832
+ }
1833
+ if (upstream.ok && buffered.status >= 500) {
1834
+ // The upstream account returned 200 — it is healthy. The buffering failure
1835
+ // (oversized body exceeding COMPACT_RESPONSE_MAX_BYTES, or a rare mid-read
1836
+ // reset on a small JSON payload) is a local proxy issue, not account flakiness.
1837
+ // Record the upstream status so a deterministic payload-size limit does not
1838
+ // soft-avoid a healthy account and rotate a thread for 30s.
1839
+ recordCompactPoolOutcome(upstream.status, { retryAfter });
1840
+ } else {
1841
+ recordCompactPoolOutcome(upstream.status, { retryAfter });
1842
+ }
1843
+ return buffered;
1760
1844
  }
1761
1845
 
1762
1846
  // ROUTED model: run the v2 synthetic-compaction turn internally (appends COMPACT_PROMPT, no
package/src/service.ts CHANGED
@@ -503,7 +503,7 @@ function installWindows(): void {
503
503
  throw new Error(`Cannot remove the native service before switching to Task Scheduler: ${err instanceof Error ? err.message : String(err)}. Remove it manually with 'sc delete ${WINSW_SERVICE_ID}' or retry.`);
504
504
  }
505
505
  if (statusWinswRaw() !== "nonexistent") {
506
- throw new Error("Native service still present after removal attempt — aborting switch. Remove it manually with 'sc delete opencodex-proxy-native'.");
506
+ throw new Error(`Native service registration could not be re-verified after the removal attempt — aborting switch. Check 'sc.exe query ${WINSW_SERVICE_ID}' and remove it manually if present.`);
507
507
  }
508
508
  }
509
509
  // End a running task BEFORE rewriting the assets it is executing — cmd.exe reading the
@@ -761,7 +761,9 @@ export function stopServiceIfInstalled(): boolean {
761
761
  const q = schtasks(["/query", "/tn", TASK]);
762
762
  if (q.includes(TASK)) { stopWindows(); stopped = true; }
763
763
  } catch { /* task not found */ }
764
- if (statusWinswRaw() !== "nonexistent") { stopWinswService(); stopped = true; }
764
+ if (statusWinswRaw() !== "nonexistent") {
765
+ try { stopWinswService(); stopped = true; } catch { /* best-effort */ }
766
+ }
765
767
  if (stopped) return true;
766
768
  } else if (process.platform === "linux" && isSystemd() && existsSync(unitPath())) {
767
769
  try { stopSystemd(); return true; } catch { return false; }
@@ -793,7 +795,14 @@ export function uninstallServiceIfInstalled(): boolean {
793
795
  const q = schtasks(["/query", "/tn", TASK]);
794
796
  if (q.includes(TASK)) { uninstallWindows(); removed = true; }
795
797
  } catch { /* task not found */ }
796
- if (statusWinswRaw() !== "nonexistent") { uninstallWinswService(); removed = true; }
798
+ if (statusWinswRaw() !== "nonexistent") {
799
+ try {
800
+ uninstallWinswService();
801
+ removed = true;
802
+ } catch (err) {
803
+ console.warn(`⚠️ Failed to remove native service: ${err instanceof Error ? err.message : String(err)}. Check 'sc.exe query ${WINSW_SERVICE_ID}'.`);
804
+ }
805
+ }
797
806
  if (removed) { removeServiceInstallState(); return true; }
798
807
  } else if (process.platform === "linux" && existsSync(unitPath())) {
799
808
  try { uninstallSystemd(); removeServiceInstallState(); return true; } catch {
package/src/types.ts CHANGED
@@ -498,8 +498,8 @@ export interface OcxComboConfig {
498
498
  strategy?: OcxComboStrategy;
499
499
  /** Successful requests retained on one RR selection batch. Default 1; range 1..100. */
500
500
  stickyLimit?: number;
501
- /** Used when the client omits reasoning.effort. Default medium. */
502
- defaultEffort?: OcxComboDefaultEffort;
501
+ /** Used when the client omits reasoning.effort. null/omitted leaves the target default unchanged. */
502
+ defaultEffort?: OcxComboDefaultEffort | null;
503
503
  }
504
504
 
505
505
  /**
@@ -30,8 +30,7 @@ export interface ExpectedPriceOverlay {
30
30
  }
31
31
 
32
32
  const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
33
- const GEMINI_35_FLASH: Cost4 = { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 };
34
- const GEMINI_3_FLASH: Cost4 = { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 };
33
+ const GEMINI_36_FLASH: Cost4 = { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 };
35
34
  const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
36
35
  const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
37
36
  const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
@@ -39,7 +38,7 @@ const KIMI_K27_CODE_HIGHSPEED: Cost4 = { input: 1.9, output: 8, cacheRead: 0.38,
39
38
  const KIMI_K26: Cost4 = { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 };
40
39
  const KIMI_K25: Cost4 = { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 };
41
40
 
42
- const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-06-18); cacheWrite=0: storage is billed per-hour, not per-token";
41
+ const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
43
42
  const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
44
43
  const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-details-usd; V4 Flash alias transition scheduled 2026-07-24 — re-verify after";
45
44
  // Kimi official tables publish input/output/cache-hit only; cacheWrite is mapped to the
@@ -58,12 +57,16 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
58
57
  // base model's standard rate per the official Billing FAQ).
59
58
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
60
59
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-high", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
61
- { provider: "google-antigravity", modelId: "gemini-3.5-flash-extra-low", cost4: GEMINI_35_FLASH, source: `derived: gemini-3.5-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
62
- { provider: "google-antigravity", modelId: "gemini-3.5-flash-low", cost4: GEMINI_35_FLASH, source: `derived: gemini-3.5-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
63
- { provider: "google-antigravity", modelId: "gemini-3.5-flash-mid", cost4: GEMINI_35_FLASH, source: `derived: gemini-3.5-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
64
- { provider: "google-antigravity", modelId: "gemini-3.5-flash-high", cost4: GEMINI_35_FLASH, source: `derived: gemini-3.5-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
65
- { provider: "google-antigravity", modelId: "gemini-3-flash-agent", cost4: GEMINI_3_FLASH, source: `derived: gemini-3-flash + Agent billing principle ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
66
- // Google Vertex/Gemini API current model (verified published table).
60
+ { provider: "google-antigravity", modelId: "gemini-3.6-flash-low", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
61
+ { provider: "google-antigravity", modelId: "gemini-3.6-flash-medium", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
62
+ { provider: "google-antigravity", modelId: "gemini-3.6-flash-high", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
63
+ { provider: "google-antigravity", modelId: "gemini-3.5-flash-extra-low", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-low ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
64
+ { provider: "google-antigravity", modelId: "gemini-3.5-flash-low", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-medium ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
65
+ { provider: "google-antigravity", modelId: "gemini-3.5-flash-mid", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-medium ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
66
+ { provider: "google-antigravity", modelId: "gemini-3.5-flash-high", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-high ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
67
+ { provider: "google-antigravity", modelId: "gemini-3-flash-agent", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-high ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
68
+ // Direct Google Gemini API current model (verified — published table).
69
+ { provider: "google", modelId: "gemini-3.6-flash", cost4: GEMINI_36_FLASH, source: GEMINI_PRICING, verifiedAt: "2026-07-22", status: "verified" },
67
70
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
68
71
  // Antigravity-bundled third-party models — derived from the underlying vendor's
69
72
  // official API price (Antigravity itself bills via subscription quota).