@bitkyc08/opencodex 2.7.40 → 2.7.41

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 (110) hide show
  1. package/README.md +63 -12
  2. package/assets/claude-code-models.gif +0 -0
  3. package/gui/dist/assets/index-B2J4t3te.css +1 -0
  4. package/gui/dist/assets/index-BmvM6wRb.js +65 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -2
  7. package/src/adapters/google.ts +48 -9
  8. package/src/adapters/kiro-events.ts +15 -3
  9. package/src/adapters/kiro.ts +292 -28
  10. package/src/adapters/openai-chat.ts +63 -15
  11. package/src/adapters/openai-responses.ts +18 -0
  12. package/src/bridge.ts +76 -21
  13. package/src/chat/outbound.ts +66 -34
  14. package/src/claude/auth-detect.ts +229 -0
  15. package/src/claude/auth-mode-migration.ts +32 -0
  16. package/src/claude/auth-mode.ts +62 -0
  17. package/src/claude/desktop-3p-guard.ts +35 -0
  18. package/src/claude/desktop-3p.ts +121 -21
  19. package/src/claude/desktop-health.ts +26 -0
  20. package/src/claude/desktop-profile.ts +263 -0
  21. package/src/claude/inbound-debug.ts +4 -0
  22. package/src/claude/model-info.ts +9 -3
  23. package/src/cli/account-extended.ts +34 -0
  24. package/src/cli/account.ts +3 -1
  25. package/src/cli/claude-desktop.ts +152 -0
  26. package/src/cli/claude.ts +37 -3
  27. package/src/cli/doctor.ts +187 -6
  28. package/src/cli/help.ts +13 -1
  29. package/src/cli/index.ts +151 -57
  30. package/src/cli/status-oauth.ts +68 -0
  31. package/src/cli/status.ts +4 -0
  32. package/src/codex/account-lifecycle.ts +31 -0
  33. package/src/codex/auth-api.ts +133 -26
  34. package/src/codex/auth-collision.ts +55 -11
  35. package/src/codex/auth-context.ts +50 -6
  36. package/src/codex/catalog/provider-fetch.ts +6 -2
  37. package/src/codex/home.ts +61 -1
  38. package/src/codex/inject.ts +28 -66
  39. package/src/codex/injected-marker.ts +72 -0
  40. package/src/codex/journal.ts +39 -3
  41. package/src/codex/main-account-cache.ts +25 -0
  42. package/src/codex/model-cache.ts +20 -1
  43. package/src/codex/paths.ts +5 -0
  44. package/src/codex/routing.ts +138 -11
  45. package/src/codex/subagent-model-fallback.ts +455 -0
  46. package/src/codex/sync.ts +17 -0
  47. package/src/combos/failover.ts +10 -2
  48. package/src/combos/index.ts +1 -0
  49. package/src/combos/types.ts +9 -0
  50. package/src/config.ts +117 -0
  51. package/src/grok/inject.ts +339 -0
  52. package/src/grok/status.ts +88 -0
  53. package/src/grok/sync.ts +66 -0
  54. package/src/lib/destination-policy.ts +13 -0
  55. package/src/lib/errors.ts +59 -0
  56. package/src/lib/privacy.ts +9 -0
  57. package/src/lib/process-control.ts +57 -4
  58. package/src/oauth/health.ts +375 -0
  59. package/src/oauth/index.ts +69 -22
  60. package/src/oauth/kiro.ts +5 -3
  61. package/src/oauth/log.ts +48 -0
  62. package/src/oauth/store.ts +55 -6
  63. package/src/providers/alibaba-region-backup.ts +75 -0
  64. package/src/providers/alibaba-region-migration.ts +143 -0
  65. package/src/providers/alibaba-region-startup.ts +36 -0
  66. package/src/providers/api-keys.ts +5 -5
  67. package/src/providers/derive.ts +22 -1
  68. package/src/providers/free-directory.ts +181 -0
  69. package/src/providers/key-failover.ts +2 -2
  70. package/src/providers/kiro-models.ts +3 -2
  71. package/src/providers/openai-tiers.ts +1 -1
  72. package/src/providers/provider-id-rewrite.ts +150 -0
  73. package/src/providers/registry.ts +49 -2
  74. package/src/responses/parser.ts +49 -20
  75. package/src/responses/state.ts +156 -2
  76. package/src/router.ts +73 -5
  77. package/src/server/chat-completions.ts +78 -22
  78. package/src/server/claude-messages.ts +8 -0
  79. package/src/server/images.ts +2 -1
  80. package/src/server/index.ts +50 -30
  81. package/src/server/live.ts +2 -1
  82. package/src/server/management/agent-settings-routes.ts +337 -18
  83. package/src/server/management/api-access.ts +141 -0
  84. package/src/server/management/combo-routes.ts +3 -3
  85. package/src/server/management/config-routes.ts +4 -4
  86. package/src/server/management/logs-usage-routes.ts +64 -4
  87. package/src/server/management/model-routes.ts +114 -8
  88. package/src/server/management/oauth-account-routes.ts +31 -5
  89. package/src/server/management/provider-routes.ts +42 -11
  90. package/src/server/management/shared.ts +81 -2
  91. package/src/server/management/system-routes.ts +6 -1
  92. package/src/server/management-api.ts +24 -7
  93. package/src/server/port-reclaim.ts +261 -0
  94. package/src/server/request-log.ts +11 -5
  95. package/src/server/responses/collaboration.ts +11 -6
  96. package/src/server/responses/compact.ts +2 -1
  97. package/src/server/responses/core.ts +358 -155
  98. package/src/server/responses/passthrough-error.ts +53 -0
  99. package/src/server/search.ts +2 -1
  100. package/src/server/system-env.ts +23 -6
  101. package/src/server/windows-tcp-drop.ts +174 -0
  102. package/src/service.ts +43 -3
  103. package/src/types.ts +74 -6
  104. package/src/update/index.ts +30 -19
  105. package/src/update/job.ts +30 -15
  106. package/src/usage/log.ts +158 -2
  107. package/src/usage/summary.ts +8 -4
  108. package/src/web-search/loop.ts +4 -1
  109. package/gui/dist/assets/index-CMip1DzF.css +0 -1
  110. package/gui/dist/assets/index-cydcmbzC.js +0 -52
