@bitkyc08/opencodex 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +24 -0
  2. package/bin/ocx.mjs +32 -4
  3. package/gui/dist/assets/index-CHwf3tTD.css +1 -0
  4. package/gui/dist/assets/index-u5eFOv2y.js +67 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/src/adapters/anthropic-image-normalize.ts +114 -20
  8. package/src/adapters/anthropic.ts +126 -10
  9. package/src/adapters/azure.ts +3 -3
  10. package/src/adapters/base.ts +7 -3
  11. package/src/adapters/cursor/discovery.ts +14 -4
  12. package/src/adapters/cursor/effort-map.ts +18 -6
  13. package/src/adapters/cursor/framing.ts +102 -27
  14. package/src/adapters/cursor/kv-store.ts +30 -3
  15. package/src/adapters/cursor/live-models.ts +22 -2
  16. package/src/adapters/cursor/live-transport.ts +245 -49
  17. package/src/adapters/cursor/mcp-manager.ts +105 -8
  18. package/src/adapters/cursor/native-exec-mcp.ts +5 -3
  19. package/src/adapters/cursor/native-exec-shell.ts +296 -14
  20. package/src/adapters/cursor/native-exec.ts +381 -33
  21. package/src/adapters/cursor/protobuf-events.ts +28 -1
  22. package/src/adapters/cursor/protobuf-request.ts +71 -39
  23. package/src/adapters/cursor/request-builder.ts +2 -2
  24. package/src/adapters/cursor/transport.ts +2 -0
  25. package/src/adapters/cursor.ts +13 -2
  26. package/src/adapters/google-antigravity-replay.ts +184 -17
  27. package/src/adapters/google.ts +58 -8
  28. package/src/adapters/kiro-thinking.ts +23 -9
  29. package/src/adapters/kiro-tools.ts +49 -18
  30. package/src/adapters/kiro.ts +377 -133
  31. package/src/adapters/mimo-free.ts +36 -4
  32. package/src/adapters/openai-chat.ts +143 -17
  33. package/src/adapters/openai-responses.ts +130 -14
  34. package/src/adapters/run-turn-queue.ts +7 -1
  35. package/src/bridge.ts +466 -69
  36. package/src/chat/outbound.ts +144 -38
  37. package/src/claude/inbound-debug.ts +53 -8
  38. package/src/claude/outbound.ts +224 -38
  39. package/src/cli/agent-driven.ts +34 -1
  40. package/src/cli/catalog-prewarm.ts +5 -2
  41. package/src/cli/claude-desktop.ts +2 -2
  42. package/src/cli/doctor.ts +12 -0
  43. package/src/cli/export-command.ts +187 -0
  44. package/src/cli/help.ts +11 -0
  45. package/src/cli/index.ts +13 -3
  46. package/src/cli/init.ts +129 -102
  47. package/src/cli/opencode.ts +36 -151
  48. package/src/cli/star-prompt.ts +13 -4
  49. package/src/cli/status-oauth.ts +12 -2
  50. package/src/clients/config-export.ts +377 -0
  51. package/src/codex/account-runtime-state.ts +19 -1
  52. package/src/codex/account-store.ts +162 -82
  53. package/src/codex/auth-api.ts +467 -159
  54. package/src/codex/auth-context.ts +15 -2
  55. package/src/codex/catalog/aggregation.ts +15 -0
  56. package/src/codex/catalog/effort.ts +16 -6
  57. package/src/codex/catalog/metadata.ts +6 -0
  58. package/src/codex/catalog/parsing.ts +3 -1
  59. package/src/codex/catalog/provider-fetch.ts +29 -0
  60. package/src/codex/catalog/sync.ts +64 -7
  61. package/src/codex/catalog.ts +2 -2
  62. package/src/codex/inject.ts +5 -5
  63. package/src/codex/main-account-cache.ts +8 -1
  64. package/src/codex/model-cache.ts +81 -2
  65. package/src/codex/pool-rotation.ts +39 -0
  66. package/src/codex/project-config-warnings.ts +12 -1
  67. package/src/codex/quota.ts +35 -3
  68. package/src/codex/routing.ts +46 -1
  69. package/src/codex/shim.ts +10 -4
  70. package/src/codex/subagent-model-fallback.ts +12 -0
  71. package/src/codex/websocket-registry.ts +27 -0
  72. package/src/combos/failover.ts +31 -1
  73. package/src/combos/request.ts +9 -0
  74. package/src/combos/resolve.ts +60 -4
  75. package/src/combos/types.ts +12 -0
  76. package/src/config.ts +510 -55
  77. package/src/github/star-state.ts +13 -1
  78. package/src/images/fulfill.ts +39 -1
  79. package/src/images/loop.ts +52 -12
  80. package/src/lib/admission.ts +83 -0
  81. package/src/lib/app-owned-memory-stores.ts +173 -0
  82. package/src/lib/app-owned-memory.ts +265 -0
  83. package/src/lib/bun-stream-caps.ts +31 -7
  84. package/src/lib/config-ownership.ts +33 -0
  85. package/src/lib/crash-guard.ts +65 -5
  86. package/src/lib/debug-log-buffer.ts +47 -6
  87. package/src/lib/destination-policy.ts +12 -1
  88. package/src/lib/errors.ts +3 -0
  89. package/src/lib/gcp-adc.ts +40 -2
  90. package/src/lib/injection-debug-log.ts +26 -2
  91. package/src/lib/provider-outbound.ts +3 -0
  92. package/src/lib/sidecar-tracker.ts +5 -2
  93. package/src/lib/sse-decoder.ts +257 -37
  94. package/src/lib/state-store-registrations.ts +109 -0
  95. package/src/lib/state-store-sweeper.ts +184 -0
  96. package/src/lib/translator-budget.ts +356 -0
  97. package/src/lib/windows-secret-acl.ts +33 -12
  98. package/src/lib/winsw.ts +14 -1
  99. package/src/oauth/anthropic-routing.ts +31 -7
  100. package/src/oauth/google-antigravity.ts +2 -1
  101. package/src/oauth/health.ts +30 -12
  102. package/src/oauth/index.ts +127 -23
  103. package/src/oauth/kiro-credentials.ts +72 -1
  104. package/src/oauth/kiro.ts +23 -4
  105. package/src/oauth/store.ts +165 -18
  106. package/src/oauth/token-guardian.ts +43 -4
  107. package/src/oauth/types.ts +2 -1
  108. package/src/providers/base-url-choices.ts +10 -0
  109. package/src/providers/derive.ts +12 -0
  110. package/src/providers/free-directory.ts +4 -1
  111. package/src/providers/key-failover.ts +12 -0
  112. package/src/providers/openai-sidecar.ts +4 -1
  113. package/src/providers/quota.ts +68 -7
  114. package/src/providers/registry.ts +279 -3
  115. package/src/responses/parser.ts +5 -1
  116. package/src/responses/spill-store.ts +394 -0
  117. package/src/responses/state.ts +520 -102
  118. package/src/router.ts +18 -1
  119. package/src/server/adapter-resolve.ts +20 -3
  120. package/src/server/auth-cors.ts +121 -28
  121. package/src/server/chat-completions.ts +57 -12
  122. package/src/server/claude-messages.ts +85 -13
  123. package/src/server/index.ts +242 -100
  124. package/src/server/lifecycle.ts +155 -25
  125. package/src/server/management/agent-settings-routes.ts +79 -36
  126. package/src/server/management/api-key-usage.ts +167 -0
  127. package/src/server/management/body.ts +35 -0
  128. package/src/server/management/combo-routes.ts +5 -1
  129. package/src/server/management/config-routes.ts +42 -12
  130. package/src/server/management/logs-usage-routes.ts +41 -21
  131. package/src/server/management/model-routes.ts +188 -54
  132. package/src/server/management/oauth-account-routes.ts +115 -26
  133. package/src/server/management/provider-routes.ts +56 -6
  134. package/src/server/management/shared.ts +16 -3
  135. package/src/server/management/sidebar-routes.ts +50 -1
  136. package/src/server/management/system-restart.ts +13 -6
  137. package/src/server/management/system-routes.ts +15 -3
  138. package/src/server/management/usage-summary-cache.ts +86 -0
  139. package/src/server/management-api.ts +39 -5
  140. package/src/server/management-auth.ts +65 -14
  141. package/src/server/port-reclaim.ts +58 -12
  142. package/src/server/ports.ts +2 -0
  143. package/src/server/proxy-liveness.ts +60 -14
  144. package/src/server/relay-eager.ts +20 -4
  145. package/src/server/relay.ts +548 -154
  146. package/src/server/request-decompress.ts +51 -4
  147. package/src/server/request-log.ts +134 -15
  148. package/src/server/responses/collaboration.ts +15 -4
  149. package/src/server/responses/compact.ts +3 -0
  150. package/src/server/responses/core.ts +241 -66
  151. package/src/server/responses-image-gen-repair.ts +19 -5
  152. package/src/server/responses-item-id-repair.ts +23 -5
  153. package/src/server/sse-payload-rewrite.ts +71 -12
  154. package/src/server/startup-health-cache.ts +14 -1
  155. package/src/server/system-env.ts +8 -1
  156. package/src/server/windows-tcp-drop.ts +15 -5
  157. package/src/server/ws-bridge.ts +25 -0
  158. package/src/service.ts +179 -13
  159. package/src/storage/policy-job.ts +93 -23
  160. package/src/storage/policy-worker.ts +6 -0
  161. package/src/storage/restore-job.ts +62 -16
  162. package/src/storage/restore-worker.ts +6 -0
  163. package/src/storage/storage-mutation-coordinator.ts +36 -6
  164. package/src/storage/worker-lifecycle.ts +181 -47
  165. package/src/tray/windows.ts +97 -25
  166. package/src/types.ts +39 -6
  167. package/src/update/index.ts +24 -5
  168. package/src/update/job.ts +598 -73
  169. package/src/usage/log.ts +115 -17
  170. package/src/usage/summary.ts +67 -2
  171. package/src/vision/index.ts +112 -22
  172. package/src/web-search/loop.ts +38 -6
  173. package/src/web-search/progress-stream.ts +14 -3
  174. package/gui/dist/assets/index-BDjpkcRN.js +0 -67
  175. package/gui/dist/assets/index-BHsKRFh9.css +0 -1
