@bitkyc08/opencodex 2.45.0 → 2.46.0-preview.20260907

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 (52) hide show
  1. package/gui/dist/assets/{index-J96sug5C.css → index-BFgUC17B.css} +1 -1
  2. package/gui/dist/assets/{index-CCfD72yq.js → index-NcAVXkST.js} +19 -19
  3. package/gui/dist/index.html +2 -2
  4. package/gui/dist/provider-icons/raycast.svg +3 -0
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +8 -3
  7. package/src/adapters/openai-responses.ts +7 -0
  8. package/src/bridge.ts +27 -7
  9. package/src/claude/inbound.ts +23 -5
  10. package/src/claude/outbound.ts +87 -19
  11. package/src/cli/capabilities.ts +4 -1
  12. package/src/cli/dispatch.ts +1 -1
  13. package/src/cli/doctor.ts +2 -2
  14. package/src/cli/export-command.ts +22 -10
  15. package/src/cli/help.ts +1 -1
  16. package/src/cli/index.ts +34 -7
  17. package/src/cli/integrations.ts +34 -1
  18. package/src/cli/provider.ts +27 -15
  19. package/src/cli/registry.ts +2 -2
  20. package/src/cli/version-skew.ts +36 -3
  21. package/src/clients/aside-profiles.ts +8 -7
  22. package/src/clients/config-export/contracts.ts +2 -1
  23. package/src/clients/config-export/raycast.ts +106 -0
  24. package/src/clients/config-export.ts +36 -0
  25. package/src/clients/model-presentation.ts +61 -0
  26. package/src/codex/catalog/sync.ts +44 -2
  27. package/src/codex/convergence.ts +7 -0
  28. package/src/generated/compatibility-version.json +59 -43
  29. package/src/generated/model-metadata.ts +1 -0
  30. package/src/images/loop.ts +10 -2
  31. package/src/integrations/catalog-refresh.ts +1 -1
  32. package/src/integrations/merge.ts +158 -25
  33. package/src/integrations/raycast-detect.ts +111 -0
  34. package/src/integrations/registry.ts +18 -0
  35. package/src/integrations/state.ts +82 -13
  36. package/src/integrations/writer.ts +46 -31
  37. package/src/lib/bounded-body.ts +22 -7
  38. package/src/oauth/anthropic-routing.ts +59 -14
  39. package/src/oauth/health.ts +3 -0
  40. package/src/providers/quota.ts +145 -9
  41. package/src/providers/registry.ts +31 -0
  42. package/src/responses/parser.ts +16 -3
  43. package/src/responses/reasoning-envelope.ts +3 -2
  44. package/src/server/grok-responses-control-frame.ts +43 -0
  45. package/src/server/management/config-routes.ts +6 -2
  46. package/src/server/management/integration-routes.ts +29 -2
  47. package/src/server/management/model-routes.ts +9 -1
  48. package/src/server/request-decompress.ts +34 -8
  49. package/src/server/responses/agent-task-recovery-cache.ts +43 -14
  50. package/src/server/responses/agent-task-recovery.ts +28 -16
  51. package/src/server/responses/core.ts +39 -6
  52. package/src/web-search/loop.ts +10 -2
@@ -2,6 +2,20 @@ const MAX_CACHE_BYTES = 8 * 1024 * 1024;
2
2
  const MAX_CONCURRENT_RECOVERIES = 32;
3
3
  const CACHE_TTL_MS = 15 * 60 * 1000;
4
4
 
