@bitkyc08/opencodex 2.10.0 → 2.10.1
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.
- package/AGENTS_INSTALL.md +77 -0
- package/README.md +4 -10
- package/bin/ocx.mjs +71 -18
- package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
- package/gui/dist/assets/index-ChZQsmBY.js +70 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +8 -4
- package/src/adapters/anthropic.ts +208 -14
- package/src/adapters/base.ts +16 -5
- package/src/adapters/cursor/effort-map.ts +3 -2
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/live-transport.ts +105 -95
- package/src/adapters/cursor/native-exec.ts +32 -6
- package/src/adapters/cursor/protobuf-request.ts +20 -15
- package/src/adapters/cursor/request-builder.ts +21 -7
- package/src/adapters/cursor/types.ts +7 -0
- package/src/adapters/google-antigravity-replay.ts +237 -21
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +50 -9
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +10 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +37 -11
- package/src/adapters/openai-chat.ts +284 -83
- package/src/adapters/openai-responses.ts +182 -24
- package/src/bridge.ts +177 -7
- package/src/chat/outbound.ts +78 -23
- package/src/claude/agents-inject.ts +27 -5
- package/src/claude/inbound.ts +11 -1
- package/src/claude/model-info.ts +13 -10
- package/src/claude/outbound.ts +17 -0
- package/src/cli/account-api.ts +24 -0
- package/src/cli/account-auth.ts +31 -6
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +5 -0
- package/src/cli/claude.ts +2 -1
- package/src/cli/doctor.ts +93 -22
- package/src/cli/export-command.ts +26 -12
- package/src/cli/help.ts +8 -6
- package/src/cli/index.ts +56 -22
- package/src/cli/integrations.ts +84 -1
- package/src/cli/observe.ts +54 -1
- package/src/cli/opencode.ts +2 -1
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +1 -1
- package/src/cli/v2.ts +36 -9
- package/src/clients/config-export.ts +687 -10
- package/src/codex/account-lifecycle.ts +30 -5
- package/src/codex/account-usability.ts +22 -2
- package/src/codex/admission.ts +255 -0
- package/src/codex/auth-api.ts +427 -140
- package/src/codex/auth-context.ts +155 -30
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +62 -0
- package/src/codex/catalog/aggregation.ts +14 -1
- package/src/codex/catalog/bundled.ts +282 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/metadata.ts +51 -6
- package/src/codex/catalog/parsing.ts +6 -3
- package/src/codex/catalog/provider-fetch.ts +576 -41
- package/src/codex/catalog/sync.ts +505 -66
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-write-serialization.ts +241 -0
- package/src/codex/catalog.ts +2 -1
- package/src/codex/codex-write-lock.ts +372 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +441 -0
- package/src/codex/desired-state.ts +177 -0
- package/src/codex/features.ts +52 -8
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +257 -0
- package/src/codex/history-lock.ts +241 -0
- package/src/codex/history-migration-guardian.ts +18 -5
- package/src/codex/history-provider.ts +9 -2
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +176 -0
- package/src/codex/inject-coordination.ts +245 -0
- package/src/codex/inject.ts +605 -124
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +10 -1
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +158 -0
- package/src/codex/native-main-owner.ts +315 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1512 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +340 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +557 -0
- package/src/codex/project-config-warnings.ts +18 -4
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +86 -3
- package/src/codex/routing.ts +299 -62
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +39 -13
- package/src/codex/subagent-model-fallback.ts +73 -12
- package/src/codex/transition-state.ts +604 -0
- package/src/codex/upstream-host-health.ts +70 -0
- package/src/codex/user-identity.ts +266 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/config.ts +562 -26
- package/src/generated/jawcode-model-metadata.ts +2 -2
- package/src/grok/inject.ts +15 -4
- package/src/grok/inspect.ts +45 -0
- package/src/images/loop.ts +113 -20
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +165 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +101 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/bounded-body.ts +46 -8
- package/src/lib/bun-runtime.ts +110 -1
- package/src/lib/bun-stream-caps.ts +2 -1
- package/src/lib/redact.ts +407 -2
- package/src/lib/shadow-call.ts +24 -0
- package/src/lib/translator-budget.ts +10 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +154 -2
- package/src/lib/windows-secret-acl.ts +212 -11
- package/src/lib/winsw.ts +9 -3
- package/src/oauth/index.ts +61 -3
- package/src/oauth/key-providers.ts +4 -0
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/store.ts +31 -6
- package/src/oauth/token-guardian.ts +9 -3
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/derive.ts +33 -1
- package/src/providers/free-directory.ts +3 -1
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/openai-sidecar.ts +64 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota.ts +334 -26
- package/src/providers/registry.ts +284 -16
- package/src/providers/xai-transport.ts +11 -4
- package/src/responses/compaction.ts +8 -1
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +44 -2
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +105 -0
- package/src/responses/spill-store.ts +45 -8
- package/src/responses/state.ts +161 -17
- package/src/router.ts +302 -16
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +204 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +590 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile.ts +423 -0
- package/src/routing/quota.ts +124 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/auth-cors.ts +46 -6
- package/src/server/chat-completions.ts +28 -13
- package/src/server/claude-messages.ts +23 -15
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +10 -1
- package/src/server/index.ts +238 -52
- package/src/server/lifecycle.ts +228 -9
- package/src/server/live.ts +14 -3
- package/src/server/management/agent-settings-routes.ts +64 -14
- package/src/server/management/combo-routes.ts +5 -5
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +42 -2
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +32 -113
- package/src/server/management/model-rows.ts +117 -0
- package/src/server/management/native-integration-routes.ts +587 -0
- package/src/server/management/oauth-account-routes.ts +1 -1
- package/src/server/management/provider-routes.ts +218 -117
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +177 -0
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +276 -24
- package/src/server/management/system-routes.ts +4 -0
- package/src/server/management-api.ts +84 -9
- package/src/server/management-auth.ts +43 -5
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +120 -6
- package/src/server/request-log.ts +26 -6
- package/src/server/responses/collaboration.ts +63 -8
- package/src/server/responses/compact.ts +272 -41
- package/src/server/responses/core.ts +730 -132
- package/src/server/responses/fetch-helpers.ts +15 -1
- package/src/server/responses-item-id-repair.ts +32 -3
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/search.ts +51 -6
- package/src/server/sse-payload-rewrite.ts +89 -12
- package/src/server/startup-health-cache.ts +7 -1
- package/src/server/ws-bridge.ts +11 -17
- package/src/service-manager-probe.ts +297 -0
- package/src/service.ts +222 -32
- package/src/tray/windows-tray.ps1 +9 -0
- package/src/tray/windows.ts +15 -7
- package/src/types.ts +194 -14
- package/src/update/index.ts +13 -13
- package/src/update/job.ts +24 -21
- package/src/update/notify.ts +7 -3
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +129 -10
- package/src/usage/log.ts +50 -15
- package/src/usage/summary.ts +4 -4
- package/src/vision/index.ts +6 -1
- package/src/web-search/loop.ts +161 -34
- package/gui/dist/assets/index-OY43ubAq.css +0 -1
- package/gui/dist/assets/index-YwNnKZcL.js +0 -67
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
package/src/server/index.ts
CHANGED
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
websocketsEnabled,
|
|
20
20
|
} from "../config";
|
|
21
21
|
import { reconcileOAuthProviders } from "../oauth";
|
|
22
|
-
import {
|
|
22
|
+
import { withCatalogWriteSerialization } from "../codex/catalog-write-serialization";
|
|
23
|
+
import { invalidateCodexModelsCacheWithPermit } from "../codex/catalog/sync";
|
|
24
|
+
import { getCodexHome } from "../codex/paths";
|
|
25
|
+
import { registerCodexCooldownRecoveryProbeWorker } from "../codex/auth-api";
|
|
23
26
|
import { startMemoryWatchdog } from "./memory-watchdog";
|
|
24
27
|
import {
|
|
25
28
|
reconcileLiveStateStores,
|
|
@@ -48,6 +51,7 @@ import {
|
|
|
48
51
|
CodexAccountCooldownError,
|
|
49
52
|
cooldownErrorMessage,
|
|
50
53
|
} from "../codex/auth-context";
|
|
54
|
+
import { codexAccountNamespaceForModel } from "../codex/account-namespace-match";
|
|
51
55
|
export {
|
|
52
56
|
clearThreadAccountMap,
|
|
53
57
|
formatCodexProviderForLog,
|
|
@@ -56,7 +60,7 @@ export {
|
|
|
56
60
|
import { formatCodexProviderForLog } from "../codex/routing";
|
|
57
61
|
import { CatalogGatherBusyError } from "../codex/catalog/provider-fetch";
|
|
58
62
|
import { registerCodexWebSocket, tryReserveCodexWebSocket, unregisterCodexWebSocket, updateCodexWebSocketAuthContext } from "../codex/websocket-registry";
|
|
59
|
-
import { resolveGuiFilePath, rootFallbackPayload, serveGuiFile } from "./gui-static";
|
|
63
|
+
import { resolveGuiFilePath, rootFallbackPayload, serveGuiFile, serveSessionBootstrap } from "./gui-static";
|
|
60
64
|
export { resolveGuiFilePath, rootFallbackPayload } from "./gui-static";
|
|
61
65
|
export { resolveAdapter } from "./adapter-resolve";
|
|
62
66
|
import { formatErrorResponse, type ResponsesTerminalStatus } from "../bridge";
|
|
@@ -153,24 +157,95 @@ import { handleChatCompletions } from "./chat-completions";
|
|
|
153
157
|
import { anthropicErrorResponse } from "../claude/outbound";
|
|
154
158
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
155
159
|
import { runClaudeAuthModeMigration } from "../claude/auth-mode-migration";
|
|
160
|
+
import {
|
|
161
|
+
bindNativeMainStartupLifecycle,
|
|
162
|
+
releaseNativeMainStartupLifecycle,
|
|
163
|
+
startNativeMainStartupLifecycle,
|
|
164
|
+
type NativeMainStartupGateDeps,
|
|
165
|
+
} from "../codex/native-profile-startup";
|
|
156
166
|
import { handleImages } from "./images";
|
|
157
167
|
import { handleLive, logLiveSidebandFrame, parseLiveSidebandTarget, resolveLiveSidebandUpgrade } from "./live";
|
|
158
168
|
import { handleSearch } from "./search";
|
|
159
|
-
import { fetchAllModels, handleManagementAPI, VERSION } from "./management-api";
|
|
160
|
-
import {
|
|
169
|
+
import { fetchAllModels, handleManagementAPI, VERSION, type ManagementApiDeps } from "./management-api";
|
|
170
|
+
import {
|
|
171
|
+
initializeManagementAuthState,
|
|
172
|
+
issueGuiSession,
|
|
173
|
+
managementPrincipal,
|
|
174
|
+
requireManagementAuth,
|
|
175
|
+
type ManagementAuthState,
|
|
176
|
+
} from "./management-auth";
|
|
161
177
|
|
|
162
178
|
const MAX_WS_FRAME_BYTES = 50 * 1024 * 1024;
|
|
163
179
|
const WEBSOCKET_IDLE_TIMEOUT_SECONDS = 0;
|
|
164
180
|
const LIVE_SIDEBAND_PENDING_MAX = 32;
|
|
181
|
+
const LIVE_SIDEBAND_CLOSE_FALLBACK_MS = 1_000;
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
183
|
+
type LiveSidebandWebSocketFactory = (
|
|
184
|
+
url: string,
|
|
185
|
+
headers: Record<string, string>,
|
|
186
|
+
) => WebSocket;
|
|
187
|
+
|
|
188
|
+
function releaseLiveSidebandAdmission(ws: ServerWebSocket<WsData>): void {
|
|
189
|
+
ws.data.liveTurnAdmissionLease?.release();
|
|
190
|
+
ws.data.liveTurnAdmissionLease = undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function finalizeLiveSideband(ws: ServerWebSocket<WsData>, upstream?: WebSocket): void {
|
|
194
|
+
if (upstream && ws.data.liveUpstream !== upstream) return;
|
|
195
|
+
if (ws.data.liveCloseFallback !== undefined) {
|
|
196
|
+
clearTimeout(ws.data.liveCloseFallback);
|
|
197
|
+
ws.data.liveCloseFallback = undefined;
|
|
171
198
|
}
|
|
172
199
|
ws.data.liveUpstream = undefined;
|
|
173
200
|
ws.data.livePending = undefined;
|
|
201
|
+
ws.data.cancel = undefined;
|
|
202
|
+
releaseLiveSidebandAdmission(ws);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function armLiveSidebandCloseFallback(ws: ServerWebSocket<WsData>, upstream: WebSocket): void {
|
|
206
|
+
if (ws.data.liveCloseFallback !== undefined) return;
|
|
207
|
+
ws.data.liveCloseFallback = setTimeout(() => {
|
|
208
|
+
ws.data.liveCloseFallback = undefined;
|
|
209
|
+
if (ws.data.liveUpstream !== upstream) return;
|
|
210
|
+
if (upstream.readyState === WebSocket.CLOSED) {
|
|
211
|
+
finalizeLiveSideband(ws, upstream);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// A close frame was already sent below. Retry once, but never surrender
|
|
215
|
+
// native-main ownership while the authenticated transport remains live.
|
|
216
|
+
try {
|
|
217
|
+
upstream.close(1000, "upstream close timeout");
|
|
218
|
+
} catch {
|
|
219
|
+
/* upstream is already unusable */
|
|
220
|
+
}
|
|
221
|
+
// Some implementations transition synchronously without delivering the
|
|
222
|
+
// close event. That is still an observed CLOSED transport and is safe to
|
|
223
|
+
// finalize. CONNECTING/CLOSING peers keep the lease so profile switching
|
|
224
|
+
// fails at its own bounded drain deadline instead of racing live traffic.
|
|
225
|
+
if (upstream.readyState === WebSocket.CLOSED) finalizeLiveSideband(ws, upstream);
|
|
226
|
+
}, LIVE_SIDEBAND_CLOSE_FALLBACK_MS);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function closeLiveSideband(ws: ServerWebSocket<WsData>, code = 1000, reason = ""): void {
|
|
230
|
+
if (ws.data.liveClosing) return;
|
|
231
|
+
ws.data.liveClosing = true;
|
|
232
|
+
ws.data.livePending = undefined;
|
|
233
|
+
ws.data.cancel = undefined;
|
|
234
|
+
const upstream = ws.data.liveUpstream;
|
|
235
|
+
if (!upstream || upstream.readyState === WebSocket.CLOSED) {
|
|
236
|
+
finalizeLiveSideband(ws, upstream);
|
|
237
|
+
} else {
|
|
238
|
+
// The sideband holds a native-main admission lease. Do not release it just
|
|
239
|
+
// because the downstream left: its authenticated upstream remains live
|
|
240
|
+
// until the close event arrives or the transport is observed CLOSED. The
|
|
241
|
+
// bounded fallback only retries close; it does not release ownership.
|
|
242
|
+
armLiveSidebandCloseFallback(ws, upstream);
|
|
243
|
+
try {
|
|
244
|
+
upstream.close(code, reason);
|
|
245
|
+
} catch {
|
|
246
|
+
/* the fallback retries close without releasing ownership */
|
|
247
|
+
}
|
|
248
|
+
}
|
|
174
249
|
try {
|
|
175
250
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
176
251
|
ws.close(code, reason);
|
|
@@ -180,7 +255,12 @@ function closeLiveSideband(ws: ServerWebSocket<WsData>, code = 1000, reason = ""
|
|
|
180
255
|
}
|
|
181
256
|
}
|
|
182
257
|
|
|
183
|
-
function attachLiveSidebandUpstream(
|
|
258
|
+
function attachLiveSidebandUpstream(
|
|
259
|
+
ws: ServerWebSocket<WsData>,
|
|
260
|
+
createWebSocket: LiveSidebandWebSocketFactory = (url, headers) => (
|
|
261
|
+
new WebSocket(url, { headers } as unknown as string[])
|
|
262
|
+
),
|
|
263
|
+
): void {
|
|
184
264
|
const url = ws.data.liveUpstreamUrl;
|
|
185
265
|
if (!url) {
|
|
186
266
|
closeLiveSideband(ws, 1011, "missing upstream");
|
|
@@ -189,21 +269,17 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
|
|
|
189
269
|
let upstream: WebSocket;
|
|
190
270
|
try {
|
|
191
271
|
// Bun accepts per-handshake headers; the DOM lib types only list protocol arrays.
|
|
192
|
-
upstream =
|
|
272
|
+
upstream = createWebSocket(url, ws.data.liveUpstreamHeaders ?? {});
|
|
193
273
|
} catch {
|
|
194
274
|
closeLiveSideband(ws, 1011, "upstream connect failed");
|
|
195
275
|
return;
|
|
196
276
|
}
|
|
197
277
|
ws.data.liveUpstream = upstream;
|
|
198
|
-
ws.data.
|
|
199
|
-
|
|
200
|
-
upstream.close(1000, "client closed");
|
|
201
|
-
} catch {
|
|
202
|
-
/* ignore */
|
|
203
|
-
}
|
|
204
|
-
};
|
|
278
|
+
ws.data.liveClosing = false;
|
|
279
|
+
ws.data.cancel = () => closeLiveSideband(ws, 1000, "client closed");
|
|
205
280
|
|
|
206
281
|
upstream.addEventListener("open", () => {
|
|
282
|
+
if (ws.data.liveUpstream !== upstream || ws.data.liveClosing) return;
|
|
207
283
|
ws.data.liveOpened = true;
|
|
208
284
|
const pending = ws.data.livePending ?? [];
|
|
209
285
|
ws.data.livePending = undefined;
|
|
@@ -217,6 +293,7 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
|
|
|
217
293
|
}
|
|
218
294
|
});
|
|
219
295
|
upstream.addEventListener("message", (event) => {
|
|
296
|
+
if (ws.data.liveUpstream !== upstream || ws.data.liveClosing) return;
|
|
220
297
|
try {
|
|
221
298
|
logLiveSidebandFrame("u2c", event.data);
|
|
222
299
|
if (typeof event.data === "string") ws.send(event.data);
|
|
@@ -229,14 +306,17 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
|
|
|
229
306
|
}
|
|
230
307
|
});
|
|
231
308
|
upstream.addEventListener("close", (event) => {
|
|
309
|
+
if (ws.data.liveUpstream !== upstream) return;
|
|
310
|
+
ws.data.liveClosing = true;
|
|
311
|
+
finalizeLiveSideband(ws, upstream);
|
|
232
312
|
try {
|
|
233
313
|
ws.close(event.code || 1000, event.reason || "");
|
|
234
314
|
} catch {
|
|
235
315
|
/* ignore */
|
|
236
316
|
}
|
|
237
|
-
ws.data.liveUpstream = undefined;
|
|
238
317
|
});
|
|
239
318
|
upstream.addEventListener("error", () => {
|
|
319
|
+
if (ws.data.liveUpstream !== upstream) return;
|
|
240
320
|
closeLiveSideband(ws, 1011, "upstream error");
|
|
241
321
|
});
|
|
242
322
|
}
|
|
@@ -250,18 +330,15 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
|
|
|
250
330
|
// if (isEventStream && upstreamResponse.body) {
|
|
251
331
|
// const repairConfig = route.provider.responsesItemIdRepair;
|
|
252
332
|
// const needsClientRewrite = imageGenCallAliases.size > 0
|
|
253
|
-
// #314 gated shape: win32
|
|
254
|
-
//
|
|
255
|
-
//
|
|
333
|
+
// #314 gated shape: win32 always uses the terminal-aware eager relay so a keep-alive
|
|
334
|
+
// upstream cannot hold Codex open after response.completed; darwin no-rewrite traffic
|
|
335
|
+
// requires explicit config-eager opt-in (`auto` always stays tee on darwin).
|
|
256
336
|
// selectEagerPath(process.platform, needsClientRewrite, config.streamMode ?? "auto")
|
|
257
337
|
// relaySseEagerBounded(upstreamResponse.body, turnAc,
|
|
258
338
|
// new Response(eagerBody,
|
|
259
339
|
// Default shape (tee + background inspection):
|
|
260
340
|
// upstreamResponse.body.tee()
|
|
261
341
|
// const repairedBody = hasResponsesItemIdRepair(repairConfig)
|
|
262
|
-
// process.platform === "win32"
|
|
263
|
-
// && !needsClientRewrite
|
|
264
|
-
// ? nativeBody
|
|
265
342
|
// relaySseWithFailedTail(repairedBody, upstream)
|
|
266
343
|
// new Response(clientBody
|
|
267
344
|
// markNativePassthroughSseResponse
|
|
@@ -271,12 +348,25 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
|
|
|
271
348
|
// trackSseForRequestLog(
|
|
272
349
|
// export function relaySseWithHeartbeat
|
|
273
350
|
|
|
274
|
-
export
|
|
351
|
+
export interface StartServerDeps {
|
|
352
|
+
/** Test-only seam; production always initializes its own management credential state. */
|
|
353
|
+
managementAuthState?: ManagementAuthState;
|
|
354
|
+
/** Test-only route dependencies, forwarded only after management admission succeeds. */
|
|
355
|
+
managementApi?: ManagementApiDeps;
|
|
356
|
+
/** Test-only native-main recovery dependencies; production constructs the normal manager. */
|
|
357
|
+
nativeMainStartup?: NativeMainStartupGateDeps;
|
|
358
|
+
/** Test-only seam for an upstream that cannot complete its WebSocket close handshake. */
|
|
359
|
+
liveSidebandWebSocketFactory?: LiveSidebandWebSocketFactory;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function startServer(port?: number, deps: StartServerDeps = {}) {
|
|
275
363
|
const config = runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()));
|
|
276
364
|
setLiveStateStoreConfig(config);
|
|
277
365
|
applyProxyEnv(config);
|
|
278
366
|
assertServerAuthConfig(config);
|
|
279
|
-
const managementAuth = initializeManagementAuthState(config);
|
|
367
|
+
const managementAuth = deps.managementAuthState ?? initializeManagementAuthState(config);
|
|
368
|
+
// Arm synchronously before listen. A pending journal therefore makes __main__ unusable
|
|
369
|
+
// before any request can resolve its physical credential, while health/management/Pool stay live.
|
|
280
370
|
// Refresh OAuth provider presets (models/noReasoningModels) from the registry so a proxy update
|
|
281
371
|
// adding/dropping models reaches existing configs on start — not just fresh installs.
|
|
282
372
|
reconcileOAuthProviders(config);
|
|
@@ -310,7 +400,16 @@ export function startServer(port?: number) {
|
|
|
310
400
|
if (migrated) saveConfig(config);
|
|
311
401
|
}
|
|
312
402
|
}
|
|
313
|
-
|
|
403
|
+
// Startup cache invalidation is best-effort and must never block the server from
|
|
404
|
+
// serving. It now takes K so it cannot race a convergence commit, but both the
|
|
405
|
+
// home resolution and the acquisition can fail on a machine with no Codex home —
|
|
406
|
+
// `getCodexHome()` THROWS when CODEX_HOME names a missing directory, which would
|
|
407
|
+
// otherwise turn "no Codex installed" into "proxy will not start".
|
|
408
|
+
try {
|
|
409
|
+
const startupCodexHome = getCodexHome();
|
|
410
|
+
withCatalogWriteSerialization(startupCodexHome, permit =>
|
|
411
|
+
invalidateCodexModelsCacheWithPermit(permit, startupCodexHome));
|
|
412
|
+
} catch { /* no readable Codex home: nothing to invalidate */ }
|
|
314
413
|
// Arm the `claudeCode` hand-edit guard (devlog 260726_claude_auth_auto/040 H1) BEFORE
|
|
315
414
|
// the server can serve a request, and AFTER the startup migrations above — those run
|
|
316
415
|
// against a config nobody else holds and are the documented exception to the save
|
|
@@ -329,6 +428,7 @@ export function startServer(port?: number) {
|
|
|
329
428
|
registerAppOwnedMemorySweepFallback();
|
|
330
429
|
configureAppOwnedMemoryBudget(resolveAppOwnedMemoryBudgetBytes(config.appOwnedMemoryBudgetMb));
|
|
331
430
|
enforceAppOwnedMemoryBudget();
|
|
431
|
+
registerCodexCooldownRecoveryProbeWorker(config);
|
|
332
432
|
startStateStoreSweeper();
|
|
333
433
|
// Issue #42 Phase 3: opt-in archived auto-cleanup (default OFF). Unref'd hourly
|
|
334
434
|
// tick for daily/weekly; startup evaluation is fire-and-forget after listen.
|
|
@@ -388,7 +488,10 @@ export function startServer(port?: number) {
|
|
|
388
488
|
return response;
|
|
389
489
|
}
|
|
390
490
|
|
|
391
|
-
const
|
|
491
|
+
const nativeMainLifecycle = startNativeMainStartupLifecycle(deps.nativeMainStartup);
|
|
492
|
+
let server: Server<WsData>;
|
|
493
|
+
try {
|
|
494
|
+
server = Bun.serve<WsData>({
|
|
392
495
|
port: listenPort,
|
|
393
496
|
hostname: bindHost,
|
|
394
497
|
idleTimeout: 255,
|
|
@@ -448,7 +551,11 @@ export function startServer(port?: number) {
|
|
|
448
551
|
if (url.pathname.startsWith("/api/")) {
|
|
449
552
|
const apiAuthError = requireManagementAuth(req, managementAuth, config);
|
|
450
553
|
if (apiAuthError) return withManagementCors(apiAuthError, req, config);
|
|
451
|
-
|
|
554
|
+
// Which credential passed the gate, resolved from the same session table the
|
|
555
|
+
// gate used. Consent-bearing routes need this: request headers are forgeable
|
|
556
|
+
// by anything holding the admin token, the credential is not.
|
|
557
|
+
const principal = managementPrincipal(req, managementAuth, config) ?? undefined;
|
|
558
|
+
const mgmtResponse = await handleManagementAPI(req, url, config, deps.managementApi, principal);
|
|
452
559
|
if (mgmtResponse) return withManagementCors(mgmtResponse, req, config);
|
|
453
560
|
return withManagementCors(formatErrorResponse(404, "not_found", `Unknown endpoint: ${req.method} ${url.pathname}`), req, config);
|
|
454
561
|
}
|
|
@@ -474,8 +581,15 @@ export function startServer(port?: number) {
|
|
|
474
581
|
}
|
|
475
582
|
throw error;
|
|
476
583
|
}
|
|
477
|
-
const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, uniqueCatalogModelsForRawPublicList, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
478
|
-
const
|
|
584
|
+
const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
585
|
+
const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
|
|
586
|
+
const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
|
|
587
|
+
const nativeSlugs = includeNativeOpenAi ? nativeOpenAiSlugs() : [];
|
|
588
|
+
const disabledNatives = disabledNativeSlugs(config);
|
|
589
|
+
const disabledModels = new Set(config.disabledModels ?? []);
|
|
590
|
+
const accountSelectors = includeAccountBoundNativeOpenAi
|
|
591
|
+
? visibleCodexAccountSelectors(config)
|
|
592
|
+
: [];
|
|
479
593
|
const goEnabled = filterCatalogVisibleModels(goModels, config);
|
|
480
594
|
const goOrdered = orderForSubagents(goEnabled, config.subagentModels);
|
|
481
595
|
// Claude Code / Claude Desktop gateway model discovery (GET /v1/models with
|
|
@@ -519,8 +633,20 @@ export function startServer(port?: number) {
|
|
|
519
633
|
// Disabled natives stay in the catalog shape with visibility "hide" (mirrors the
|
|
520
634
|
// on-disk sync; codex-rs keeps them out of the picker itself).
|
|
521
635
|
const maMode = config.multiAgentMode === "v1" || config.multiAgentMode === "v2" ? config.multiAgentMode : "default";
|
|
522
|
-
|
|
523
|
-
|
|
636
|
+
// Account rows use the same hidden-inclusive supported set as on-disk sync. This lets a
|
|
637
|
+
// newly re-enabled native reappear under each selector before the next sync, while the
|
|
638
|
+
// no-selector path keeps nativeOpenAiSlugs()'s existing visibility-sensitive behavior.
|
|
639
|
+
const catalogNativeSlugs = accountSelectors.length > 0
|
|
640
|
+
? NATIVE_OPENAI_MODELS
|
|
641
|
+
: nativeSlugs;
|
|
642
|
+
const entries = buildCatalogEntries(loadCatalogTemplate(), catalogNativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), accountSelectors);
|
|
643
|
+
return jsonResponse({
|
|
644
|
+
models: applyNativeVisibility(
|
|
645
|
+
entries,
|
|
646
|
+
disabledModels,
|
|
647
|
+
accountSelectors.length > 0,
|
|
648
|
+
),
|
|
649
|
+
}, 200, req, config);
|
|
524
650
|
}
|
|
525
651
|
// OpenAI list shape: native gpt bare + routed models namespaced "<provider>/<id>"
|
|
526
652
|
// (pure availability list — disabled natives are omitted entirely).
|
|
@@ -548,14 +674,34 @@ export function startServer(port?: number) {
|
|
|
548
674
|
reasoning_efforts: efforts.map(effort => grokEffortOption(effort, effort === defaultEffort)),
|
|
549
675
|
};
|
|
550
676
|
};
|
|
551
|
-
const
|
|
552
|
-
...visibleNativeSlugs(config).map(id => ({
|
|
677
|
+
const nativeModelRow = (id: string, metadataId = id) => ({
|
|
553
678
|
id,
|
|
554
679
|
object: "model",
|
|
555
680
|
created: 0,
|
|
556
681
|
owned_by: "openai",
|
|
557
|
-
...grokEffortFields(
|
|
558
|
-
|
|
682
|
+
...grokEffortFields(
|
|
683
|
+
nativeReasoningEfforts(metadataId),
|
|
684
|
+
nativeDefaultReasoningEffort(metadataId),
|
|
685
|
+
),
|
|
686
|
+
});
|
|
687
|
+
// Selector-active discovery follows the same complete supported set as the Codex catalog
|
|
688
|
+
// for both bare and qualified rows. Without selectors, the live catalog continues to own
|
|
689
|
+
// bare availability.
|
|
690
|
+
const selectorNativeSlugs = accountSelectors.length > 0
|
|
691
|
+
? NATIVE_OPENAI_MODELS.filter(slug => !disabledNatives.has(slug))
|
|
692
|
+
: [];
|
|
693
|
+
const visibleNatives = includeNativeOpenAi
|
|
694
|
+
? accountSelectors.length > 0 ? selectorNativeSlugs : visibleNativeSlugs(config)
|
|
695
|
+
: [];
|
|
696
|
+
const visibleAccountNatives = accountSelectors.flatMap(selector =>
|
|
697
|
+
selectorNativeSlugs.flatMap(metadataId => {
|
|
698
|
+
const id = `${selector}/${metadataId}`;
|
|
699
|
+
return disabledModels.has(id) ? [] : [{ id, metadataId }];
|
|
700
|
+
})
|
|
701
|
+
);
|
|
702
|
+
const data = [
|
|
703
|
+
...visibleNatives.map(id => nativeModelRow(id)),
|
|
704
|
+
...visibleAccountNatives.map(({ id, metadataId }) => nativeModelRow(id, metadataId)),
|
|
559
705
|
...uniqueCatalogModelsForRawPublicList(goOrdered).map(m => ({
|
|
560
706
|
id: m.alias ?? `${m.provider}/${m.id}`,
|
|
561
707
|
object: "model",
|
|
@@ -587,10 +733,10 @@ export function startServer(port?: number) {
|
|
|
587
733
|
...admissionFields(admission),
|
|
588
734
|
inboundProtocol: "responses",
|
|
589
735
|
};
|
|
590
|
-
return runAdmittedHttpTurn(req, async
|
|
736
|
+
return runAdmittedHttpTurn(req, async turnAdmissionLease => {
|
|
591
737
|
let response: Response;
|
|
592
738
|
try {
|
|
593
|
-
response = await handleResponsesCompact(req, config, logCtx);
|
|
739
|
+
response = await handleResponsesCompact(req, config, logCtx, turnAdmissionLease);
|
|
594
740
|
} catch {
|
|
595
741
|
response = formatErrorResponse(500, "server_error", "Unexpected compact request failure");
|
|
596
742
|
}
|
|
@@ -621,8 +767,8 @@ export function startServer(port?: number) {
|
|
|
621
767
|
...admissionFields(admission),
|
|
622
768
|
};
|
|
623
769
|
const endpoint = url.pathname.endsWith("/edits") ? "edits" as const : "generations" as const;
|
|
624
|
-
return runAdmittedHttpTurn(req, async
|
|
625
|
-
const response = await handleImages(req, config, endpoint, logCtx);
|
|
770
|
+
return runAdmittedHttpTurn(req, async turnAdmissionLease => {
|
|
771
|
+
const response = await handleImages(req, config, endpoint, logCtx, turnAdmissionLease);
|
|
626
772
|
addFinalRequestLog(requestId, start, logCtx, response.status, response.status === 499 ? { closeReason: "client_cancel" } : undefined);
|
|
627
773
|
return withCors(response, req, config);
|
|
628
774
|
});
|
|
@@ -675,8 +821,8 @@ export function startServer(port?: number) {
|
|
|
675
821
|
provider: "unknown",
|
|
676
822
|
...admissionFields(admission),
|
|
677
823
|
};
|
|
678
|
-
return runAdmittedHttpTurn(req, async
|
|
679
|
-
const response = await handleSearch(req, config, logCtx);
|
|
824
|
+
return runAdmittedHttpTurn(req, async turnAdmissionLease => {
|
|
825
|
+
const response = await handleSearch(req, config, logCtx, turnAdmissionLease);
|
|
680
826
|
addFinalRequestLog(requestId, start, logCtx, response.status,
|
|
681
827
|
response.status === 499 ? { closeReason: "client_cancel" } : undefined);
|
|
682
828
|
return withCors(response, req, config);
|
|
@@ -825,8 +971,8 @@ export function startServer(port?: number) {
|
|
|
825
971
|
provider: "unknown",
|
|
826
972
|
...admissionFields(admission),
|
|
827
973
|
};
|
|
828
|
-
return runAdmittedHttpTurn(req, async
|
|
829
|
-
const response = await handleLive(req, config, logCtx);
|
|
974
|
+
return runAdmittedHttpTurn(req, async turnAdmissionLease => {
|
|
975
|
+
const response = await handleLive(req, config, logCtx, turnAdmissionLease);
|
|
830
976
|
addFinalRequestLog(
|
|
831
977
|
requestId,
|
|
832
978
|
start,
|
|
@@ -859,8 +1005,17 @@ export function startServer(port?: number) {
|
|
|
859
1005
|
provider: "unknown",
|
|
860
1006
|
...admissionFields(admission),
|
|
861
1007
|
};
|
|
862
|
-
const
|
|
1008
|
+
const turnAdmissionLease = tryAdmitTurn();
|
|
1009
|
+
if (!turnAdmissionLease) return serverBusyResponse(req, "active turns");
|
|
1010
|
+
let resolved;
|
|
1011
|
+
try {
|
|
1012
|
+
resolved = await resolveLiveSidebandUpgrade(req, config, logCtx, liveSidebandTarget, turnAdmissionLease);
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
turnAdmissionLease.release();
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
863
1017
|
if (resolved instanceof Response) {
|
|
1018
|
+
turnAdmissionLease.release();
|
|
864
1019
|
addFinalRequestLog(requestId, start, logCtx, resolved.status);
|
|
865
1020
|
return withCors(resolved, req, config);
|
|
866
1021
|
}
|
|
@@ -872,8 +1027,10 @@ export function startServer(port?: number) {
|
|
|
872
1027
|
liveUpstreamHeaders: resolved.headers,
|
|
873
1028
|
livePending: [],
|
|
874
1029
|
liveOpened: false,
|
|
1030
|
+
liveTurnAdmissionLease: turnAdmissionLease,
|
|
875
1031
|
} satisfies WsData,
|
|
876
1032
|
})) return undefined as unknown as Response;
|
|
1033
|
+
turnAdmissionLease.release();
|
|
877
1034
|
return withCors(formatErrorResponse(426, "upgrade_required", "WebSocket upgrade failed"), req, config);
|
|
878
1035
|
}
|
|
879
1036
|
|
|
@@ -888,6 +1045,11 @@ export function startServer(port?: number) {
|
|
|
888
1045
|
const guiSessionCandidate = req.method === "GET" && (url.pathname === "/" || !url.pathname.includes("."))
|
|
889
1046
|
? issueGuiSession(req, config, managementAuth)
|
|
890
1047
|
: null;
|
|
1048
|
+
// Dedicated bootstrap path: answer without requiring a packaged GUI build, so the
|
|
1049
|
+
// Vite dev server can mint an origin-bound loopback session on a fresh checkout.
|
|
1050
|
+
if (url.pathname === "/opencodex-session" && guiSessionCandidate) {
|
|
1051
|
+
return serveSessionBootstrap(guiSessionCandidate);
|
|
1052
|
+
}
|
|
891
1053
|
const guiFile = serveGuiFile(url.pathname, undefined, guiSessionCandidate ?? undefined);
|
|
892
1054
|
if (guiFile) return guiFile;
|
|
893
1055
|
if (url.pathname === "/" && req.method === "GET") {
|
|
@@ -904,7 +1066,11 @@ export function startServer(port?: number) {
|
|
|
904
1066
|
// Live sideband sockets (kind=live-sideband) are a transparent bidirectional relay instead.
|
|
905
1067
|
open(ws: ServerWebSocket<WsData>) {
|
|
906
1068
|
if (ws.data.kind === "live-sideband") {
|
|
907
|
-
|
|
1069
|
+
if (!ws.data.liveTurnAdmissionLease) {
|
|
1070
|
+
closeLiveSideband(ws, 1013, "server busy");
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
attachLiveSidebandUpstream(ws, deps.liveSidebandWebSocketFactory);
|
|
908
1074
|
return;
|
|
909
1075
|
}
|
|
910
1076
|
if (!ws.data.admissionLease) {
|
|
@@ -916,6 +1082,7 @@ export function startServer(port?: number) {
|
|
|
916
1082
|
},
|
|
917
1083
|
message(ws: ServerWebSocket<WsData>, raw: string | Buffer) {
|
|
918
1084
|
if (ws.data.kind === "live-sideband") {
|
|
1085
|
+
if (ws.data.liveClosing) return;
|
|
919
1086
|
logLiveSidebandFrame("c2u", raw);
|
|
920
1087
|
const upstream = ws.data.liveUpstream;
|
|
921
1088
|
if (!upstream || upstream.readyState === WebSocket.CONNECTING || !ws.data.liveOpened) {
|
|
@@ -1030,6 +1197,7 @@ export function startServer(port?: number) {
|
|
|
1030
1197
|
let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
|
|
1031
1198
|
const response = await handleResponses(req, config, logCtx, {
|
|
1032
1199
|
forceEmptyResponseId: true,
|
|
1200
|
+
inboundTransport: "websocket",
|
|
1033
1201
|
abortSignal: turnAbort.signal,
|
|
1034
1202
|
turnAdmissionLease,
|
|
1035
1203
|
onFirstOutput: () => recordFirstOutput(logCtx, start),
|
|
@@ -1057,9 +1225,12 @@ export function startServer(port?: number) {
|
|
|
1057
1225
|
finalizeLog(429);
|
|
1058
1226
|
// Codex Desktop rides this WS transport, so it must carry the same
|
|
1059
1227
|
// actionable text as HTTP; a frame has no headers, hence message-only.
|
|
1228
|
+
const accountSelector = typeof payload.model === "string"
|
|
1229
|
+
? codexAccountNamespaceForModel(config.codexAccountNamespaces, payload.model)
|
|
1230
|
+
: undefined;
|
|
1060
1231
|
sendJsonFrame(ws, buildWsErrorFrame(429, {
|
|
1061
1232
|
type: "rate_limit_error",
|
|
1062
|
-
message: cooldownErrorMessage(err),
|
|
1233
|
+
message: cooldownErrorMessage(err, accountSelector),
|
|
1063
1234
|
}));
|
|
1064
1235
|
return;
|
|
1065
1236
|
}
|
|
@@ -1080,8 +1251,7 @@ export function startServer(port?: number) {
|
|
|
1080
1251
|
},
|
|
1081
1252
|
close(ws: ServerWebSocket<WsData>) {
|
|
1082
1253
|
if (ws.data.kind === "live-sideband") {
|
|
1083
|
-
ws
|
|
1084
|
-
ws.data.liveUpstream = undefined;
|
|
1254
|
+
closeLiveSideband(ws);
|
|
1085
1255
|
return;
|
|
1086
1256
|
}
|
|
1087
1257
|
unregisterCodexWebSocket(ws);
|
|
@@ -1090,8 +1260,24 @@ export function startServer(port?: number) {
|
|
|
1090
1260
|
ws.data.cancel?.(); // RC2: abort the upstream when the client disconnects
|
|
1091
1261
|
},
|
|
1092
1262
|
},
|
|
1093
|
-
|
|
1263
|
+
});
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
void nativeMainLifecycle.release();
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
1094
1268
|
|
|
1269
|
+
bindNativeMainStartupLifecycle(server, nativeMainLifecycle);
|
|
1270
|
+
const nativeStop = server.stop.bind(server);
|
|
1271
|
+
Object.defineProperty(server, "stop", {
|
|
1272
|
+
configurable: true,
|
|
1273
|
+
value: async (closeActiveConnections?: boolean): Promise<void> => {
|
|
1274
|
+
try {
|
|
1275
|
+
await nativeStop(closeActiveConnections);
|
|
1276
|
+
} finally {
|
|
1277
|
+
await releaseNativeMainStartupLifecycle(server);
|
|
1278
|
+
}
|
|
1279
|
+
},
|
|
1280
|
+
});
|
|
1095
1281
|
setServerRef(server);
|
|
1096
1282
|
const actualPort = server.port ?? listenPort;
|
|
1097
1283
|
setCorsOrigin(actualPort);
|