@@ -12,6 +12,7 @@ import type { Server, ServerWebSocket } from "bun";
12
12
  import {
13
13
  DEFAULT_SUBAGENT_MODELS,
14
14
  applyProxyEnv,
15
+ armClaudeCodeBaseline,
15
16
  loadConfig,
16
17
  saveConfig,
17
18
  websocketsEnabled,
@@ -20,10 +21,12 @@ import { reconcileOAuthProviders } from "../oauth";
20
21
  import { invalidateCodexModelsCache } from "../codex/catalog";
21
22
  import { startMemoryWatchdog } from "./memory-watchdog";
22
23
  import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
24
+ import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
23
25
  import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
24
26
  import { providerCodexAccountMode } from "../providers/registry";
25
27
  import {
26
28
  CodexAccountCooldownError,
29
+ cooldownErrorMessage,
27
30
  } from "../codex/auth-context";
28
31
  export {
29
32
  clearThreadAccountMap,
@@ -121,6 +124,7 @@ import { handleClaudeCountTokens, handleClaudeMessages } from "./claude-messages
121
124
  import { handleChatCompletions } from "./chat-completions";
122
125
  import { anthropicErrorResponse } from "../claude/outbound";
123
126
  import { buildDesktop3pRegistry } from "../claude/desktop-3p";
127
+ import { runClaudeAuthModeMigration } from "../claude/auth-mode-migration";
124
128
  import { handleImages } from "./images";
125
129
  import { handleLive, logLiveSidebandFrame, parseLiveSidebandTarget, resolveLiveSidebandUpgrade } from "./live";
126
130
  import { handleSearch } from "./search";
@@ -235,7 +239,7 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
235
239
  // export function relaySseWithHeartbeat
236
240
 
237
241
  export function startServer(port?: number) {
238
- const config = runOpenAiTierStartupMigration(loadConfig());
242
+ const config = runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()));
239
243
  applyProxyEnv(config);
240
244
  assertServerAuthConfig(config);
241
245
  // Refresh OAuth provider presets (models/noReasoningModels) from the registry so a proxy update
@@ -247,6 +251,11 @@ export function startServer(port?: number) {
247
251
  config.subagentModels = [...DEFAULT_SUBAGENT_MODELS];
248
252
  saveConfig(config);
249
253
  }
254
+ // authMode migration (devlog 260726_claude_auth_auto/015): before "auto" existed,
255
+ // choosing Subscription DELETED the key, so a pre-upgrade block with no authMode is
256
+ // indistinguishable from "never chose". Pin those to subscription once so an upgrade
257
+ // never silently moves a deliberate subscriber onto proxy.
258
+ if (runClaudeAuthModeMigration(config)) saveConfig(config);
250
259
  // Sidecar model migration (KST 2026-07-10 06:00 = UTC 2026-07-09 21:00): auto-migrate the old
251
260
  // gpt-5.4-mini default to gpt-5.6-luna for both search and vision sidecars. Only touches configs
252
261
  // still on the old default — explicit user choices are preserved.
@@ -266,6 +275,13 @@ export function startServer(port?: number) {
266
275
  }
267
276
  }