5
+ export type AgentTaskRecoveryResolutionFailureReason =
6
+ | "recovery_unavailable"
7
+ | "caller_cancelled"
8
+ | "recovery_http_rejected"
9
+ | "recovery_timeout"
10
+ | "recovery_aborted"
11
+ | "recovery_transport_error"
12
+ | "recovery_invalid_output";
13
+
14
+ /** Shared flights carry bounded failures; only successful plaintext enters the cache. */
15
+ export type AgentTaskRecoveryResolution =
16
+ | { readonly recovered: true; readonly assignment: string }
17
+ | { readonly recovered: false; readonly reason: AgentTaskRecoveryResolutionFailureReason };
18
+
5
19
  interface RecoveryCacheEntry {
6
20
  assignment: string;
7
21
  bytes: number;
@@ -11,7 +25,7 @@ interface RecoveryCacheEntry {
11
25
 
12
26
  interface RecoveryFlight {
13
27
  controller: AbortController;
14
- promise: Promise<string | null>;
28
+ promise: Promise<AgentTaskRecoveryResolution>;
15
29
  waiters: number;
16
30
  settled: boolean;
17
31
  }
@@ -63,7 +77,7 @@ function insertRecoveryCacheEntry(key: string, assignment: string, maxEntries: n
63
77
  function startRecoveryFlight(
64
78
  key: string,
65
79
  maxEntries: number,
66
- request: (signal: AbortSignal) => Promise<string | null>,
80
+ request: (signal: AbortSignal) => Promise<AgentTaskRecoveryResolution>,
67
81
  ): RecoveryFlight | null {
68
82
  const active = RECOVERY_FLIGHTS.get(key);
69
83
  if (active) return active;
@@ -72,15 +86,15 @@ function startRecoveryFlight(
72
86
  const controller = new AbortController();
73
87
  const flight: RecoveryFlight = {
74
88
  controller,
75
- promise: Promise.resolve(null),
89
+ promise: Promise.resolve({ recovered: false, reason: "recovery_unavailable" }),
76
90
  waiters: 0,
77
91
  settled: false,
78
92
  };
79
93
  flight.promise = request(controller.signal)
80
- .then((assignment) => {
81
- if (!assignment || controller.signal.aborted) return null;
82
- insertRecoveryCacheEntry(key, assignment, maxEntries);
83
- return assignment;
94
+ .then((result): AgentTaskRecoveryResolution => {
95
+ if (controller.signal.aborted) return { recovered: false, reason: "recovery_aborted" };
96
+ if (result.recovered) insertRecoveryCacheEntry(key, result.assignment, maxEntries);
97
+ return result;
84
98
  })
85
99
  .finally(() => {
86
100
  flight.settled = true;
@@ -93,14 +107,14 @@ function startRecoveryFlight(
93
107
  async function waitForRecoveryFlight(
94
108
  flight: RecoveryFlight,
95
109
  abortSignal?: AbortSignal,
96
- ): Promise<string | null> {
97
- if (abortSignal?.aborted) return null;
110
+ ): Promise<AgentTaskRecoveryResolution> {
111
+ if (abortSignal?.aborted) return { recovered: false, reason: "caller_cancelled" };
98
112
  flight.waiters += 1;
99
113
  let onAbort: (() => void) | undefined;
100
114
  try {
101
115
  if (!abortSignal) return await flight.promise;
102
- const cancelled = new Promise<null>((resolve) => {
103
- onAbort = () => resolve(null);
116
+ const cancelled = new Promise<AgentTaskRecoveryResolution>((resolve) => {
117
+ onAbort = () => resolve({ recovered: false, reason: "caller_cancelled" });
104
118
  abortSignal.addEventListener("abort", onAbort, { once: true });
105
119
  if (abortSignal.aborted) onAbort();
106
120
  });
@@ -120,12 +134,27 @@ export async function resolveCachedAgentTaskRecovery(
120
134
  request: (signal: AbortSignal) => Promise<string | null>,
121
135
  abortSignal?: AbortSignal,
122
136
  ): Promise<string | null> {
123
- if (abortSignal?.aborted) return null;
137
+ const result = await resolveCachedAgentTaskRecoveryWithResult(key, maxEntries, async signal => {
138
+ const assignment = await request(signal);
139
+ return assignment
140
+ ? { recovered: true, assignment }
141
+ : { recovered: false, reason: "recovery_unavailable" };
142
+ }, abortSignal);
143
+ return result.recovered ? result.assignment : null;
144
+ }
145
+
146
+ export async function resolveCachedAgentTaskRecoveryWithResult(
147
+ key: string,
148
+ maxEntries: number,
149
+ request: (signal: AbortSignal) => Promise<AgentTaskRecoveryResolution>,
150
+ abortSignal?: AbortSignal,
151
+ ): Promise<AgentTaskRecoveryResolution> {
152
+ if (abortSignal?.aborted) return { recovered: false, reason: "caller_cancelled" };
124
153
  sweepRecoveryCache(Date.now(), maxEntries);
125
154
  const cached = RECOVERY_CACHE.get(key)?.assignment;
126
- if (cached) return cached;
155
+ if (cached) return { recovered: true, assignment: cached };
127
156
  const flight = startRecoveryFlight(key, maxEntries, request);
128
- return flight ? waitForRecoveryFlight(flight, abortSignal) : null;
157
+ return flight ? waitForRecoveryFlight(flight, abortSignal) : { recovered: false, reason: "recovery_unavailable" };
129
158
  }
130
159
 
131
160
  export function discardCachedAgentTaskRecovery(key: string): void {
@@ -1,14 +1,16 @@
1
1
  import { createHash, createHmac, randomBytes } from "node:crypto";
2
2
  import { decodeJwtPayload, extractAccountId } from "../../oauth/chatgpt";
3
3
  import type { OcxConfig } from "../../types";
4
- import { readBoundedResponseBody } from "../../lib/bounded-body";
4
+ import { boundedBodyDecodeFailure, readBoundedResponseBody } from "../../lib/bounded-body";
5
5
  import { isApiAuthRequired, isProxyAdmissionSecret } from "../auth-cors";
6
6
  import { structurallyValidFernetTokens } from "./encrypted-payload";
7
7
  import {
8
8
  cachedAgentTaskRecovery,
9
9
  discardCachedAgentTaskRecovery,
10
10
  resetAgentTaskRecoveryCache,
11
- resolveCachedAgentTaskRecovery,
11
+ resolveCachedAgentTaskRecoveryWithResult,
12
+ type AgentTaskRecoveryResolution,
13
+ type AgentTaskRecoveryResolutionFailureReason,
12
14
  } from "./agent-task-recovery-cache";
13
15
 
14
16
  /** Experimental opt-in normalization through ChatGPT's fixed Codex endpoint. */
@@ -44,9 +46,8 @@ export interface AgentTaskRecoveryOptions {
44
46
  export type AgentTaskRecoveryFailureReason =
45
47
  | "unsupported_envelope"
46
48
  | "admission_denied"
47
- // Includes cache capacity rejection; does not imply an upstream request was attempted.
48
- | "recovery_unavailable"
49
- | "caller_cancelled"
49
+ // recovery_unavailable includes capacity rejection, which does not imply an upstream attempt.
50
+ | AgentTaskRecoveryResolutionFailureReason
50
51
  | "input_changed";
51
52
 
52
53
  export type AgentTaskRecoveryResult =
@@ -436,7 +437,7 @@ async function requestRecovery(
436
437
  envelope: AgentEnvelope,
437
438
  options: AgentTaskRecoveryOptions,
438
439
  abortSignal?: AbortSignal,
439
- ): Promise<string | null> {
440
+ ): Promise<AgentTaskRecoveryResolution> {
440
441
  const controller = new AbortController();
441
442
  const timeout = setTimeout(
442
443
  () => controller.abort(new DOMException("Agent task recovery timed out", "TimeoutError")),
@@ -454,8 +455,11 @@ async function requestRecovery(
454
455
  redirect: "error",
455
456
  });
456
457
  if (!response.ok) {
457
- try { await response.body?.cancel(); } catch { /* already closed */ }
458
- return null;
458
+ // A rejected or never-settling cancellation must not extend the recovery deadline.
459
+ try { void response.body?.cancel().catch(() => undefined); } catch { /* already closed */ }
460
+ if (abortSignal?.aborted) return { recovered: false, reason: "recovery_aborted" };
461
+ if (controller.signal.aborted) return { recovered: false, reason: "recovery_timeout" };
462
+ return { recovered: false, reason: "recovery_http_rejected" };
459
463
  }
460
464
  const body = await readBoundedResponseBody(response, {
461
465
  signal,
@@ -465,10 +469,18 @@ async function requestRecovery(
465
469
  inactivityTimeoutMs: options.timeoutMs ?? 45_000,
466
470
  firstByteTimeoutMs: options.timeoutMs ?? 45_000,
467
471
  });
468
- if (body.truncated || body.oversized || body.timedOut || !body.displaySafe) return null;
469
- return assignmentFromRecoverySse(body.text, envelope);
470
- } catch {
471
- return null;
472
+ if (abortSignal?.aborted) return { recovered: false, reason: "recovery_aborted" };
473
+ if (controller.signal.aborted || body.timedOut) return { recovered: false, reason: "recovery_timeout" };
474
+ if (body.truncated || body.oversized || !body.displaySafe) return { recovered: false, reason: "recovery_invalid_output" };
475
+ const assignment = assignmentFromRecoverySse(body.text, envelope);
476
+ return assignment === null
477
+ ? { recovered: false, reason: "recovery_invalid_output" }
478
+ : { recovered: true, assignment };
479
+ } catch (error) {
480
+ if (abortSignal?.aborted) return { recovered: false, reason: "recovery_aborted" };
481
+ const decodeFailure = boundedBodyDecodeFailure(error);
482
+ if (controller.signal.aborted || decodeFailure === "timeout") return { recovered: false, reason: "recovery_timeout" };
483
+ return { recovered: false, reason: decodeFailure === "invalid_utf8" ? "recovery_invalid_output" : "recovery_transport_error" };
472
484
  } finally {
473
485
  clearTimeout(timeout);
474
486
  }
@@ -497,23 +509,23 @@ export async function recoverEncryptedAgentTaskWithResult(
497
509
  const admitted = admittedRecovery(req, input, config, context.parentThreadId);
498
510
  if (!admitted.admitted) return { recovered: false, reason: admitted.reason };
499
511
  const { admission, cacheKey, envelope } = admitted.recovery;
500
- const assignment = await resolveCachedAgentTaskRecovery(
512
+ const result = await resolveCachedAgentTaskRecoveryWithResult(
501
513
  cacheKey,
502
514
  options.cacheEntries ?? 200,
503
515
  signal => requestRecovery(admission, envelope, options, signal),
504
516
  context.abortSignal,
505
517
  );
506
- if (!assignment) {
518
+ if (!result.recovered) {
507
519
  return {
508
520
  recovered: false,
509
- reason: context.abortSignal?.aborted ? "caller_cancelled" : "recovery_unavailable",
521
+ reason: context.abortSignal?.aborted ? "caller_cancelled" : result.reason,
510
522
  };
511
523
  }
512
524
  if (context.abortSignal?.aborted) {
513
525
  discardCachedAgentTaskRecovery(cacheKey);
514
526
  return { recovered: false, reason: "caller_cancelled" };
515
527
  }
516
- if (!injectAssignment(input, envelope, assignment)) {
528
+ if (!injectAssignment(input, envelope, result.assignment)) {
517
529
  discardCachedAgentTaskRecovery(cacheKey);
518
530
  return { recovered: false, reason: "input_changed" };
519
531
  }
@@ -230,7 +230,7 @@ import {
230
230
  } from "../../providers/request-pacing";
231
231
  import { slugsEquivalent } from "../../providers/slug-codec";
232
232
  import { isMuseSubscriptionUsagePayload, parseMuseSubscriptionUsage } from "../../providers/muse-subscription-usage";
233
- import { hasPassiveAccountQuota, recordPassiveAccountQuota } from "../../providers/quota";
233
+ import { hasPassiveAccountQuota, recordAnthropicAccountQuotaFromHeaders, recordPassiveAccountQuota } from "../../providers/quota";
234
234
  import { captureConfigGeneration } from "../../lib/state-store-sweeper";
235
235
  import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../providers/openai-virtual-models";
236
236
  import { isUsageDebugEnabled } from "../../usage/debug";
@@ -374,6 +374,7 @@ import {
374
374
  type UpstreamHostAdmissionLease,
375
375
  } from "../../codex/upstream-host-health";
376
376
  import { createGrokResponsesSparseTerminalBlockRewrite } from "../grok-responses-snapshot-repair";
377
+ import { createGrokResponsesControlFrameBlockRewrite } from "../grok-responses-control-frame";
377
378
  import {
378
379
  createResponsesSnapshotBlockRewrite,
379
380
  hasResponsesSnapshotRepair,
@@ -3946,7 +3947,27 @@ async function handleResponsesInner(
3946
3947
  for (let attempt = 0; attempt < 3; attempt++) {
3947
3948
  if (selectionIsCurrent(requestBindings.get(wireRequest))) {
3948
3949
  const fetchImpl = (route.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? execute;
3949
- return fetchImpl(destination, dispatchInit);
3950
+ const binding = requestBindings.get(wireRequest);
3951
+ const snapshot = route.providerName === "anthropic" && anthropicPoolAccountId && binding?.kind === "oauth"
3952
+ ? binding.snapshot : undefined;
3953
+ const writerGeneration = snapshot ? captureConfigGeneration() : 0;
3954
+ const sentHeaders = snapshot ? new Headers(dispatchInit.headers) : undefined;
3955
+ const ownsBearer = snapshot !== undefined
3956
+ && sentHeaders?.get("authorization") === `Bearer ${snapshot.accessToken}`
3957
+ && !sentHeaders?.has("x-api-key");
3958
+ const response = await fetchImpl(destination, dispatchInit);
3959
+ // Observe each physical response before retries replace it. The binding belongs to
3960
+ // this dispatch, so a manual switch cannot file A's headers against B. Header
3961
+ // overrides and credential replacement make ownership unprovable: skip those writes.
3962
+ if (ownsBearer && snapshot) {
3963
+ try {
3964
+ const current = getAccountCredentialWithStatus("anthropic", snapshot.accountId);
3965
+ if (current && !current.needsReauth && credentialGeneration(current.credential) === snapshot.generation) {
3966
+ recordAnthropicAccountQuotaFromHeaders(snapshot.accountId, response.headers, writerGeneration);
3967
+ }
3968
+ } catch { /* best-effort observation cannot fail the response */ }
3969
+ }
3970
+ return response;
3950
3971
  }
3951
3972
  const nextAdapter = await refreshDispatchAdapter(requestParsed);
3952
3973
  const rebuilt = await nextAdapter.buildRequest(requestParsed, {
@@ -5494,9 +5515,9 @@ async function handleResponsesInner(
5494
5515
  // Grok Build renders deltas live but reconstructs its durable assistant
5495
5516
  // turn from the completed response snapshot. Native Responses streams
5496
5517
  // may instead carry the complete items in output_item.done, so the
5497
- // explicit Grok compatibility marker enables strict terminal-only repair.
5518
+ // explicit Grok compatibility marker enables strict client compatibility rewrites.
5498
5519
  // The provider's broader snapshot/lifecycle repair remains opt-in.
5499
- const grokClientSnapshotRepairEnabled = logCtx.surface === "grok";
5520
+ const grokClientCompatibilityEnabled = logCtx.surface === "grok";
5500
5521
  const snapshotRepairEnabled = hasResponsesSnapshotRepair(route.provider.responsesSnapshotRepair);
5501
5522
  const githubCopilotRepairEnabled = route.providerName === "github-copilot";
5502
5523
  const responseModelRewrite = parsed._responseModelId !== undefined
@@ -5545,7 +5566,10 @@ async function handleResponsesInner(
5545
5566
  githubCopilotRepairEnabled
5546
5567
  ? createGithubCopilotResponsesBlockRewrite(translatorBudget)
5547
5568
  : undefined,
5548
- grokClientSnapshotRepairEnabled
5569
+ grokClientCompatibilityEnabled
5570
+ ? createGrokResponsesControlFrameBlockRewrite()
5571
+ : undefined,
5572
+ grokClientCompatibilityEnabled
5549
5573
  ? createGrokResponsesSparseTerminalBlockRewrite(translatorBudget)
5550
5574
  : undefined,
5551
5575
  snapshotRepairEnabled
@@ -5956,7 +5980,10 @@ async function handleResponsesInner(
5956
5980
  const imgPlan = !routedCompaction ? await planImageBridge(config, parsed, route.provider) : undefined;
5957
5981
  const vidPlan = !routedCompaction ? await planVideoBridge(config, parsed, route.provider) : undefined;
5958
5982
  const canRunWebSearch = !!wsPlan && !adapter.runTurn;
5959
- const rotateSidecarProviderOn429 = async (retryAfter: string | null): Promise<ProviderAdapter | null> => {
5983
+ const rotateSidecarProviderOn429 = async (
5984
+ retryAfter: string | null,
5985
+ responseHeaders?: Headers,
5986
+ ): Promise<ProviderAdapter | null> => {
5960
5987
  const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
5961
5988
  retryAfter,
5962
5989
  now: Date.now(),
@@ -6000,6 +6027,8 @@ async function handleResponsesInner(
6000
6027
  anthropicPoolAccountId,
6001
6028
  retryAfter,
6002
6029
  anthropicSessionKey,
6030
+ Date.now(),
6031
+ responseHeaders,
6003
6032
  );
6004
6033
  if (!nextAccountId) return null;
6005
6034
  try {
@@ -7032,6 +7061,8 @@ async function handleResponsesInner(
7032
7061
  anthropicPoolAccountId,
7033
7062
  upstreamResponse.headers.get("retry-after"),
7034
7063
  anthropicSessionKey,
7064
+ Date.now(),
7065
+ upstreamResponse.headers,
7035
7066
  );
7036
7067
  if (!nextAccountId) break;
7037
7068
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
@@ -7445,6 +7476,8 @@ async function handleResponsesInner(
7445
7476
  anthropicPoolAccountId,
7446
7477
  response.headers.get("retry-after"),
7447
7478
  anthropicSessionKey,
7479
+ Date.now(),
7480
+ response.headers,
7448
7481
  );
7449
7482
  if (nextAccountId) {
7450
7483
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
@@ -309,8 +309,16 @@ export interface WebSearchLoopDeps {
309
309
  * 429 failover hook: rotate the provider's active credential and return a rebuilt adapter,
310
310
  * or null when the pool is exhausted. Async hooks support OAuth refresh; existing synchronous
311
311
  * key-pool hooks remain valid.
312
+ *
313
+ * `responseHeaders` carries the whole refusal, not just Retry-After, because an Anthropic
314
+ * 429 states the window's reset epoch even when it omits Retry-After -- and a rotation that
315
+ * cannot see it cools the drained account for the short default instead of until the window
316
+ * actually reopens. Optional so existing callers keep compiling.
312
317
  */
313
- on429?: (retryAfterHeader: string | null) => ProviderAdapter | null | Promise<ProviderAdapter | null>;
318
+ on429?: (
319
+ retryAfterHeader: string | null,
320
+ responseHeaders?: Headers,
321
+ ) => ProviderAdapter | null | Promise<ProviderAdapter | null>;
314
322
  /** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
315
323
  retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
316
324
  /** Called only when the final bridged Responses stream reaches completed or incomplete. */
@@ -521,7 +529,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
521
529
  // 429 key-failover parity with the normal routed path: rotate pool keys until one responds
522
530
  // or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
523
531
  while (prepared.response.status === 429 && deps.on429) {
524
- const rotated = await deps.on429(prepared.response.headers.get("retry-after"));
532
+ const rotated = await deps.on429(prepared.response.headers.get("retry-after"), prepared.response.headers);
525
533
  if (!rotated) break;
526
534
  // Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
527
535
  // it, but proceed immediately to the rotated fetch under the SAME deadline signal.