@@ -4,6 +4,7 @@ import {
4
4
  buildWsErrorFrame,
5
5
  selectForwardHeaders,
6
6
  sendJsonFrame,
7
+ buildResponsesWsData,
7
8
  sendResponseToWebSocket,
8
9
  sendTextFrame,
9
10
  type WsData,
@@ -20,6 +21,21 @@ import {
20
21
  import { reconcileOAuthProviders } from "../oauth";
21
22
  import { invalidateCodexModelsCache } from "../codex/catalog";
22
23
  import { startMemoryWatchdog } from "./memory-watchdog";
24
+ import {
25
+ reconcileLiveStateStores,
26
+ setLiveStateStoreConfig,
27
+ } from "../lib/state-store-registrations";
28
+ import { startStateStoreSweeper } from "../lib/state-store-sweeper";
29
+ import {
30
+ configureAppOwnedMemoryBudget,
31
+ enforceAppOwnedMemoryBudget,
32
+ resolveAppOwnedMemoryBudgetBytes,
33
+ } from "../lib/app-owned-memory";
34
+ import {
35
+ registerAppOwnedMemorySweepFallback,
36
+ registerDefaultAppOwnedMemoryStores,
37
+ registerDefaultAppOwnedObservedBuffers,
38
+ } from "../lib/app-owned-memory-stores";
23
39
  import { setStorageCleanupPolicyLiveSink } from "../storage/policy";
24
40
  import { setStorageCleanupPolicyJobLiveApply } from "../storage/policy-job";
25
41
  import { scheduleStorageCleanupStartupRun, startStorageCleanupScheduler } from "../storage/policy-scheduler";
@@ -38,7 +54,8 @@ export {
38
54
  resolveCodexAccountForThread,
39
55
  } from "../codex/routing";
40
56
  import { formatCodexProviderForLog } from "../codex/routing";
41
- import { registerCodexWebSocket, unregisterCodexWebSocket, updateCodexWebSocketAuthContext } from "../codex/websocket-registry";
57
+ import { CatalogGatherBusyError } from "../codex/catalog/provider-fetch";
58
+ import { registerCodexWebSocket, tryReserveCodexWebSocket, unregisterCodexWebSocket, updateCodexWebSocketAuthContext } from "../codex/websocket-registry";
42
59
  import { resolveGuiFilePath, rootFallbackPayload, serveGuiFile } from "./gui-static";
43
60
  export { resolveGuiFilePath, rootFallbackPayload } from "./gui-static";
44
61
  export { resolveAdapter } from "./adapter-resolve";
@@ -50,7 +67,9 @@ import {
50
67
  registerTurn,
51
68
  setServerRef,
52
69
  trackStreamLifetime,
70
+ tryAdmitTurn,
53
71
  unregisterTurn,
72
+ type ActiveTurnLease,
54
73
  } from "./lifecycle";
55
74
  export {
56
75
  drainAndShutdown,
@@ -105,14 +124,14 @@ import {
105
124
  assertServerAuthConfig,
106
125
  corsHeaders,
107
126
  managementCorsHeaders,
108
- hasValidApiAuth,
109
127
  isAllowedRequestOrigin,
110
128
  isAllowedManagementOrigin,
111
129
  isApiAuthRequired,
112
130
  isLoopbackHostname,
113
131
  jsonResponse,
114
- requireApiAuth,
115
- requireResponsesApiAuth,
132
+ admissionFields,
133
+ resolveApiAuth,
134
+ resolveResponsesApiAuth,
116
135
  safeConfigDTO,
117
136
  setCorsOrigin,
118
137
  withCors,
@@ -231,8 +250,10 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
231
250
  // if (isEventStream && upstreamResponse.body) {
232
251
  // const repairConfig = route.provider.responsesItemIdRepair;
233
252
  // const needsClientRewrite = imageGenCallAliases.size > 0
234
- // #314 gated shape (win32-no-client-rewrite only; default OFF on the bundled known-bad runtime):
235
- // decideEagerRelay(config.streamMode ?? "auto")
253
+ // #314 gated shape: win32 no-rewrite traffic follows runtime/config policy; darwin no-rewrite
254
+ // traffic requires explicit config-eager opt-in (`auto` always stays tee on darwin). Default OFF
255
+ // on the bundled known-bad runtime; policy lives in 260731_macos_rss_retention phase 100.
256
+ // selectEagerPath(process.platform, needsClientRewrite, config.streamMode ?? "auto")
236
257
  // relaySseEagerBounded(upstreamResponse.body, turnAc,
237
258
  // new Response(eagerBody,
238
259
  // Default shape (tee + background inspection):
@@ -252,12 +273,14 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
252
273
 
253
274
  export function startServer(port?: number) {
254
275
  const config = runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()));
276
+ setLiveStateStoreConfig(config);
255
277
  applyProxyEnv(config);
256
278
  assertServerAuthConfig(config);
257
279
  const managementAuth = initializeManagementAuthState(config);
258
280
  // Refresh OAuth provider presets (models/noReasoningModels) from the registry so a proxy update
259
281
  // adding/dropping models reaches existing configs on start — not just fresh installs.
260
282
  reconcileOAuthProviders(config);
283
+ reconcileLiveStateStores();
261
284
  // Seed default featured subagent models on first run only (UNSET → defaults). A user-set list,
262
285
  // even [], is left alone so GUI removals persist.
263
286
  if (config.subagentModels === undefined) {
@@ -301,6 +324,12 @@ export function startServer(port?: number) {
301
324
  // #314: warn-only RSS observability (unref'd, idempotent — safe under repeated
302
325
  // startServer(0) in tests). Snapshot surfaces via GET /api/system/memory.
303
326
  startMemoryWatchdog();
327
+ registerDefaultAppOwnedMemoryStores();
328
+ registerDefaultAppOwnedObservedBuffers();
329
+ registerAppOwnedMemorySweepFallback();
330
+ configureAppOwnedMemoryBudget(resolveAppOwnedMemoryBudgetBytes(config.appOwnedMemoryBudgetMb));
331
+ enforceAppOwnedMemoryBudget();
332
+ startStateStoreSweeper();
304
333
  // Issue #42 Phase 3: opt-in archived auto-cleanup (default OFF). Unref'd hourly
305
334
  // tick for daily/weekly; startup evaluation is fire-and-forget after listen.
306
335
  // Heavy work runs in a Worker via the single-flight job controller.
@@ -334,6 +363,31 @@ export function startServer(port?: number) {
334
363
  return new Response(response.body, { status: 503, headers });
335
364
  }
336
365
 
366
+ function serverBusyResponse(req: Request, resource: string): Response {
367
+ return withCors(new Response(JSON.stringify({
368
+ error: { type: "server_error", code: "server_busy", message: `${resource} capacity reached` },
369
+ }), {
370
+ status: 503,
371
+ headers: { "Content-Type": "application/json", "Retry-After": "1" },
372
+ }), req, config);
373
+ }
374
+
375
+ async function runAdmittedHttpTurn(req: Request, work: (lease: ActiveTurnLease) => Promise<Response>): Promise<Response> {
376
+ const lease = tryAdmitTurn();
377
+ if (!lease) return serverBusyResponse(req, "active turns");
378
+ let response: Response;
379
+ try {
380
+ response = await work(lease);
381
+ } catch (error) {
382
+ lease.release();
383
+ throw error;
384
+ }
385
+ if (!lease.isTransferred()) {
386
+ lease.release();
387
+ }
388
+ return response;
389
+ }
390
+
337
391
  const server: Server<WsData> = Bun.serve<WsData>({
338
392
  port: listenPort,
339
393
  hostname: bindHost,
@@ -362,8 +416,10 @@ export function startServer(port?: number) {
362
416
  if (isDraining()) {
363
417
  return drainingResponse(req);
364
418
  }
365
- const apiAuthError = requireResponsesApiAuth(req, config);
366
- if (apiAuthError) return withCors(apiAuthError, req, config);
419
+ const admission = resolveResponsesApiAuth(req, config);
420
+ if (!admission) {
421
+ return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
422
+ }
367
423
  if (!isAllowedRequestOrigin(req, config)) {
368
424
  return withCors(formatErrorResponse(403, "origin_rejected", "WebSocket upgrade blocked: non-local Origin"), req, config);
369
425
  }
@@ -375,11 +431,12 @@ export function startServer(port?: number) {
375
431
  if (!websocketsEnabled(config)) {
376
432
  return withCors(formatErrorResponse(426, "upgrade_required", "Responses WebSocket transport is disabled; use HTTP"), req, config);
377
433
  }
434
+ const websocketLease = tryReserveCodexWebSocket();
435
+ if (!websocketLease) return serverBusyResponse(req, "Codex WebSockets");
378
436
  if (server.upgrade(req, {
379
- data: {
380
- headers: selectForwardHeaders(req.headers),
381
- },
437
+ data: buildResponsesWsData(selectForwardHeaders(req.headers), admission, websocketLease),
382
438
  })) return undefined as unknown as Response;
439
+ websocketLease.release();
383
440
  return withCors(formatErrorResponse(426, "upgrade_required", "WebSocket upgrade failed"), req, config);
384
441
  }
385
442
 
@@ -400,13 +457,24 @@ export function startServer(port?: number) {
400
457
  // Model discovery never forwards Authorization upstream, so the broader admission
401
458
  // set (Authorization / x-api-key / x-opencodex-api-key) is safe here and required by
402
459
  // remote OpenAI-style bearer clients and Claude gateway discovery (anthropic-version).
403
- const apiAuthError = requireApiAuth(req, config, "data-plane");
404
- if (apiAuthError) return withCors(apiAuthError, req, config);
460
+ const admission = resolveApiAuth(req, config);
461
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
405
462
  if (!isAllowedRequestOrigin(req, config)) {
406
463
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
407
464
  }
408
- const goModels = await fetchAllModels(config);
409
- const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, nativeOpenAiSlugs, orderForSubagents, filterCatalogVisibleModels, uniqueCatalogModelsForRawPublicList, visibleNativeSlugs } = await import("../codex/catalog");
465
+ let goModels;
466
+ try {
467
+ goModels = await fetchAllModels(config);
468
+ } catch (error) {
469
+ if (error instanceof CatalogGatherBusyError) {
470
+ return withCors(new Response(JSON.stringify({ error: { type: "server_error", code: "catalog_busy", message: error.message } }), {
471
+ status: 503,
472
+ headers: { "content-type": "application/json", "Retry-After": "1" },
473
+ }), req, config);
474
+ }
475
+ throw error;
476
+ }
477
+ const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, nativeOpenAiSlugs, orderForSubagents, filterCatalogVisibleModels, uniqueCatalogModelsForRawPublicList, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
410
478
  const nativeSlugs = nativeOpenAiSlugs();
411
479
  const goEnabled = filterCatalogVisibleModels(goModels, config);
412
480
  const goOrdered = orderForSubagents(goEnabled, config.subagentModels);
@@ -424,7 +492,7 @@ export function startServer(port?: number) {
424
492
  if (config.claudeCode?.enabled === false) return jsonResponse({ data: [] }, 200, req, config);
425
493
  // Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
426
494
  buildDesktop3pRegistry(
427
- [...visibleNativeSlugs(config)],
495
+ [...desktopVisibleNativeSlugs(config)],
428
496
  goOrdered.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
429
497
  config.claudeCode?.desktopProfile,
430
498
  );
@@ -441,7 +509,7 @@ export function startServer(port?: number) {
441
509
  : idsParam === "desktop"
442
510
  ? "desktop3p" as const
443
511
  : (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
444
- const data = buildAnthropicModelInfos([...visibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias);
512
+ const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias);
445
513
  return jsonResponse({ data }, 200, req, config);
446
514
  }
447
515
  if (url.searchParams.has("client_version")) {
@@ -470,28 +538,30 @@ export function startServer(port?: number) {
470
538
  if (isDraining()) {
471
539
  return drainingResponse(req);
472
540
  }
473
- const apiAuthError = requireResponsesApiAuth(req, config);
474
- if (apiAuthError) return withCors(apiAuthError, req, config);
541
+ const admission = resolveResponsesApiAuth(req, config);
542
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
475
543
  if (!isAllowedRequestOrigin(req, config)) {
476
544
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
477
545
  }
478
546
  const start = Date.now();
479
547
  const requestId = nextRequestLogId(start);
480
- const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
481
- let response: Response;
482
- try {
483
- response = await handleResponsesCompact(req, config, logCtx);
484
- } catch {
485
- response = formatErrorResponse(500, "server_error", "Unexpected compact request failure");
486
- }
487
- addFinalRequestLog(
488
- requestId,
489
- start,
490
- logCtx,
491
- response.status,
492
- response.status === 499 ? { closeReason: "client_cancel" } : undefined,
493
- );
494
- return withCors(response, req, config);
548
+ const logCtx: RequestLogContext = {
549
+ model: "unknown",
550
+ provider: "unknown",
551
+ ...admissionFields(admission),
552
+ inboundProtocol: "responses",
553
+ };
554
+ return runAdmittedHttpTurn(req, async () => {
555
+ let response: Response;
556
+ try {
557
+ response = await handleResponsesCompact(req, config, logCtx);
558
+ } catch {
559
+ response = formatErrorResponse(500, "server_error", "Unexpected compact request failure");
560
+ }
561
+ addFinalRequestLog(requestId, start, logCtx, response.status,
562
+ response.status === 499 ? { closeReason: "client_cancel" } : undefined);
563
+ return withCors(response, req, config);
564
+ });
495
565
  }
496
566
 
497
567
  if (
@@ -502,23 +572,29 @@ export function startServer(port?: number) {
502
572
  if (isDraining()) {
503
573
  return drainingResponse(req);
504
574
  }
505
- const apiAuthError = requireApiAuth(req, config, "data-plane");
506
- if (apiAuthError) return withCors(apiAuthError, req, config);
575
+ const admission = resolveApiAuth(req, config);
576
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
507
577
  if (!isAllowedRequestOrigin(req, config)) {
508
578
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
509
579
  }
510
580
  const start = Date.now();
511
581
  const requestId = nextRequestLogId(start);
512
- const logCtx: RequestLogContext = { model: "image_gen", provider: "unknown" };
582
+ const logCtx: RequestLogContext = {
583
+ model: "image_gen",
584
+ provider: "unknown",
585
+ ...admissionFields(admission),
586
+ };
513
587
  const endpoint = url.pathname.endsWith("/edits") ? "edits" as const : "generations" as const;
514
- const response = await handleImages(req, config, endpoint, logCtx);
515
- addFinalRequestLog(requestId, start, logCtx, response.status, response.status === 499 ? { closeReason: "client_cancel" } : undefined);
516
- return withCors(response, req, config);
588
+ return runAdmittedHttpTurn(req, async () => {
589
+ const response = await handleImages(req, config, endpoint, logCtx);
590
+ addFinalRequestLog(requestId, start, logCtx, response.status, response.status === 499 ? { closeReason: "client_cancel" } : undefined);
591
+ return withCors(response, req, config);
592
+ });
517
593
  }
518
594
 
519
595
  if (req.method === "GET" && url.pathname.startsWith("/v1/opencodex/artifacts/")) {
520
- const apiAuthError = requireApiAuth(req, config, "data-plane");
521
- if (apiAuthError) return withCors(apiAuthError, req, config);
596
+ const admission = resolveApiAuth(req, config);
597
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
522
598
  if (!isAllowedRequestOrigin(req, config)) {
523
599
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
524
600
  }
@@ -551,23 +627,24 @@ export function startServer(port?: number) {
551
627
  if (isDraining()) {
552
628
  return drainingResponse(req);
553
629
  }
554
- const apiAuthError = requireApiAuth(req, config, "data-plane");
555
- if (apiAuthError) return withCors(apiAuthError, req, config);
630
+ const admission = resolveApiAuth(req, config);
631
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
556
632
  if (!isAllowedRequestOrigin(req, config)) {
557
633
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
558
634
  }
559
635
  const start = Date.now();
560
636
  const requestId = nextRequestLogId(start);
561
- const logCtx: RequestLogContext = { model: "web_search", provider: "unknown" };
562
- const response = await handleSearch(req, config, logCtx);
563
- addFinalRequestLog(
564
- requestId,
565
- start,
566
- logCtx,
567
- response.status,
568
- response.status === 499 ? { closeReason: "client_cancel" } : undefined,
569
- );
570
- return withCors(response, req, config);
637
+ const logCtx: RequestLogContext = {
638
+ model: "web_search",
639
+ provider: "unknown",
640
+ ...admissionFields(admission),
641
+ };
642
+ return runAdmittedHttpTurn(req, async () => {
643
+ const response = await handleSearch(req, config, logCtx);
644
+ addFinalRequestLog(requestId, start, logCtx, response.status,
645
+ response.status === 499 ? { closeReason: "client_cancel" } : undefined);
646
+ return withCors(response, req, config);
647
+ });
571
648
  }
572
649
 
573
650
  if (url.pathname === "/v1/responses" && req.method === "POST") {
@@ -575,14 +652,19 @@ export function startServer(port?: number) {
575
652
  if (isDraining()) {
576
653
  return drainingResponse(req);
577
654
  }
578
- const apiAuthError = requireResponsesApiAuth(req, config);
579
- if (apiAuthError) return withCors(apiAuthError, req, config);
655
+ const admission = resolveResponsesApiAuth(req, config);
656
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
580
657
  if (!isAllowedRequestOrigin(req, config)) {
581
658
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
582
659
  }
583
660
  const start = Date.now();
584
661
  const requestId = nextRequestLogId(start);
585
- const logCtx = { model: "unknown", provider: "unknown" };
662
+ const logCtx: RequestLogContext = {
663
+ model: "unknown",
664
+ provider: "unknown",
665
+ ...admissionFields(admission),
666
+ inboundProtocol: "responses",
667
+ };
586
668
  let logged = false;
587
669
  const finalizeNativePassthroughLog = (
588
670
  status: number,
@@ -592,20 +674,23 @@ export function startServer(port?: number) {
592
674
  logged = true;
593
675
  addFinalRequestLog(requestId, start, logCtx, status, meta);
594
676
  };
595
- const response = await handleResponses(req, config, logCtx, {
596
- abortSignal: req.signal,
597
- onFirstOutput: () => recordFirstOutput(logCtx, start),
598
- onNativePassthroughTerminal: status => {
599
- finalizeNativePassthroughLog(httpStatusForTerminalStatus(status), {
600
- terminalStatus: status,
601
- closeReason: "terminal",
602
- });
603
- },
604
- onNativePassthroughCancel: () => {
605
- finalizeNativePassthroughLog(499, { closeReason: "client_cancel" });
606
- },
677
+ return runAdmittedHttpTurn(req, async turnAdmissionLease => {
678
+ const response = await handleResponses(req, config, logCtx, {
679
+ turnAdmissionLease,
680
+ abortSignal: req.signal,
681
+ onFirstOutput: () => recordFirstOutput(logCtx, start),
682
+ onNativePassthroughTerminal: status => {
683
+ finalizeNativePassthroughLog(httpStatusForTerminalStatus(status), {
684
+ terminalStatus: status,
685
+ closeReason: "terminal",
686
+ });
687
+ },
688
+ onNativePassthroughCancel: () => {
689
+ finalizeNativePassthroughLog(499, { closeReason: "client_cancel" });
690
+ },
691
+ });
692
+ return withCors(responseWithDeferredRequestLog(response, requestId, start, logCtx), req, config);
607
693
  });
608
- return withCors(responseWithDeferredRequestLog(response, requestId, start, logCtx), req, config);
609
694
  }
610
695
 
611
696
  // Anthropic Messages inbound (Claude Code). count_tokens FIRST (longer path).
@@ -614,14 +699,14 @@ export function startServer(port?: number) {
614
699
  if (isDraining()) {
615
700
  return drainingResponse(req);
616
701
  }
617
- if (!hasValidApiAuth(req, config)) {
702
+ const admission = resolveApiAuth(req, config);
703
+ if (!admission) {
618
704
  return withCors(anthropicErrorResponse(401, "opencodex API key required", "authentication_error"), req, config);
619
705
  }
620
706
  if (!isAllowedRequestOrigin(req, config)) {
621
707
  return withCors(anthropicErrorResponse(403, "cross-origin data-plane request blocked", "permission_error"), req, config);
622
708
  }
623
- const response = await handleClaudeCountTokens(req, config);
624
- return withCors(response, req, config);
709
+ return runAdmittedHttpTurn(req, async () => withCors(await handleClaudeCountTokens(req, config), req, config));
625
710
  }
626
711
 
627
712
  if (url.pathname === "/v1/messages" && req.method === "POST") {
@@ -629,7 +714,8 @@ export function startServer(port?: number) {
629
714
  if (isDraining()) {
630
715
  return drainingResponse(req);
631
716
  }
632
- if (!hasValidApiAuth(req, config)) {
717
+ const admission = resolveApiAuth(req, config);
718
+ if (!admission) {
633
719
  return withCors(anthropicErrorResponse(401, "opencodex API key required", "authentication_error"), req, config);
634
720
  }
635
721
  if (!isAllowedRequestOrigin(req, config)) {
@@ -637,12 +723,20 @@ export function startServer(port?: number) {
637
723
  }
638
724
  const start = Date.now();
639
725
  const requestId = nextRequestLogId(start);
640
- const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
726
+ const logCtx: RequestLogContext = {
727
+ model: "unknown",
728
+ provider: "unknown",
729
+ ...admissionFields(admission),
730
+ inboundProtocol: "messages",
731
+ };
641
732
  // Logging is finalized inside handleClaudeMessages (Responses-vocab tap on the
642
733
  // pre-translation stream + native passthrough callbacks) — do not re-wrap the
643
734
  // translated Anthropic stream here.
644
- const response = await handleClaudeMessages(req, config, logCtx, { requestId, start });
645
- return withCors(response, req, config);
735
+ return runAdmittedHttpTurn(req, async turnAdmissionLease => withCors(
736
+ await handleClaudeMessages(req, config, logCtx, { requestId, start, turnAdmissionLease }),
737
+ req,
738
+ config,
739
+ ));
646
740
  }
647
741
 
648
742
 
@@ -652,16 +746,24 @@ export function startServer(port?: number) {
652
746
  if (isDraining()) {
653
747
  return drainingResponse(req);
654
748
  }
655
- const apiAuthError = requireResponsesApiAuth(req, config);
656
- if (apiAuthError) return withCors(apiAuthError, req, config);
749
+ const admission = resolveResponsesApiAuth(req, config);
750
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
657
751
  if (!isAllowedRequestOrigin(req, config)) {
658
752
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
659
753
  }
660
754
  const start = Date.now();
661
755
  const requestId = nextRequestLogId(start);
662
- const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
663
- const response = await handleChatCompletions(req, config, logCtx, { requestId, start });
664
- return withCors(response, req, config);
756
+ const logCtx: RequestLogContext = {
757
+ model: "unknown",
758
+ provider: "unknown",
759
+ ...admissionFields(admission),
760
+ inboundProtocol: "chat",
761
+ };
762
+ return runAdmittedHttpTurn(req, async turnAdmissionLease => withCors(
763
+ await handleChatCompletions(req, config, logCtx, { requestId, start, turnAdmissionLease }),
764
+ req,
765
+ config,
766
+ ));
665
767
  }
666
768
 
667
769
  // ChatGPT / Codex App voice (GPT‑Live / Frameless Bidi) + OpenAI Realtime call-create.
@@ -675,23 +777,29 @@ export function startServer(port?: number) {
675
777
  if (isDraining()) {
676
778
  return drainingResponse(req);
677
779
  }
678
- const apiAuthError = requireApiAuth(req, config, "data-plane");
679
- if (apiAuthError) return withCors(apiAuthError, req, config);
780
+ const admission = resolveApiAuth(req, config);
781
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
680
782
  if (!isAllowedRequestOrigin(req, config)) {
681
783
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
682
784
  }
683
785
  const start = Date.now();
684
786
  const requestId = nextRequestLogId(start);
685
- const logCtx: RequestLogContext = { model: "gpt-live", provider: "unknown" };
686
- const response = await handleLive(req, config, logCtx);
687
- addFinalRequestLog(
688
- requestId,
689
- start,
690
- logCtx,
691
- response.status,
692
- response.status === 499 ? { closeReason: "client_cancel" } : undefined,
693
- );
694
- return withCors(response, req, config);
787
+ const logCtx: RequestLogContext = {
788
+ model: "gpt-live",
789
+ provider: "unknown",
790
+ ...admissionFields(admission),
791
+ };
792
+ return runAdmittedHttpTurn(req, async () => {
793
+ const response = await handleLive(req, config, logCtx);
794
+ addFinalRequestLog(
795
+ requestId,
796
+ start,
797
+ logCtx,
798
+ response.status,
799
+ response.status === 499 ? { closeReason: "client_cancel" } : undefined,
800
+ );
801
+ return withCors(response, req, config);
802
+ });
695
803
  }
696
804
 
697
805
  // Voice / Realtime sideband WebSocket: Frameless joins /v1/live/{callId}; Realtime v1 joins
@@ -703,14 +811,18 @@ export function startServer(port?: number) {
703
811
  if (isDraining()) {
704
812
  return drainingResponse(req);
705
813
  }
706
- const apiAuthError = requireApiAuth(req, config, "data-plane");
707
- if (apiAuthError) return withCors(apiAuthError, req, config);
814
+ const admission = resolveApiAuth(req, config);
815
+ if (!admission) return withCors(formatErrorResponse(401, "authentication_error", "opencodex API key required"), req, config);
708
816
  if (!isAllowedRequestOrigin(req, config)) {
709
817
  return withCors(formatErrorResponse(403, "origin_rejected", "WebSocket upgrade blocked: non-local Origin"), req, config);
710
818
  }
711
819
  const start = Date.now();
712
820
  const requestId = nextRequestLogId(start);
713
- const logCtx: RequestLogContext = { model: "gpt-live", provider: "unknown" };
821
+ const logCtx: RequestLogContext = {
822
+ model: "gpt-live",
823
+ provider: "unknown",
824
+ ...admissionFields(admission),
825
+ };
714
826
  const resolved = await resolveLiveSidebandUpgrade(req, config, logCtx, liveSidebandTarget);
715
827
  if (resolved instanceof Response) {
716
828
  addFinalRequestLog(requestId, start, logCtx, resolved.status);
@@ -759,6 +871,11 @@ export function startServer(port?: number) {
759
871
  attachLiveSidebandUpstream(ws);
760
872
  return;
761
873
  }
874
+ if (!ws.data.admissionLease) {
875
+ ws.close(1013, "server busy");
876
+ return;
877
+ }
878
+ ws.data.admissionLease.bind(ws);
762
879
  registerCodexWebSocket(ws);
763
880
  },
764
881
  message(ws: ServerWebSocket<WsData>, raw: string | Buffer) {
@@ -827,13 +944,35 @@ export function startServer(port?: number) {
827
944
  return;
828
945
  }
829
946
 
947
+ const turnAdmissionLease = tryAdmitTurn();
948
+ if (!turnAdmissionLease) {
949
+ sendJsonFrame(ws, buildWsErrorFrame(503, {
950
+ type: "server_error",
951
+ code: "server_busy",
952
+ message: "active turns capacity reached",
953
+ retryable: true,
954
+ }, new Headers({ "Retry-After": "1" })));
955
+ if (ws.data.cancel === cancelTurn) ws.data.cancel = undefined;
956
+ return;
957
+ }
958
+
830
959
  const payload: Record<string, unknown> = { ...frame };
831
960
  delete payload.type;
832
- registerTurn(turnAbort);
961
+ turnAdmissionLease.bindAbortController(turnAbort);
833
962
  void (async () => {
834
963
  const start = Date.now();
835
964
  const requestId = nextRequestLogId(start);
836
- const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
965
+ // Resolved once at the handshake a frame has no request headers left
966
+ // to re-resolve from. Optional on WsData like every other member, so
967
+ // narrow rather than assume: an unattributed frame is preferable to a
968
+ // fabricated attribution.
969
+ const wsAdmission = ws.data.admission;
970
+ const logCtx: RequestLogContext = {
971
+ model: "unknown",
972
+ provider: "unknown",
973
+ ...(wsAdmission ? admissionFields(wsAdmission) : {}),
974
+ inboundProtocol: "responses",
975
+ };
837
976
  let logged = false;
838
977
  const finalizeLog = (
839
978
  status: number,
@@ -856,6 +995,7 @@ export function startServer(port?: number) {
856
995
  const response = await handleResponses(req, config, logCtx, {
857
996
  forceEmptyResponseId: true,
858
997
  abortSignal: turnAbort.signal,
998
+ turnAdmissionLease,
859
999
  onFirstOutput: () => recordFirstOutput(logCtx, start),
860
1000
  onCodexAuthContextResolved: context => updateCodexWebSocketAuthContext(ws, context),
861
1001
  recordTerminalOutcomes: false,
@@ -896,7 +1036,7 @@ export function startServer(port?: number) {
896
1036
  /* socket already gone or send dropped */
897
1037
  }
898
1038
  } finally {
899
- unregisterTurn(turnAbort);
1039
+ turnAdmissionLease.release();
900
1040
  if (!logged && turnAbort.signal.aborted) finalizeLog(499);
901
1041
  if (ws.data.cancel === cancelTurn) ws.data.cancel = undefined;
902
1042
  }
@@ -909,6 +1049,8 @@ export function startServer(port?: number) {
909
1049
  return;
910
1050
  }
911
1051
  unregisterCodexWebSocket(ws);
1052
+ ws.data.admissionLease?.release();
1053
+ ws.data.admissionLease = undefined;
912
1054
  ws.data.cancel?.(); // RC2: abort the upstream when the client disconnects
913
1055
  },
914
1056
  },