@iislee/opencodex 2.31.0 → 2.35.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.
- package/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
package/src/server/auth-cors.ts
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { formatErrorResponse } from "../bridge";
|
|
3
|
+
import {
|
|
4
|
+
codexAutoStartEnabled,
|
|
5
|
+
modelPreferHostedToolsConfigError,
|
|
6
|
+
providerModelCostsConfigError,
|
|
7
|
+
requestPacingConfigError,
|
|
8
|
+
retryOn429PolicyConfigError,
|
|
9
|
+
sanitizeModelCostsForDisplay,
|
|
10
|
+
} from "../config";
|
|
3
11
|
import {
|
|
4
12
|
apiKeyTransportConfigError,
|
|
5
13
|
booleanRecordConfigError,
|
|
6
14
|
modelAdapterRecordConfigError,
|
|
7
|
-
modelPreferHostedToolsConfigError,
|
|
8
|
-
codexAutoStartEnabled,
|
|
9
15
|
nonBlankStringArrayConfigError,
|
|
10
16
|
positiveIntegerConfigError,
|
|
11
17
|
positiveIntegerRecordConfigError,
|
|
12
18
|
providerBaseUrlConfigError,
|
|
13
19
|
providerHeadersConfigError,
|
|
14
|
-
providerModelCostsConfigError,
|
|
15
20
|
reasoningSummaryDeliveryRecordConfigError,
|
|
16
|
-
retryOn429PolicyConfigError,
|
|
17
|
-
requestPacingConfigError,
|
|
18
|
-
sanitizeModelCostsForDisplay,
|
|
19
21
|
upstreamHttpVersionConfigError,
|
|
20
|
-
} from "../config";
|
|
22
|
+
} from "../config/provider-validation";
|
|
21
23
|
import { providerDestinationConfigError } from "../lib/destination-policy";
|
|
22
24
|
import { redactSecretString } from "../lib/redact";
|
|
23
25
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
24
26
|
import { providerConfigSeed } from "../providers/derive";
|
|
25
27
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
26
28
|
import { openRouterRoutingConfigError } from "../providers/openrouter-routing";
|
|
29
|
+
import { modelAutoCompactTokenLimitsConfigError } from "../providers/auto-compact-budget";
|
|
27
30
|
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
28
31
|
import { xaiResponsesOptInState } from "../providers/xai-responses-opt-in";
|
|
29
32
|
|
|
@@ -563,6 +566,8 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
563
566
|
if (contextOverlayError) return contextOverlayError;
|
|
564
567
|
delete canonicalCandidate.contextWindow;
|
|
565
568
|
delete canonicalCandidate.modelContextWindows;
|
|
569
|
+
// User-owned soft compaction policy; it does not alter the canonical transport seed.
|
|
570
|
+
delete canonicalCandidate.modelAutoCompactTokenLimits;
|
|
566
571
|
const canonical = seed && sameCanonicalProviderSeed(canonicalCandidate, seed);
|
|
567
572
|
if (!canonical) {
|
|
568
573
|
return `provider ${name} must equal the canonical built-in provider seed`;
|
|
@@ -605,6 +610,13 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
605
610
|
if (apiKeyTransportError) return `provider ${name} ${apiKeyTransportError}`;
|
|
606
611
|
const maxInputError = positiveIntegerRecordConfigError(raw.modelMaxInputTokens, "modelMaxInputTokens");
|
|
607
612
|
if (maxInputError) return `provider ${name} ${maxInputError}`;
|
|
613
|
+
const autoCompactError = modelAutoCompactTokenLimitsConfigError(
|
|
614
|
+
raw.modelAutoCompactTokenLimits,
|
|
615
|
+
{ requireNativeIds: name === "openai" },
|
|
616
|
+
);
|
|
617
|
+
if (autoCompactError) {
|
|
618
|
+
return `provider ${JSON.stringify(redactSecretString(name))} ${autoCompactError}`;
|
|
619
|
+
}
|
|
608
620
|
const reasoningSummariesError = booleanRecordConfigError(raw.modelSupportsReasoningSummaries, "modelSupportsReasoningSummaries");
|
|
609
621
|
if (reasoningSummariesError) return `provider ${name} ${reasoningSummariesError}`;
|
|
610
622
|
const reasoningSummaryDeliveryError = reasoningSummaryDeliveryRecordConfigError(
|
|
@@ -694,6 +706,9 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
694
706
|
}
|
|
695
707
|
for (const key of [
|
|
696
708
|
"defaultModel",
|
|
709
|
+
"alias",
|
|
710
|
+
"modelAliases",
|
|
711
|
+
"defaultAliases",
|
|
697
712
|
"disabled",
|
|
698
713
|
"allowPrivateNetwork",
|
|
699
714
|
"authMode",
|
|
@@ -705,6 +720,7 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
705
720
|
"models",
|
|
706
721
|
"contextWindow",
|
|
707
722
|
"modelContextWindows",
|
|
723
|
+
"modelAutoCompactTokenLimits",
|
|
708
724
|
"defaultMaxOutputTokens",
|
|
709
725
|
"modelMaxOutputTokens",
|
|
710
726
|
"openRouterRouting",
|
|
@@ -744,8 +760,12 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
744
760
|
port: config.port,
|
|
745
761
|
hostname: config.hostname ?? "127.0.0.1",
|
|
746
762
|
defaultProvider: config.defaultProvider,
|
|
763
|
+
defaultModelAliases: config.defaultModelAliases,
|
|
747
764
|
codexAutoStart: codexAutoStartEnabled(config),
|
|
748
765
|
websockets: config.websockets,
|
|
766
|
+
// The GUI's browser-open toggle reads and writes this; absent means the
|
|
767
|
+
// historical auto-open behavior.
|
|
768
|
+
oauthOpenBrowser: config.oauthOpenBrowser !== false,
|
|
749
769
|
providers,
|
|
750
770
|
};
|
|
751
771
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
responsesJsonToChatCompletion,
|
|
19
19
|
responsesSseToChatCompletionsSse,
|
|
20
20
|
} from "../chat/outbound";
|
|
21
|
-
import { classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
|
|
21
|
+
import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
|
|
22
22
|
import { redactSecretString } from "../lib/redact";
|
|
23
23
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
24
24
|
import { estimateTokens } from "../lib/token-estimate";
|
|
@@ -120,6 +120,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
120
120
|
logCtx.model = route.modelId;
|
|
121
121
|
logCtx.providerAdapter = route.provider.adapter;
|
|
122
122
|
logCtx.requestedModel = requestedModel;
|
|
123
|
+
if (route.routeReason === "model-alias" || route.modelId !== requestedModel && requestedModel.includes("/")) logCtx.requestedAlias = requestedModel;
|
|
123
124
|
logCtx.provider = route.providerName;
|
|
124
125
|
logCtx.routeDecision = route.routeDecision;
|
|
125
126
|
settledRoute = route;
|
|
@@ -280,26 +281,26 @@ async function handleChatCompletionsWithBudget(
|
|
|
280
281
|
const parsed = JSON.parse(text) as {
|
|
281
282
|
error?: { message?: string; type?: string; code?: string | null } | string;
|
|
282
283
|
message?: string;
|
|
284
|
+
type?: string;
|
|
285
|
+
code?: string | null;
|
|
283
286
|
};
|
|
284
287
|
const nested = typeof parsed?.error === "object" && parsed.error ? parsed.error : undefined;
|
|
285
288
|
const flat = typeof parsed?.error === "string" ? parsed.error : parsed?.message;
|
|
286
289
|
const rawFallback = text
|
|
287
290
|
? `upstream error (${upstream.status}): ${redactSecretString(text).slice(0, 400)}`
|
|
288
291
|
: message;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
const upstreamMessage = nested?.message || flat;
|
|
293
|
+
message = upstreamMessage
|
|
294
|
+
? redactSecretString(upstreamMessage).slice(0, 500)
|
|
295
|
+
: rawFallback;
|
|
296
|
+
const structuredType = nested?.type ?? parsed.type;
|
|
297
|
+
const structuredCode = nested?.code ?? parsed.code;
|
|
298
|
+
if (typeof structuredType === "string") upstreamType = structuredType;
|
|
299
|
+
if (structuredCode === null || typeof structuredCode === "string") upstreamCode = structuredCode;
|
|
294
300
|
} catch {
|
|
295
301
|
if (text) message = `upstream error (${upstream.status}): ${redactSecretString(text).slice(0, 400)}`;
|
|
296
302
|
}
|
|
297
303
|
} catch { /* keep fallback */ }
|
|
298
|
-
const retryAfter = resolveClientRetryAfter({
|
|
299
|
-
status: upstream.status,
|
|
300
|
-
message,
|
|
301
|
-
upstreamRetryAfter: upstream.headers.get("retry-after"),
|
|
302
|
-
});
|
|
303
304
|
const classified = classifyError(
|
|
304
305
|
upstream.status,
|
|
305
306
|
upstreamType
|
|
@@ -309,9 +310,9 @@ async function handleChatCompletionsWithBudget(
|
|
|
309
310
|
: "invalid_request_error"),
|
|
310
311
|
message,
|
|
311
312
|
);
|
|
312
|
-
if (isCyberPolicyCode(upstreamCode)) {
|
|
313
|
+
if (isCyberPolicyCode(upstreamCode) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
313
314
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
314
|
-
classified.type =
|
|
315
|
+
classified.type = cyberPolicyErrorType(upstreamType);
|
|
315
316
|
} else if (upstreamCode === "model_not_found") {
|
|
316
317
|
// Structured model_not_found must win over classifyError's generic remaps.
|
|
317
318
|
classified.code = "model_not_found";
|
|
@@ -320,6 +321,13 @@ async function handleChatCompletionsWithBudget(
|
|
|
320
321
|
classified.code = upstreamCode;
|
|
321
322
|
}
|
|
322
323
|
const status = isCyberPolicyCode(classified.code) ? 400 : upstream.status;
|
|
324
|
+
const retryAfter = isCyberPolicyCode(classified.code)
|
|
325
|
+
? undefined
|
|
326
|
+
: resolveClientRetryAfter({
|
|
327
|
+
status: upstream.status,
|
|
328
|
+
message,
|
|
329
|
+
upstreamRetryAfter: upstream.headers.get("retry-after"),
|
|
330
|
+
});
|
|
323
331
|
const rewritten = new Response(JSON.stringify({
|
|
324
332
|
error: {
|
|
325
333
|
message: classified.message,
|
|
@@ -386,14 +394,14 @@ async function handleChatCompletionsWithBudget(
|
|
|
386
394
|
const status = (json as Rec)?.status;
|
|
387
395
|
if (status === "failed") {
|
|
388
396
|
const error = (json as { error?: { message?: string; type?: string; code?: string | null } }).error;
|
|
389
|
-
const message = error?.message ?? "upstream request failed";
|
|
397
|
+
const message = redactSecretString(error?.message ?? "upstream request failed");
|
|
390
398
|
const classified = classifyError(502, error?.type ?? "server_error", message);
|
|
391
399
|
if (error?.code === "translation_buffer_limit") {
|
|
392
400
|
classified.code = "translation_buffer_limit";
|
|
393
401
|
classified.type = "upstream_error";
|
|
394
|
-
} else if (isCyberPolicyCode(error?.code)) {
|
|
402
|
+
} else if (isCyberPolicyCode(error?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
395
403
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
396
|
-
classified.type =
|
|
404
|
+
classified.type = cyberPolicyErrorType(error?.type);
|
|
397
405
|
} else if (error?.code === "model_not_found") {
|
|
398
406
|
// Same deliberate preserve as the non-OK path: structured code beats generic classify.
|
|
399
407
|
classified.code = "model_not_found";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { chatCompletionsErrorBody } from "../chat/outbound";
|
|
2
|
-
import { classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
|
|
2
|
+
import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
|
|
3
3
|
import { redactSecretString } from "../lib/redact";
|
|
4
4
|
import {
|
|
5
5
|
isTranslatorBudgetExceededError,
|
|
@@ -226,9 +226,9 @@ export function nativeChatSse(
|
|
|
226
226
|
if (error) {
|
|
227
227
|
const status = error.status ?? 502;
|
|
228
228
|
const classified = classifyError(status, error.type ?? "upstream_error", error.message);
|
|
229
|
-
if (isCyberPolicyCode(error.code)) {
|
|
229
|
+
if (isCyberPolicyCode(error.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
230
230
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
231
|
-
classified.type =
|
|
231
|
+
classified.type = cyberPolicyErrorType(error.type);
|
|
232
232
|
} else if (error.code !== undefined && error.code !== null) {
|
|
233
233
|
classified.code = error.code;
|
|
234
234
|
}
|
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
collectChatCompletion,
|
|
7
7
|
isChatCompletionsStreamError,
|
|
8
8
|
} from "../chat/outbound";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
classifyError,
|
|
11
|
+
cyberPolicyErrorType,
|
|
12
|
+
CYBER_POLICY_ERROR_CODE,
|
|
13
|
+
isCyberPolicyCode,
|
|
14
|
+
isCyberPolicyMessage,
|
|
15
|
+
} from "../lib/errors";
|
|
10
16
|
import type { AdmissionLease } from "../lib/admission";
|
|
11
17
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
12
18
|
import { redactSecretString } from "../lib/redact";
|
|
@@ -281,13 +287,24 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
281
287
|
const detail = activeAdapter.formatErrorBody?.(response.status, response.headers, bodyText) ?? "";
|
|
282
288
|
let upstreamType: string | undefined;
|
|
283
289
|
let upstreamCode: string | null | undefined;
|
|
290
|
+
let upstreamMessage: string | undefined;
|
|
284
291
|
try {
|
|
285
292
|
const parsedError = JSON.parse(bodyText) as Rec;
|
|
286
293
|
const nested = isRec(parsedError.error) ? parsedError.error : undefined;
|
|
287
|
-
|
|
288
|
-
if (
|
|
294
|
+
const details = nested ?? parsedError;
|
|
295
|
+
if (typeof details.type === "string") upstreamType = details.type;
|
|
296
|
+
if (details.code === null || typeof details.code === "string") upstreamCode = details.code;
|
|
297
|
+
const rawMessage = typeof details.message === "string"
|
|
298
|
+
? details.message
|
|
299
|
+
: typeof parsedError.error === "string" ? parsedError.error : undefined;
|
|
300
|
+
if (rawMessage?.trim()) {
|
|
301
|
+
upstreamMessage = redactSecretString(rawMessage.trim());
|
|
302
|
+
}
|
|
289
303
|
} catch { /* keep generic classification */ }
|
|
290
|
-
const message =
|
|
304
|
+
const message = upstreamMessage
|
|
305
|
+
&& (isCyberPolicyCode(upstreamCode) || isCyberPolicyMessage(upstreamMessage))
|
|
306
|
+
? upstreamMessage
|
|
307
|
+
: detail ? `Provider error ${response.status}: ${detail}` : `Provider error ${response.status}`;
|
|
291
308
|
const classified = classifyError(
|
|
292
309
|
response.status,
|
|
293
310
|
upstreamType ?? (response.status === 401 ? "authentication_error"
|
|
@@ -295,9 +312,9 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
295
312
|
: response.status >= 500 ? "server_error" : "invalid_request_error"),
|
|
296
313
|
message,
|
|
297
314
|
);
|
|
298
|
-
if (isCyberPolicyCode(upstreamCode)) {
|
|
315
|
+
if (isCyberPolicyCode(upstreamCode) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
299
316
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
300
|
-
classified.type =
|
|
317
|
+
classified.type = cyberPolicyErrorType(upstreamType);
|
|
301
318
|
} else if (upstreamCode === "model_not_found") {
|
|
302
319
|
classified.code = "model_not_found";
|
|
303
320
|
classified.type = "invalid_request_error";
|
|
@@ -305,11 +322,13 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
305
322
|
classified.code = upstreamCode;
|
|
306
323
|
}
|
|
307
324
|
const status = isCyberPolicyCode(classified.code) ? 400 : response.status;
|
|
308
|
-
const retryAfter =
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
325
|
+
const retryAfter = isCyberPolicyCode(classified.code)
|
|
326
|
+
? undefined
|
|
327
|
+
: resolveClientRetryAfter({
|
|
328
|
+
status: response.status,
|
|
329
|
+
message: classified.message,
|
|
330
|
+
upstreamRetryAfter: response.headers.get("retry-after"),
|
|
331
|
+
});
|
|
313
332
|
finishLog(status, classified.message);
|
|
314
333
|
return new Response(JSON.stringify(chatCompletionsErrorBody(status, classified.message, classified.type, classified.code)), {
|
|
315
334
|
status,
|
|
@@ -553,7 +553,7 @@ export async function fetchWithHeaderDeadline(
|
|
|
553
553
|
): Promise<HeaderDeadlineFetchResult> {
|
|
554
554
|
const deadline = makeDeadline(timeoutMs, parent);
|
|
555
555
|
try {
|
|
556
|
-
const upstream = await fetchImpl(input, { ...init, signal: deadline.signal });
|
|
556
|
+
const upstream = await fetchImpl(input, { ...init, signal: deadline.signal, timeout: 0 });
|
|
557
557
|
return { kind: "response", upstream };
|
|
558
558
|
} catch (error) {
|
|
559
559
|
if (deadline.didExpire()) return { kind: "timeout" };
|
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { markActivity } from "../lib/sidecar-tracker";
|
|
2
|
+
import { knownModelIdsForProvider } from "../router";
|
|
2
3
|
import {
|
|
3
4
|
buildWarmupCompletionFrames,
|
|
4
5
|
buildWsErrorFrame,
|
|
@@ -22,10 +23,11 @@ import {
|
|
|
22
23
|
import { reconcileOAuthProviders } from "../oauth";
|
|
23
24
|
import { withCatalogWriteSerialization } from "../codex/catalog-write-serialization";
|
|
24
25
|
import { invalidateCodexModelsCacheWithPermit } from "../codex/catalog/sync";
|
|
25
|
-
import {
|
|
26
|
+
import { currentServiceHomes, serviceStatePathsForOpenCodexHome } from "../service";
|
|
26
27
|
import { shouldSyncCodexOnStart } from "../codex/desired-state";
|
|
27
28
|
import {
|
|
28
29
|
inspectNativeCodexOwnership,
|
|
30
|
+
type NativeCodexOwnership,
|
|
29
31
|
type OwnershipInspection,
|
|
30
32
|
} from "../integrations/native/ownership-preflight";
|
|
31
33
|
import { registerCodexCooldownRecoveryProbeWorker } from "../codex/auth-api";
|
|
@@ -108,6 +110,7 @@ import {
|
|
|
108
110
|
type RequestLogContext,
|
|
109
111
|
type RequestLogEntry,
|
|
110
112
|
} from "./request-log";
|
|
113
|
+
import { sessionLaneIdFromRequest } from "./request-log-conversation";
|
|
111
114
|
export {
|
|
112
115
|
addFinalRequestLog,
|
|
113
116
|
filterRequestLogs,
|
|
@@ -174,8 +177,8 @@ import { runClaudeAuthModeMigration } from "../claude/auth-mode-migration";
|
|
|
174
177
|
import {
|
|
175
178
|
bindNativeMainStartupLifecycle,
|
|
176
179
|
blockNativeMainStartupForUnownedServiceHome,
|
|
180
|
+
prepareNativeMainStartupLifecycle,
|
|
177
181
|
releaseNativeMainStartupLifecycle,
|
|
178
|
-
startNativeMainStartupLifecycle,
|
|
179
182
|
type NativeMainStartupGateDeps,
|
|
180
183
|
type NativeMainStartupLifecycle,
|
|
181
184
|
} from "../codex/native-profile-startup";
|
|
@@ -199,6 +202,11 @@ import {
|
|
|
199
202
|
import { SYSTEM_RESTART_CAPABILITY_VERSION } from "../lib/system-restart-contract";
|
|
200
203
|
import { LOCAL_PROVIDER_RELOAD_CAPABILITY_VERSION } from "../lib/local-provider-reload-contract";
|
|
201
204
|
import { createReadinessGate, type ReadinessGate } from "./readiness";
|
|
205
|
+
import {
|
|
206
|
+
createRuntimePackageTreeIntegrityGuard,
|
|
207
|
+
type PackageTreeIntegrityGuard,
|
|
208
|
+
} from "../lib/package-tree-integrity";
|
|
209
|
+
import { detectInstall } from "../update/index";
|
|
202
210
|
|
|
203
211
|
export const MAX_WS_FRAME_BYTES = 50 * 1024 * 1024;
|
|
204
212
|
const WEBSOCKET_IDLE_TIMEOUT_SECONDS = 0;
|
|
@@ -443,17 +451,34 @@ export interface StartServerDeps {
|
|
|
443
451
|
nativeMainStartup?: NativeMainStartupGateDeps;
|
|
444
452
|
/** Test-only ownership evidence; production inspects the installed service state. */
|
|
445
453
|
inspectNativeCodexOwnership?: typeof inspectNativeCodexOwnership;
|
|
454
|
+
/** Test-only service-home resolver; production resolves the current homes directly. */
|
|
455
|
+
resolveServiceHomes?: typeof currentServiceHomes;
|
|
446
456
|
/** Test-only seam for an upstream that cannot complete its WebSocket close handshake. */
|
|
447
457
|
liveSidebandWebSocketFactory?: LiveSidebandWebSocketFactory;
|
|
448
458
|
/** Test-only seam; production derives a fresh local-attestation secret per process. */
|
|
449
459
|
localAttestationSecret?: string;
|
|
450
460
|
/** Optional readiness gate; a fresh pending gate is created when omitted. */
|
|
451
461
|
readinessGate?: ReadinessGate;
|
|
462
|
+
/** Test-only package-tree observation; production captures package.json identity at boot. */
|
|
463
|
+
packageTreeIntegrity?: PackageTreeIntegrityGuard;
|
|
452
464
|
}
|
|
453
465
|
|
|
454
|
-
function inspectStartupOwnership(
|
|
466
|
+
function inspectStartupOwnership(
|
|
467
|
+
deps: StartServerDeps,
|
|
468
|
+
currentHomes: ReturnType<typeof currentServiceHomes> | null,
|
|
469
|
+
statePaths: readonly string[] | null,
|
|
470
|
+
): OwnershipInspection {
|
|
455
471
|
try {
|
|
456
|
-
|
|
472
|
+
if (currentHomes === null || statePaths === null) {
|
|
473
|
+
return {
|
|
474
|
+
ownership: "unknown",
|
|
475
|
+
reason: "startup service-home resolution failed",
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (deps.inspectNativeCodexOwnership) {
|
|
479
|
+
return deps.inspectNativeCodexOwnership({ currentHomes, statePaths });
|
|
480
|
+
}
|
|
481
|
+
return inspectNativeCodexOwnership({ currentHomes, statePaths });
|
|
457
482
|
} catch {
|
|
458
483
|
return {
|
|
459
484
|
ownership: "unknown",
|
|
@@ -540,15 +565,27 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
540
565
|
// journal, or credential path. Both positive foreign evidence and an unprovable
|
|
541
566
|
// ownership state are non-authority.
|
|
542
567
|
startupCacheInvalidationWrote = false;
|
|
543
|
-
const
|
|
568
|
+
const resolveServiceHomes = deps.resolveServiceHomes ?? currentServiceHomes;
|
|
569
|
+
let startupOwnershipHomes: ReturnType<typeof currentServiceHomes> | null = null;
|
|
570
|
+
let startupOwnershipStatePaths: readonly string[] | null = null;
|
|
571
|
+
try {
|
|
572
|
+
const homes = resolveServiceHomes();
|
|
573
|
+
const statePaths = serviceStatePathsForOpenCodexHome(homes.opencodexHome);
|
|
574
|
+
startupOwnershipHomes = homes;
|
|
575
|
+
startupOwnershipStatePaths = statePaths;
|
|
576
|
+
} catch { /* inspection below stays unknown */ }
|
|
577
|
+
const startupCacheOwnership = inspectStartupOwnership(
|
|
578
|
+
deps,
|
|
579
|
+
startupOwnershipHomes,
|
|
580
|
+
startupOwnershipStatePaths,
|
|
581
|
+
);
|
|
544
582
|
// Startup cache invalidation is best-effort and must never block the server from
|
|
545
|
-
// serving. It now takes K so it cannot race a convergence commit
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
if (startupCacheOwnership.ownership === "owned") {
|
|
583
|
+
// serving. It now takes K so it cannot race a convergence commit. Use the home
|
|
584
|
+
// paired with the ownership inspection; re-reading ambient CODEX_HOME here could
|
|
585
|
+
// invalidate a different installation after an environment or mount change.
|
|
586
|
+
if (startupCacheOwnership.ownership === "owned" && startupOwnershipHomes !== null) {
|
|
550
587
|
try {
|
|
551
|
-
const startupCodexHome =
|
|
588
|
+
const startupCodexHome = startupOwnershipHomes.codexHome;
|
|
552
589
|
// #1046: record whether this actually rewrote the cache. `handleStart` ORs this
|
|
553
590
|
// with the later startup sync and warns ONCE about stale app-servers; warning
|
|
554
591
|
// here instead would read a catalog mtime the sync is about to move.
|
|
@@ -668,12 +705,21 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
668
705
|
}), req, policy);
|
|
669
706
|
}
|
|
670
707
|
|
|
708
|
+
function packageTreeChangedResponse(req: Request, policy: RequestPolicyView, message: string): Response {
|
|
709
|
+
return withCors(new Response(JSON.stringify({
|
|
710
|
+
error: { type: "server_error", code: "package_tree_changed", message },
|
|
711
|
+
}), {
|
|
712
|
+
status: 503,
|
|
713
|
+
headers: { "Content-Type": "application/json" },
|
|
714
|
+
}), req, policy);
|
|
715
|
+
}
|
|
716
|
+
|
|
671
717
|
async function runAdmittedHttpTurn(
|
|
672
718
|
req: Request,
|
|
673
719
|
policy: RequestPolicyView,
|
|
674
720
|
work: (lease: ActiveTurnLease) => Promise<Response>,
|
|
675
721
|
): Promise<Response> {
|
|
676
|
-
const lease = tryAdmitTurn();
|
|
722
|
+
const lease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers));
|
|
677
723
|
if (!lease) return serverBusyResponse(req, "active turns", policy);
|
|
678
724
|
let response: Response;
|
|
679
725
|
try {
|
|
@@ -694,6 +740,8 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
694
740
|
// passes it in, and transitions it after the post-startup sync settles. When
|
|
695
741
|
// no gate is supplied (tests, ad-hoc starts) a fresh pending gate is created.
|
|
696
742
|
const readinessGate = deps.readinessGate ?? createReadinessGate();
|
|
743
|
+
const packageTreeIntegrity = deps.packageTreeIntegrity
|
|
744
|
+
?? createRuntimePackageTreeIntegrityGuard(detectInstall());
|
|
697
745
|
// Actual bound port, filled in after Bun.serve binds so /readyz reports the
|
|
698
746
|
// real ephemeral port for startServer(0). /healthz keeps its existing port
|
|
699
747
|
// field (the requested listenPort) byte-for-byte.
|
|
@@ -705,18 +753,71 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
705
753
|
// clients; no Codex request can use this lifecycle in that state.
|
|
706
754
|
// Re-probe here instead of trusting the earlier cache decision: startup work
|
|
707
755
|
// between the two sites must not widen the service-install race.
|
|
708
|
-
const nativeOwnership = inspectStartupOwnership(deps);
|
|
756
|
+
const nativeOwnership = inspectStartupOwnership(deps, startupOwnershipHomes, startupOwnershipStatePaths);
|
|
757
|
+
const preparedNativeMainLifecycle = nativeOwnership.ownership !== "foreign"
|
|
758
|
+
&& startupOwnershipHomes !== null
|
|
759
|
+
? prepareNativeMainStartupLifecycle(
|
|
760
|
+
deps.nativeMainStartup,
|
|
761
|
+
{ codexHome: startupOwnershipHomes.codexHome, configDir: startupOwnershipHomes.opencodexHome },
|
|
762
|
+
)
|
|
763
|
+
: null;
|
|
764
|
+
let retryOwnershipHomes = startupOwnershipHomes;
|
|
765
|
+
let retryOwnershipStatePaths = startupOwnershipStatePaths;
|
|
766
|
+
let retryPreparedNativeMainLifecycle = preparedNativeMainLifecycle;
|
|
767
|
+
const reprobeNativeOwnership = (): NativeCodexOwnership => {
|
|
768
|
+
// If startup could not resolve the homes at all, preserve a bounded retry
|
|
769
|
+
// without guessing an authority. The first successful resolution is pinned
|
|
770
|
+
// together with its service-state paths before ownership is inspected.
|
|
771
|
+
if (retryOwnershipHomes === null || retryOwnershipStatePaths === null) {
|
|
772
|
+
try {
|
|
773
|
+
const homes = resolveServiceHomes();
|
|
774
|
+
const statePaths = serviceStatePathsForOpenCodexHome(homes.opencodexHome);
|
|
775
|
+
retryOwnershipHomes = homes;
|
|
776
|
+
retryOwnershipStatePaths = statePaths;
|
|
777
|
+
} catch {
|
|
778
|
+
return "unknown";
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const homes = retryOwnershipHomes;
|
|
782
|
+
const statePaths = retryOwnershipStatePaths;
|
|
783
|
+
const answer = inspectStartupOwnership(deps, homes, statePaths).ownership;
|
|
784
|
+
if (answer !== "owned") return answer;
|
|
785
|
+
retryPreparedNativeMainLifecycle ??= prepareNativeMainStartupLifecycle(
|
|
786
|
+
deps.nativeMainStartup,
|
|
787
|
+
{ codexHome: homes.codexHome, configDir: homes.opencodexHome },
|
|
788
|
+
);
|
|
789
|
+
// An ownership verdict without a lifecycle bound to that same home is not
|
|
790
|
+
// enough to reopen native-main admission.
|
|
791
|
+
return retryPreparedNativeMainLifecycle ? "owned" : "unknown";
|
|
792
|
+
};
|
|
793
|
+
const ownershipRetryOptions = {
|
|
794
|
+
reprobe: reprobeNativeOwnership,
|
|
795
|
+
expectedHomeId: () => retryPreparedNativeMainLifecycle?.homeId ?? null,
|
|
796
|
+
startOwnedLifecycle: () => {
|
|
797
|
+
if (!retryPreparedNativeMainLifecycle) {
|
|
798
|
+
throw new Error("Native-main ownership became known before its startup lifecycle was prepared.");
|
|
799
|
+
}
|
|
800
|
+
return retryPreparedNativeMainLifecycle.start();
|
|
801
|
+
},
|
|
802
|
+
};
|
|
709
803
|
const nativeMainLifecycle: NativeMainStartupLifecycle = shouldSyncCodexOnStart(config)
|
|
710
804
|
? nativeOwnership.ownership === "owned"
|
|
711
|
-
?
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
805
|
+
? preparedNativeMainLifecycle
|
|
806
|
+
? preparedNativeMainLifecycle.start()
|
|
807
|
+
: blockNativeMainStartupForUnownedServiceHome(
|
|
808
|
+
"ownership-unknown",
|
|
809
|
+
ownershipRetryOptions,
|
|
810
|
+
)
|
|
811
|
+
: nativeOwnership.ownership === "foreign"
|
|
812
|
+
? blockNativeMainStartupForUnownedServiceHome("foreign-ownership")
|
|
813
|
+
: blockNativeMainStartupForUnownedServiceHome(
|
|
814
|
+
"ownership-unknown",
|
|
815
|
+
// #2108: an `unknown` verdict means the probe could not answer, not that this host
|
|
816
|
+
// is unownable. Hand the fence a way to re-ask so a host that becomes answerable
|
|
817
|
+
// after boot reopens on its own instead of needing `ocx restart`. A `foreign`
|
|
818
|
+
// verdict ignores this by design — that one is a fact, not a question.
|
|
819
|
+
ownershipRetryOptions,
|
|
820
|
+
)
|
|
720
821
|
: {
|
|
721
822
|
homeId: null,
|
|
722
823
|
settled: Promise.resolve({ status: "ready", homeId: null }),
|
|
@@ -767,6 +868,29 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
767
868
|
if (decoded === "/readyz" || decoded === "/readyz/") readyzPath = decoded;
|
|
768
869
|
} catch { /* malformed encoding — not a readiness path */ }
|
|
769
870
|
|
|
871
|
+
const packageTreeStatus = packageTreeIntegrity.status();
|
|
872
|
+
if (!packageTreeStatus.ok && (
|
|
873
|
+
url.pathname === "/healthz"
|
|
874
|
+
|| readyzPath !== undefined
|
|
875
|
+
|| url.pathname.startsWith("/v1/")
|
|
876
|
+
)) {
|
|
877
|
+
const message = "OpenCodex package files changed while this proxy was running; restart OpenCodex before retrying.";
|
|
878
|
+
const response = url.pathname === "/healthz" || readyzPath !== undefined
|
|
879
|
+
? jsonResponse({
|
|
880
|
+
status: "restart_required",
|
|
881
|
+
service: "opencodex",
|
|
882
|
+
version: VERSION,
|
|
883
|
+
uptime: process.uptime(),
|
|
884
|
+
pid: process.pid,
|
|
885
|
+
port: boundPort ?? requestServer.port ?? listenPort,
|
|
886
|
+
error: { code: "package_tree_changed", message },
|
|
887
|
+
}, 503, req, policy)
|
|
888
|
+
: packageTreeChangedResponse(req, policy, message);
|
|
889
|
+
const headers = new Headers(response.headers);
|
|
890
|
+
headers.set("Retry-After", "5");
|
|
891
|
+
return new Response(response.body, { status: 503, headers });
|
|
892
|
+
}
|
|
893
|
+
|
|
770
894
|
if (req.method === "OPTIONS") {
|
|
771
895
|
// /readyz is exact-GET only; OPTIONS (like POST and the trailing-slash
|
|
772
896
|
// path) must answer the deterministic JSON 404, never the generic 204
|
|
@@ -815,7 +939,12 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
815
939
|
// unauthenticated loopback listener (#1102) is a second Bun.serve, and handing its
|
|
816
940
|
// request to the public server's upgrade would fail or cross sockets.
|
|
817
941
|
if (requestServer.upgrade(req, {
|
|
818
|
-
data: buildResponsesWsData(
|
|
942
|
+
data: buildResponsesWsData(
|
|
943
|
+
selectForwardHeaders(req.headers),
|
|
944
|
+
admission,
|
|
945
|
+
websocketLease,
|
|
946
|
+
sessionLaneIdFromRequest(req.headers),
|
|
947
|
+
),
|
|
819
948
|
})) return undefined as unknown as Response;
|
|
820
949
|
websocketLease.release();
|
|
821
950
|
return withCors(formatErrorResponse(426, "upgrade_required", "WebSocket upgrade failed"), req, policy);
|
|
@@ -946,6 +1075,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
946
1075
|
: [];
|
|
947
1076
|
const disabledNatives = disabledNativeSlugs(config);
|
|
948
1077
|
const disabledModels = new Set(config.disabledModels ?? []);
|
|
1078
|
+
const exactComboSlugs = exactComboCatalogSlugs(config);
|
|
949
1079
|
const shadowedNativeSlugs = configuredNativeAliasSlugs(config);
|
|
950
1080
|
const suppressedBareNativeSlugs = new Set([
|
|
951
1081
|
...desktopAllowlistSuppressedNativeSlugs(config),
|
|
@@ -1031,7 +1161,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1031
1161
|
config.subagentModels,
|
|
1032
1162
|
websocketsEnabled(config),
|
|
1033
1163
|
maMode as "v1" | "default" | "v2",
|
|
1034
|
-
|
|
1164
|
+
exactComboSlugs,
|
|
1035
1165
|
accountSelectors,
|
|
1036
1166
|
suppressedBareNativeSlugs,
|
|
1037
1167
|
new Set(),
|
|
@@ -1108,12 +1238,29 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1108
1238
|
const data = [
|
|
1109
1239
|
...visibleNatives.map(id => nativeModelRow(id)),
|
|
1110
1240
|
...visibleAccountNatives.map(({ id, metadataId }) => nativeModelRow(id, metadataId)),
|
|
1111
|
-
...uniqueCatalogModelsForRawPublicList(goOrdered).map(m =>
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1241
|
+
...await Promise.all(uniqueCatalogModelsForRawPublicList(goOrdered).map(async m => {
|
|
1242
|
+
const publicId = m.alias ?? `${m.provider}/${m.id}`;
|
|
1243
|
+
const isCombo = m.provider === "combo" && exactComboSlugs.has(publicId);
|
|
1244
|
+
const provider = config.providers[m.provider];
|
|
1245
|
+
const effective = provider
|
|
1246
|
+
? (await import("../providers/default-aliases")).effectiveModelAliases(
|
|
1247
|
+
config,
|
|
1248
|
+
provider,
|
|
1249
|
+
knownModelIdsForProvider(m.provider, provider, config),
|
|
1250
|
+
).get(m.id)
|
|
1251
|
+
: undefined;
|
|
1252
|
+
return {
|
|
1253
|
+
id: publicId,
|
|
1254
|
+
object: "model",
|
|
1255
|
+
created: 0,
|
|
1256
|
+
// This endpoint is an OpenAI-compatible inbound contract. Some clients use
|
|
1257
|
+
// owned_by as an adapter selector, so a virtual combo must name that wire
|
|
1258
|
+
// adapter rather than the internal catalog authority marker.
|
|
1259
|
+
owned_by: isCombo ? "openai" : (m.owned_by ?? m.provider),
|
|
1260
|
+
...(isCombo ? { is_combo: true } : {}),
|
|
1261
|
+
...(effective ? { alias_of: `${provider?.alias || m.provider}/${effective.alias}` } : {}),
|
|
1262
|
+
...grokEffortFields(m.reasoningEfforts ?? [], m.defaultReasoningEffort),
|
|
1263
|
+
};
|
|
1117
1264
|
})),
|
|
1118
1265
|
];
|
|
1119
1266
|
return jsonResponse({ object: "list", data }, 200, req, policy);
|
|
@@ -1420,7 +1567,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1420
1567
|
provider: "unknown",
|
|
1421
1568
|
...admissionFields(admission),
|
|
1422
1569
|
};
|
|
1423
|
-
const turnAdmissionLease = tryAdmitTurn();
|
|
1570
|
+
const turnAdmissionLease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers));
|
|
1424
1571
|
if (!turnAdmissionLease) return serverBusyResponse(req, "active turns", policy);
|
|
1425
1572
|
let resolved;
|
|
1426
1573
|
try {
|
|
@@ -1572,7 +1719,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1572
1719
|
return;
|
|
1573
1720
|
}
|
|
1574
1721
|
|
|
1575
|
-
const turnAdmissionLease = tryAdmitTurn();
|
|
1722
|
+
const turnAdmissionLease = tryAdmitTurn(ws.data.sessionLaneId);
|
|
1576
1723
|
if (!turnAdmissionLease) {
|
|
1577
1724
|
sendJsonFrame(ws, buildWsErrorFrame(503, {
|
|
1578
1725
|
type: "server_error",
|