@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/auth-cors.ts
CHANGED
|
@@ -4,14 +4,17 @@ import {
|
|
|
4
4
|
apiKeyTransportConfigError,
|
|
5
5
|
booleanRecordConfigError,
|
|
6
6
|
modelAdapterRecordConfigError,
|
|
7
|
+
modelPreferHostedToolsConfigError,
|
|
7
8
|
codexAutoStartEnabled,
|
|
8
9
|
positiveIntegerConfigError,
|
|
9
10
|
positiveIntegerRecordConfigError,
|
|
10
11
|
providerBaseUrlConfigError,
|
|
11
12
|
providerHeadersConfigError,
|
|
12
13
|
reasoningSummaryDeliveryRecordConfigError,
|
|
14
|
+
retryOn429PolicyConfigError,
|
|
13
15
|
} from "../config";
|
|
14
16
|
import { providerDestinationConfigError } from "../lib/destination-policy";
|
|
17
|
+
import { redactSecretString } from "../lib/redact";
|
|
15
18
|
import { getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
16
19
|
import { providerConfigSeed } from "../providers/derive";
|
|
17
20
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -80,15 +83,28 @@ export function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean
|
|
|
80
83
|
|
|
81
84
|
function isExtraAllowedOrigin(origin: string, cfg: OcxConfig): boolean {
|
|
82
85
|
if (!cfg.corsAllowOrigins?.length) return false;
|
|
86
|
+
const parsedOrigin = comparableOrigin(origin);
|
|
83
87
|
return cfg.corsAllowOrigins.some(allowed => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
88
|
+
const parsedAllowed = comparableOrigin(allowed);
|
|
89
|
+
return parsedOrigin !== null && parsedAllowed !== null
|
|
90
|
+
? parsedAllowed === parsedOrigin
|
|
91
|
+
: allowed === origin;
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
function comparableOrigin(value: string): string | null {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = new URL(value);
|
|
98
|
+
if (parsed.origin !== "null") return parsed.origin;
|
|
99
|
+
// WHATWG URL exposes authority-based custom schemes (for example browser
|
|
100
|
+
// extensions) as opaque `null` origins. Compare their scheme + authority so
|
|
101
|
+
// one allowlisted extension cannot admit every other opaque origin.
|
|
102
|
+
return parsed.host ? `${parsed.protocol}//${parsed.host}` : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
export function managementRequestOrigin(req: Request, config: OcxConfig): string | null {
|
|
93
109
|
const host = req.headers.get("Host");
|
|
94
110
|
const parsedHost = parseHttpHost(host);
|
|
@@ -375,6 +391,11 @@ function sameCanonicalProviderSeed(actual: Record<string, unknown>, expected: Oc
|
|
|
375
391
|
return actualKeys.every(key => JSON.stringify(actual[key]) === JSON.stringify((expected as unknown as Record<string, unknown>)[key]));
|
|
376
392
|
}
|
|
377
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Validate a provider object arriving at the management write boundary. Returns an error
|
|
396
|
+
* string, or null when the provider may be persisted. Caller-controlled names/fields are
|
|
397
|
+
* redacted and JSON-escaped so secrets never reach the response.
|
|
398
|
+
*/
|
|
378
399
|
export function providerManagementConfigError(name: unknown, provider: unknown): string | null {
|
|
379
400
|
if (typeof name !== "string" || !provider || typeof provider !== "object" || Array.isArray(provider)) {
|
|
380
401
|
return "provider must be a plain object";
|
|
@@ -392,7 +413,9 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
392
413
|
return "provider openai codexAccountMode must be pool or direct";
|
|
393
414
|
}
|
|
394
415
|
if (seed) seed.codexAccountMode = raw.codexAccountMode;
|
|
395
|
-
const
|
|
416
|
+
const canonicalCandidate = { ...raw };
|
|
417
|
+
delete canonicalCandidate.responsesSnapshotRepair;
|
|
418
|
+
const canonical = seed && sameCanonicalProviderSeed(canonicalCandidate, seed);
|
|
396
419
|
if (!canonical) {
|
|
397
420
|
return `provider ${name} must equal the canonical built-in provider seed`;
|
|
398
421
|
}
|
|
@@ -406,6 +429,12 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
406
429
|
if (destinationError) return `provider ${name} ${destinationError}`;
|
|
407
430
|
const headersError = providerHeadersConfigError(typed.headers);
|
|
408
431
|
if (headersError) return `provider ${name} ${headersError}`;
|
|
432
|
+
const retryOn429Error = retryOn429PolicyConfigError(raw.retryOn429);
|
|
433
|
+
if (retryOn429Error) {
|
|
434
|
+
// The provider name is caller-controlled and can be token-shaped; redact and JSON-escape
|
|
435
|
+
// it before it reaches the management API response.
|
|
436
|
+
return `provider ${JSON.stringify(redactSecretString(name))} ${retryOn429Error}`;
|
|
437
|
+
}
|
|
409
438
|
const apiKeyTransportError = apiKeyTransportConfigError(typed);
|
|
410
439
|
if (apiKeyTransportError) return `provider ${name} ${apiKeyTransportError}`;
|
|
411
440
|
const maxInputError = positiveIntegerRecordConfigError(raw.modelMaxInputTokens, "modelMaxInputTokens");
|
|
@@ -419,6 +448,16 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
419
448
|
if (reasoningSummaryDeliveryError) return `provider ${name} ${reasoningSummaryDeliveryError}`;
|
|
420
449
|
const modelAdaptersError = modelAdapterRecordConfigError(raw.modelAdapters, "modelAdapters", name, typed);
|
|
421
450
|
if (modelAdaptersError) return `provider ${name} ${modelAdaptersError}`;
|
|
451
|
+
const preferHostedToolsError = modelPreferHostedToolsConfigError(
|
|
452
|
+
raw.modelPreferHostedTools,
|
|
453
|
+
"modelPreferHostedTools",
|
|
454
|
+
name,
|
|
455
|
+
typed,
|
|
456
|
+
);
|
|
457
|
+
if (preferHostedToolsError) return `provider ${name} ${preferHostedToolsError}`;
|
|
458
|
+
if (raw.responsesSnapshotRepair !== undefined && typeof raw.responsesSnapshotRepair !== "boolean") {
|
|
459
|
+
return `provider ${name} responsesSnapshotRepair must be a boolean`;
|
|
460
|
+
}
|
|
422
461
|
const defaultMaxOutputError = positiveIntegerConfigError(raw.defaultMaxOutputTokens, "defaultMaxOutputTokens");
|
|
423
462
|
if (defaultMaxOutputError) return `provider ${name} ${defaultMaxOutputError}`;
|
|
424
463
|
const maxOutputError = positiveIntegerRecordConfigError(raw.modelMaxOutputTokens, "modelMaxOutputTokens");
|
|
@@ -496,6 +535,7 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
496
535
|
"modelOpenRouterRouting",
|
|
497
536
|
"reasoningEfforts",
|
|
498
537
|
"modelReasoningEfforts",
|
|
538
|
+
"reasoningWireFormat",
|
|
499
539
|
"noVisionModels",
|
|
500
540
|
"noReasoningModels",
|
|
501
541
|
"noTemperatureModels",
|
|
@@ -18,7 +18,8 @@ import { classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../li
|
|
|
18
18
|
import { redactSecretString } from "../lib/redact";
|
|
19
19
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
20
20
|
import { estimateTokens } from "../lib/token-estimate";
|
|
21
|
-
import { routeModel } from "../router";
|
|
21
|
+
import { NoEligiblePolicyCandidateError, routeModel } from "../router";
|
|
22
|
+
import { evidenceFromBody } from "../routing/request-evidence";
|
|
22
23
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
23
24
|
import type { OcxConfig } from "../types";
|
|
24
25
|
import { readJsonRequestBody } from "./request-decompress";
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
import { responseWithDeferredRequestLog } from "./relay";
|
|
33
34
|
import { handleResponses } from "./responses";
|
|
34
35
|
import type { AdmissionLease } from "../lib/admission";
|
|
36
|
+
import { tryClaimNativeMainProfileForTurn } from "../codex/native-main-admission";
|
|
35
37
|
import {
|
|
36
38
|
createTranslatorBudget,
|
|
37
39
|
finalizeTranslatorBudgetResponse,
|
|
@@ -109,7 +111,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
109
111
|
let nativeRoute = false;
|
|
110
112
|
let directRoute = false;
|
|
111
113
|
try {
|
|
112
|
-
const route = routeModel(config, internalBody.model as string);
|
|
114
|
+
const route = routeModel(config, internalBody.model as string, evidenceFromBody(internalBody));
|
|
113
115
|
// Settle the wire once so every branch below reads the adapter this model will
|
|
114
116
|
// actually use, not the provider-wide default (#404).
|
|
115
117
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "chat");
|
|
@@ -117,6 +119,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
117
119
|
logCtx.providerAdapter = route.provider.adapter;
|
|
118
120
|
logCtx.requestedModel = requestedModel;
|
|
119
121
|
logCtx.provider = route.providerName;
|
|
122
|
+
logCtx.routeDecision = route.routeDecision;
|
|
120
123
|
if (route.provider.adapter === "openai-responses") {
|
|
121
124
|
nativeRoute = true;
|
|
122
125
|
directRoute = route.codexAccountMode === "direct";
|
|
@@ -146,7 +149,12 @@ async function handleChatCompletionsWithBudget(
|
|
|
146
149
|
const ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
147
150
|
if (ladder !== undefined && ladder.length === 0) delete internalBody.reasoning;
|
|
148
151
|
}
|
|
149
|
-
} catch {
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
154
|
+
logCtx.routeDecision = err.trace;
|
|
155
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
156
|
+
return chatCompletionsErrorResponse(404, err.message, "invalid_request_error");
|
|
157
|
+
}
|
|
150
158
|
/* unknown model: let handleResponses shape the 404 */
|
|
151
159
|
}
|
|
152
160
|
void nativeRoute;
|
|
@@ -158,15 +166,22 @@ async function handleChatCompletionsWithBudget(
|
|
|
158
166
|
if (value) headers.set(name, value);
|
|
159
167
|
}
|
|
160
168
|
// Prefer main ChatGPT auth so OpenAI-backed sidecars remain reachable on routed turns.
|
|
161
|
-
if (!directRoute)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
if (!directRoute) {
|
|
170
|
+
// This enrichment is optional for routed/non-main providers. If native main
|
|
171
|
+
// is fenced, omit it and let auth-context reject only a final physical-main
|
|
172
|
+
// selection while healthy pool/provider routes continue.
|
|
173
|
+
if (tryClaimNativeMainProfileForTurn(logIds?.turnAdmissionLease)) {
|
|
174
|
+
try {
|
|
175
|
+
const { getMainAccountToken } = await import("../codex/main-account");
|
|
176
|
+
const token = getMainAccountToken();
|
|
177
|
+
if (token) {
|
|
178
|
+
headers.set("authorization", `Bearer ${token.accessToken}`);
|
|
179
|
+
headers.set("chatgpt-account-id", token.chatgptAccountId);
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
/* optional */
|
|
183
|
+
}
|
|
167
184
|
}
|
|
168
|
-
} catch {
|
|
169
|
-
/* optional */
|
|
170
185
|
}
|
|
171
186
|
|
|
172
187
|
const internalBodyJson = JSON.stringify(internalBody);
|
|
@@ -316,7 +331,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
316
331
|
const classified = classifyError(502, error?.type ?? "server_error", message);
|
|
317
332
|
if (error?.code === "translation_buffer_limit") {
|
|
318
333
|
classified.code = "translation_buffer_limit";
|
|
319
|
-
classified.type = "
|
|
334
|
+
classified.type = "upstream_error";
|
|
320
335
|
} else if (isCyberPolicyCode(error?.code)) {
|
|
321
336
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
322
337
|
classified.type = "invalid_request_error";
|
|
@@ -327,7 +342,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
327
342
|
}
|
|
328
343
|
return chatCompletionsErrorResponse(
|
|
329
344
|
classified.code === "translation_buffer_limit"
|
|
330
|
-
?
|
|
345
|
+
? 502
|
|
331
346
|
: isCyberPolicyCode(classified.code) ? 400 : 502,
|
|
332
347
|
message,
|
|
333
348
|
classified.type,
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
} from "../claude/outbound";
|
|
26
26
|
import { clearableDeadline, idleDeadline } from "../lib/abort";
|
|
27
27
|
import { estimateTokens } from "../lib/token-estimate";
|
|
28
|
-
import { routeModel } from "../router";
|
|
28
|
+
import { NoEligiblePolicyCandidateError, routeModel } from "../router";
|
|
29
|
+
import { evidenceFromBody } from "../routing/request-evidence";
|
|
29
30
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
30
31
|
import type { OcxConfig } from "../types";
|
|
31
32
|
import { readJsonRequestBody } from "./request-decompress";
|
|
@@ -34,6 +35,7 @@ import { conversationIdFromClaudeMetadata } from "./request-log-conversation";
|
|
|
34
35
|
import { responseWithDeferredRequestLog } from "./relay";
|
|
35
36
|
import { handleResponses } from "./responses";
|
|
36
37
|
import type { AdmissionLease } from "../lib/admission";
|
|
38
|
+
import { tryClaimNativeMainProfileForTurn } from "../codex/native-main-admission";
|
|
37
39
|
import {
|
|
38
40
|
createTranslatorBudget,
|
|
39
41
|
finalizeTranslatorBudgetResponse,
|
|
@@ -627,10 +629,11 @@ async function handleClaudeMessagesWithBudget(
|
|
|
627
629
|
// verified live 2026-07-11). Strip them for that route; routed providers keep them.
|
|
628
630
|
let nativeRoute = false;
|
|
629
631
|
try {
|
|
630
|
-
const route = routeModel(config, internalBody.model as string);
|
|
632
|
+
const route = routeModel(config, internalBody.model as string, evidenceFromBody(internalBody));
|
|
631
633
|
// Settle the wire once so the sampling decision below reads the effective
|
|
632
634
|
// adapter rather than the provider-wide default (#404).
|
|
633
635
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic");
|
|
636
|
+
logCtx.routeDecision = route.routeDecision;
|
|
634
637
|
if (route.provider.adapter === "openai-responses") {
|
|
635
638
|
nativeRoute = true;
|
|
636
639
|
delete internalBody.max_output_tokens;
|
|
@@ -661,7 +664,14 @@ async function handleClaudeMessagesWithBudget(
|
|
|
661
664
|
const ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
662
665
|
if (ladder !== undefined && ladder.length === 0) delete internalBody.reasoning;
|
|
663
666
|
}
|
|
664
|
-
} catch {
|
|
667
|
+
} catch (err) {
|
|
668
|
+
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
669
|
+
logCtx.routeDecision = err.trace;
|
|
670
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
671
|
+
return anthropicErrorResponse(404, err.message, "invalid_request_error");
|
|
672
|
+
}
|
|
673
|
+
/* unknown model: let handleResponses shape the 404 */
|
|
674
|
+
}
|
|
665
675
|
|
|
666
676
|
const headers = new Headers({ "content-type": "application/json" });
|
|
667
677
|
for (const name of FORWARD_HEADERS) {
|
|
@@ -671,8 +681,11 @@ async function handleClaudeMessagesWithBudget(
|
|
|
671
681
|
const value = req.headers.get(name);
|
|
672
682
|
if (value) headers.set(name, value);
|
|
673
683
|
}
|
|
674
|
-
|
|
675
|
-
|
|
684
|
+
// Routed replays need main ChatGPT auth so OpenAI-backed sidecars remain reachable;
|
|
685
|
+
// native replays have no caller ChatGPT credential. This enrichment is optional:
|
|
686
|
+
// auth-context later rejects a real physical-main selection, while routed/pool
|
|
687
|
+
// traffic continues without reading native credentials during a fence/recovery.
|
|
688
|
+
if (tryClaimNativeMainProfileForTurn(logIds?.turnAdmissionLease)) {
|
|
676
689
|
const { getMainAccountToken } = await import("../codex/main-account");
|
|
677
690
|
const token = getMainAccountToken();
|
|
678
691
|
if (token) {
|
|
@@ -681,14 +694,6 @@ async function handleClaudeMessagesWithBudget(
|
|
|
681
694
|
}
|
|
682
695
|
}
|
|
683
696
|
if (nativeRoute) {
|
|
684
|
-
// No forwarded ChatGPT auth exists on this surface. Attach the main codex login
|
|
685
|
-
// (read-only auth.json token); account-pool rotation still overrides downstream.
|
|
686
|
-
const { getMainAccountToken } = await import("../codex/main-account");
|
|
687
|
-
const token = getMainAccountToken();
|
|
688
|
-
if (token) {
|
|
689
|
-
headers.set("authorization", `Bearer ${token.accessToken}`);
|
|
690
|
-
headers.set("chatgpt-account-id", token.chatgptAccountId);
|
|
691
|
-
}
|
|
692
697
|
// ChatGPT-backend prompt-cache affinity rides the session_id HEADER (codex
|
|
693
698
|
// clients always send their session uuid; devlog 090 follow-up: body-level
|
|
694
699
|
// prompt_cache_key alone still yielded cached_tokens:0). Claude Code never sends
|
|
@@ -763,8 +768,11 @@ async function handleClaudeMessagesWithBudget(
|
|
|
763
768
|
// rewrite): log = upstream truth, client = retry signal.
|
|
764
769
|
// Retryable 429s also get Retry-After (#507) so Codex-shaped clients and Claude Code
|
|
765
770
|
// share a backoff hint when the upstream omitted the header.
|
|
766
|
-
const
|
|
767
|
-
|
|
771
|
+
const nativeMainFence = response.status === 503
|
|
772
|
+
&& upstreamRetryAfter?.trim() === "1"
|
|
773
|
+
&& message === "Native Codex main profile is switching; retry this request";
|
|
774
|
+
const transient = !nativeMainFence && isTransientUpstreamStatus(response.status);
|
|
775
|
+
const outStatus = nativeMainFence ? 503 : transient ? 529 : response.status;
|
|
768
776
|
const out = new Response(JSON.stringify(anthropicErrorBody(outStatus, message)), {
|
|
769
777
|
status: outStatus,
|
|
770
778
|
headers: {
|
package/src/server/gui-static.ts
CHANGED
|
@@ -56,16 +56,25 @@ function isFile(path: string): boolean {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
/** HTML-attribute escape for values interpolated into meta tags. */
|
|
60
|
+
function escapeHtmlAttribute(value: string): string {
|
|
61
|
+
return value
|
|
62
|
+
.replaceAll("&", "&")
|
|
63
|
+
.replaceAll('"', """)
|
|
64
|
+
.replaceAll("<", "<")
|
|
65
|
+
.replaceAll(">", ">");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Shared session meta-tag block, escaped for quoted attribute interpolation. */
|
|
69
|
+
function sessionBootstrapMeta(session: GuiSessionBootstrap): string {
|
|
70
|
+
return [
|
|
71
|
+
`<meta name="opencodex-session-token" content="${escapeHtmlAttribute(session.token)}">`,
|
|
72
|
+
`<meta name="opencodex-session-csrf" content="${escapeHtmlAttribute(session.csrfToken)}">`,
|
|
73
|
+
`<meta name="opencodex-session-origin" content="${escapeHtmlAttribute(session.origin)}">`,
|
|
74
|
+
].join("");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function htmlDocumentResponse(html: string): Response {
|
|
69
78
|
return new Response(html, {
|
|
70
79
|
headers: {
|
|
71
80
|
"Content-Type": "text/html",
|
|
@@ -76,6 +85,26 @@ function htmlResponse(path: string, session?: GuiSessionBootstrap): Response {
|
|
|
76
85
|
});
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
function htmlResponse(path: string, session?: GuiSessionBootstrap): Response {
|
|
89
|
+
let html = readFileSync(path, "utf8");
|
|
90
|
+
if (session) {
|
|
91
|
+
const bootstrap = sessionBootstrapMeta(session);
|
|
92
|
+
html = html.includes("</head>") ? html.replace("</head>", `${bootstrap}</head>`) : `${bootstrap}${html}`;
|
|
93
|
+
}
|
|
94
|
+
return htmlDocumentResponse(html);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Minimal session-bootstrap document, independent of any packaged GUI build. The dev
|
|
99
|
+
* GUI (Vite) proxies /opencodex-session to the backend with the original host so the
|
|
100
|
+
* backend can mint an origin-bound loopback session even when gui/dist does not exist.
|
|
101
|
+
*/
|
|
102
|
+
export function serveSessionBootstrap(session: GuiSessionBootstrap): Response {
|
|
103
|
+
const bootstrap = sessionBootstrapMeta(session);
|
|
104
|
+
const html = `<!doctype html><html><head><meta charset="utf-8">${bootstrap}</head><body></body></html>`;
|
|
105
|
+
return htmlDocumentResponse(html);
|
|
106
|
+
}
|
|
107
|
+
|
|
79
108
|
export function serveGuiFile(
|
|
80
109
|
pathname: string,
|
|
81
110
|
guiDist = findGuiDist(),
|
package/src/server/images.ts
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
import { formatErrorResponse } from "../bridge";
|
|
16
16
|
import {
|
|
17
17
|
CodexAccountCooldownError,
|
|
18
|
+
codexMainProfileDrainingResponse,
|
|
18
19
|
cooldownErrorResponse,
|
|
19
20
|
CodexAuthContextError,
|
|
21
|
+
CodexMainProfileDrainingError,
|
|
20
22
|
CodexPoolAuthenticationError,
|
|
21
23
|
CodexThreadAffinityExpiredError,
|
|
22
24
|
} from "../codex/auth-context";
|
|
@@ -35,6 +37,8 @@ import { safeAntigravityHttpErrorMessage } from "../adapters/google-errors";
|
|
|
35
37
|
import { sanitizeUpstreamErrorText } from "../adapters/upstream-http-error";
|
|
36
38
|
import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
|
|
37
39
|
import { decodeValidatedImageBase64, MAX_ENCODED_BYTES_PER_IMAGE } from "../images/artifacts";
|
|
40
|
+
import type { AdmissionLease } from "../lib/admission";
|
|
41
|
+
import { codexAccountSelectionForTurn } from "./lifecycle";
|
|
38
42
|
|
|
39
43
|
export type ImagesEndpoint = "generations" | "edits";
|
|
40
44
|
|
|
@@ -323,6 +327,7 @@ export async function handleImages(
|
|
|
323
327
|
config: OcxConfig,
|
|
324
328
|
endpoint: ImagesEndpoint,
|
|
325
329
|
logCtx: RequestLogContext,
|
|
330
|
+
turnAdmissionLease?: AdmissionLease,
|
|
326
331
|
): Promise<Response> {
|
|
327
332
|
const candidates = selectImagesProvider(config);
|
|
328
333
|
if (candidates.error) {
|
|
@@ -375,11 +380,15 @@ export async function handleImages(
|
|
|
375
380
|
let forwardAuthError: Response | undefined;
|
|
376
381
|
if (canUseOpenAiForward) {
|
|
377
382
|
try {
|
|
378
|
-
forward = await resolveFirstUsableOpenAiSidecar(candidates.forwardCandidates, req.headers, config
|
|
383
|
+
forward = await resolveFirstUsableOpenAiSidecar(candidates.forwardCandidates, req.headers, config, {
|
|
384
|
+
beginCodexAccountSelection: codexAccountSelectionForTurn(turnAdmissionLease),
|
|
385
|
+
});
|
|
379
386
|
if (forward) logCtx.provider = formatCodexProviderForLog(forward.providerName, codexLogAccountId(forward.authContext), config);
|
|
380
387
|
} catch (err) {
|
|
381
388
|
if (err instanceof CodexAccountCooldownError) {
|
|
382
389
|
forwardAuthError = cooldownErrorResponse(err);
|
|
390
|
+
} else if (err instanceof CodexMainProfileDrainingError) {
|
|
391
|
+
forwardAuthError = codexMainProfileDrainingResponse();
|
|
383
392
|
} else if (err instanceof CodexThreadAffinityExpiredError) {
|
|
384
393
|
forwardAuthError = formatErrorResponse(409, "invalid_request_error", "Codex thread account affinity expired; start a new session");
|
|
385
394
|
} else if (err instanceof CodexAuthContextError) {
|