268
277
  invalidateCodexModelsCache();
278
+ // Arm the `claudeCode` hand-edit guard (devlog 260726_claude_auth_auto/040 H1) BEFORE
279
+ // the server can serve a request, and AFTER the startup migrations above — those run
280
+ // against a config nobody else holds and are the documented exception to the save
281
+ // boundary, so the baseline should reflect what they wrote. Arming is eager on
282
+ // purpose: a lazy "arm on first save" loses exactly the hand edit made before that
283
+ // first save, which is the case the guard exists for.
284
+ armClaudeCodeBaseline(config);
269
285
  // usage.jsonl already persists every request; rehydrate the in-memory Logs ring so
270
286
  // /api/logs (and the GUI) survive `ocx stop` / `ocx start` process restarts.
271
287
  hydrateRequestLogsFromDisk();
@@ -282,6 +298,18 @@ export function startServer(port?: number) {
282
298
  // (0.0.0.0/::) and specific hosts are left untouched so intentional exposure is preserved.
283
299
  const bindHost = /^localhost$/i.test(config.hostname ?? "") ? "127.0.0.1" : (config.hostname ?? "127.0.0.1");
284
300
 
301
+ // Codex treats empty / non-JSON 503 bodies as "Unknown error" (#452). Keep Retry-After and
302
+ // the server_is_overloaded code so clients can back off, but always return a JSON envelope.
303
+ function drainingResponse(req: Request): Response {
304
+ const response = formatErrorResponse(503, "server_error", "Service shutting down");
305
+ const headers = new Headers(response.headers);
306
+ for (const [name, value] of Object.entries(corsHeaders(req, config))) {
307
+ headers.set(name, value);
308
+ }
309
+ headers.set("Retry-After", "5");
310
+ return new Response(response.body, { status: 503, headers });
311
+ }
312
+
285
313
  const server: Server<WsData> = Bun.serve<WsData>({
286
314
  port: listenPort,
287
315
  hostname: bindHost,
@@ -301,7 +329,7 @@ export function startServer(port?: number) {
301
329
  // handshake-time only, so capture inbound headers and thread them into the pipeline.
302
330
  if (url.pathname === "/v1/responses" && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
303
331
  if (isDraining()) {
304
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
332
+ return drainingResponse(req);
305
333
  }
306
334
  const apiAuthError = requireResponsesApiAuth(req, config);
307
335
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -362,8 +390,15 @@ export function startServer(port?: number) {
362
390
  || url.searchParams.get("flavor") === "anthropic";
363
391
  if (wantsAnthropicList && !url.searchParams.has("client_version")) {
364
392
  if (config.claudeCode?.enabled === false) return jsonResponse({ data: [] }, 200, req, config);
393
+ // Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
394
+ buildDesktop3pRegistry(
395
+ [...visibleNativeSlugs(config)],
396
+ goOrdered.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
397
+ config.claudeCode?.desktopProfile,
398
+ );
365
399
  const { buildAnthropicModelInfos } = await import("../claude/model-info");
366
400
  const { resolveAutoContext } = await import("../claude/context-windows");
401
+ const { activeDesktop3pAlias } = await import("../claude/desktop-3p");
367
402
  // Per-surface id family (devlog 050): explicit ?ids= wins; otherwise the
368
403
  // Claude Code CLI discovery UA (`claude-code/<version>`, binary n_()) gets
369
404
  // readable claude-ocx ids and every other client (Desktop 3P) keeps the
@@ -374,12 +409,7 @@ export function startServer(port?: number) {
374
409
  : idsParam === "desktop"
375
410
  ? "desktop3p" as const
376
411
  : (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
377
- const data = buildAnthropicModelInfos([...visibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle);
378
- // Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
379
- buildDesktop3pRegistry(
380
- [...visibleNativeSlugs(config)],
381
- goOrdered.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
382
- );
412
+ const data = buildAnthropicModelInfos([...visibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias);
383
413
  return jsonResponse({ data }, 200, req, config);
384
414
  }
385
415
  if (url.searchParams.has("client_version")) {
@@ -406,7 +436,7 @@ export function startServer(port?: number) {
406
436
  // before the /v1/* 404 guard below.
407
437
  if (url.pathname === "/v1/responses/compact" && req.method === "POST") {
408
438
  if (isDraining()) {
409
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
439
+ return drainingResponse(req);
410
440
  }
411
441
  const apiAuthError = requireResponsesApiAuth(req, config);
412
442
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -438,10 +468,7 @@ export function startServer(port?: number) {
438
468
  ) {
439
469
  disableResponsesRequestTimeout(req, requestServer);
440
470
  if (isDraining()) {
441
- return new Response("Service shutting down", {
442
- status: 503,
443
- headers: { ...corsHeaders(req, config), "Retry-After": "5" },
444
- });
471
+ return drainingResponse(req);
445
472
  }
446
473
  const apiAuthError = requireApiAuth(req, config, "data-plane");
447
474
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -460,10 +487,7 @@ export function startServer(port?: number) {
460
487
  if (url.pathname === "/v1/alpha/search" && req.method === "POST") {
461
488
  disableResponsesRequestTimeout(req, requestServer);
462
489
  if (isDraining()) {
463
- return new Response("Service shutting down", {
464
- status: 503,
465
- headers: { ...corsHeaders(req, config), "Retry-After": "5" },
466
- });
490
+ return drainingResponse(req);
467
491
  }
468
492
  const apiAuthError = requireApiAuth(req, config, "data-plane");
469
493
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -487,7 +511,7 @@ export function startServer(port?: number) {
487
511
  if (url.pathname === "/v1/responses" && req.method === "POST") {
488
512
  disableResponsesRequestTimeout(req, requestServer);
489
513
  if (isDraining()) {
490
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
514
+ return drainingResponse(req);
491
515
  }
492
516
  const apiAuthError = requireResponsesApiAuth(req, config);
493
517
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -526,7 +550,7 @@ export function startServer(port?: number) {
526
550
  // Claude Code posts `/v1/messages?beta=true` — pathname match ignores the query (003 G9).
527
551
  if (url.pathname === "/v1/messages/count_tokens" && req.method === "POST") {
528
552
  if (isDraining()) {
529
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
553
+ return drainingResponse(req);
530
554
  }
531
555
  if (!hasValidApiAuth(req, config)) {
532
556
  return withCors(anthropicErrorResponse(401, "opencodex API key required", "authentication_error"), req, config);
@@ -541,7 +565,7 @@ export function startServer(port?: number) {
541
565
  if (url.pathname === "/v1/messages" && req.method === "POST") {
542
566
  disableResponsesRequestTimeout(req, requestServer);
543
567
  if (isDraining()) {
544
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
568
+ return drainingResponse(req);
545
569
  }
546
570
  if (!hasValidApiAuth(req, config)) {
547
571
  return withCors(anthropicErrorResponse(401, "opencodex API key required", "authentication_error"), req, config);
@@ -564,7 +588,7 @@ export function startServer(port?: number) {
564
588
  if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
565
589
  disableResponsesRequestTimeout(req, requestServer);
566
590
  if (isDraining()) {
567
- return new Response("Service shutting down", { status: 503, headers: { ...corsHeaders(req, config), "Retry-After": "5" } });
591
+ return drainingResponse(req);
568
592
  }
569
593
  const apiAuthError = requireResponsesApiAuth(req, config);
570
594
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -587,10 +611,7 @@ export function startServer(port?: number) {
587
611
  ) {
588
612
  disableResponsesRequestTimeout(req, requestServer);
589
613
  if (isDraining()) {
590
- return new Response("Service shutting down", {
591
- status: 503,
592
- headers: { ...corsHeaders(req, config), "Retry-After": "5" },
593
- });
614
+ return drainingResponse(req);
594
615
  }
595
616
  const apiAuthError = requireApiAuth(req, config, "data-plane");
596
617
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -618,10 +639,7 @@ export function startServer(port?: number) {
618
639
  : null;
619
640
  if (liveSidebandTarget) {
620
641
  if (isDraining()) {
621
- return new Response("Service shutting down", {
622
- status: 503,
623
- headers: { ...corsHeaders(req, config), "Retry-After": "5" },
624
- });
642
+ return drainingResponse(req);
625
643
  }
626
644
  const apiAuthError = requireApiAuth(req, config, "data-plane");
627
645
  if (apiAuthError) return withCors(apiAuthError, req, config);
@@ -796,9 +814,11 @@ export function startServer(port?: number) {
796
814
  try {
797
815
  if (err instanceof CodexAccountCooldownError) {
798
816
  finalizeLog(429);
817
+ // Codex Desktop rides this WS transport, so it must carry the same
818
+ // actionable text as HTTP; a frame has no headers, hence message-only.
799
819
  sendJsonFrame(ws, buildWsErrorFrame(429, {
800
820
  type: "rate_limit_error",
801
- message: "Selected Codex account is cooling down",
821
+ message: cooldownErrorMessage(err),
802
822
  }));
803
823
  return;
804
824
  }
@@ -24,6 +24,7 @@ import { appendFileSync } from "node:fs";
24
24
  import { formatErrorResponse } from "../bridge";
25
25
  import {
26
26
  CodexAccountCooldownError,
27
+ cooldownErrorResponse,
27
28
  CodexAuthContextError,
28
29
  CodexPoolAuthenticationError,
29
30
  CodexThreadAffinityExpiredError,
@@ -392,7 +393,7 @@ export async function resolveLiveRelay(
392
393
  }
393
394
  } catch (err) {
394
395
  if (err instanceof CodexAccountCooldownError) {
395
- forwardAuthError = formatErrorResponse(429, "rate_limit_error", "Selected Codex account is cooling down");
396
+ forwardAuthError = cooldownErrorResponse(err);
396
397
  } else if (err instanceof CodexThreadAffinityExpiredError) {
397
398
  forwardAuthError = formatErrorResponse(
398
399
  409,