@bitkyc08/opencodex 2.42.0 → 2.43.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/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-Djowl68T.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +13 -3
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +216 -26
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +22 -10
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +21 -6
- package/src/cli/capabilities.ts +13 -2
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +170 -23
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +30 -4
- package/src/codex/catalog/metadata.ts +39 -9
- package/src/codex/catalog/native-models.ts +53 -12
- package/src/codex/catalog/parsing.ts +119 -5
- package/src/codex/catalog/provider-fetch.ts +150 -20
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +1 -1
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +3 -28
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +454 -166
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +127 -26
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +535 -104
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +107 -21
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +38 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +476 -57
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-BU1tE0sr.js +0 -112
- package/gui/dist/assets/index-DL9-iS6J.css +0 -1
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "../../combos";
|
|
29
29
|
import { isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
30
30
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
31
|
-
import { modelInList, namespacedToolName, toolChoiceToolPredicate } from "../../types";
|
|
31
|
+
import { dottedToolName, modelInList, namespacedToolName, toolChoiceToolPredicate } from "../../types";
|
|
32
32
|
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
|
|
33
33
|
import {
|
|
34
34
|
forceRefreshOAuthAccessSnapshot,
|
|
@@ -122,6 +122,34 @@ export function buildToolBridgeMaps(parsed: OcxParsedRequest, budget?: Translato
|
|
|
122
122
|
const requestedTools = parsed.context.tools ?? [];
|
|
123
123
|
const toolAllowed = toolChoiceToolPredicate(parsed.options.toolChoice, requestedTools);
|
|
124
124
|
const authorizedTools = requestedTools.filter(toolAllowed);
|
|
125
|
+
// A dotted alias is only safe while it names ONE tool. Namespaces and names both come from
|
|
126
|
+
// the caller's tool catalog and may contain dots, so `{ns: "a", name: "b.c"}` and
|
|
127
|
+
// `{ns: "a.b", name: "c"}` flatten to the same "a.b.c". Registering both would let a dotted
|
|
128
|
+
// provider echo restore against whichever was inserted last, which is a dispatch decision made
|
|
129
|
+
// by declaration order. Resolve ownership across the whole catalog FIRST so the outcome does
|
|
130
|
+
// not depend on that order, then register only the aliases that stayed unambiguous.
|
|
131
|
+
const dottedAliasOwners = new Map<string, string | null>();
|
|
132
|
+
for (const t of authorizedTools) {
|
|
133
|
+
if (!t.namespace) continue;
|
|
134
|
+
const alias = dottedToolName(t.namespace, t.name);
|
|
135
|
+
const identity = JSON.stringify([t.namespace, t.name]);
|
|
136
|
+
const owner = dottedAliasOwners.get(alias);
|
|
137
|
+
if (owner === undefined) dottedAliasOwners.set(alias, identity);
|
|
138
|
+
else if (owner !== identity) dottedAliasOwners.set(alias, null);
|
|
139
|
+
}
|
|
140
|
+
// A dotted alias that shadows a canonical `ns__name` or a bare declaration is the same
|
|
141
|
+
// confusion wearing a different spelling, so those lose the alias too.
|
|
142
|
+
for (const t of authorizedTools) {
|
|
143
|
+
const canonical = namespacedToolName(t.namespace, t.name);
|
|
144
|
+
const owner = dottedAliasOwners.get(canonical);
|
|
145
|
+
if (owner !== undefined && owner !== JSON.stringify([t.namespace, t.name])) {
|
|
146
|
+
dottedAliasOwners.set(canonical, null);
|
|
147
|
+
}
|
|
148
|
+
const bare = dottedAliasOwners.get(t.name);
|
|
149
|
+
if (bare !== undefined && bare !== JSON.stringify([t.namespace, t.name])) {
|
|
150
|
+
dottedAliasOwners.set(t.name, null);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
125
153
|
for (const t of authorizedTools) {
|
|
126
154
|
// Upstream output is untrusted: only restore calls for tools the caller authorized.
|
|
127
155
|
const wireName = namespacedToolName(t.namespace, t.name);
|
|
@@ -133,6 +161,18 @@ export function buildToolBridgeMaps(parsed: OcxParsedRequest, budget?: Translato
|
|
|
133
161
|
if (t.namespace) {
|
|
134
162
|
budget?.chargeRetained(new TextEncoder().encode(JSON.stringify([wireName, t.namespace, t.name])).byteLength, { kind: "retained_collectors" });
|
|
135
163
|
toolNsMap.set(wireName, { namespace: t.namespace, name: t.name, ...(t.freeform ? { freeform: true } : {}) });
|
|
164
|
+
// Dotted echo alias (`ns.name`, #3402): same tool identity as the flattened wire name,
|
|
165
|
+
// so a provider that echoes the dotted spelling still restores against this entry.
|
|
166
|
+
const dottedName = dottedToolName(t.namespace, t.name);
|
|
167
|
+
// Ambiguous aliases were resolved to null above; skipping them falls back to the
|
|
168
|
+
// unambiguous `ns__name` form, which every provider can still echo.
|
|
169
|
+
if (dottedAliasOwners.get(dottedName) !== null && dottedName !== wireName) {
|
|
170
|
+
budget?.chargeRetained(new TextEncoder().encode(dottedName).byteLength, { kind: "retained_collectors" });
|
|
171
|
+
declaredToolNames.add(dottedName);
|
|
172
|
+
budget?.chargeRetained(new TextEncoder().encode(JSON.stringify([dottedName, t.namespace, t.name])).byteLength, { kind: "retained_collectors" });
|
|
173
|
+
toolNsMap.set(dottedName, { namespace: t.namespace, name: t.name, ...(t.freeform ? { freeform: true } : {}) });
|
|
174
|
+
if (t.parameters && typeof t.parameters === "object") toolParameterSchemas.set(dottedName, t.parameters);
|
|
175
|
+
}
|
|
136
176
|
}
|
|
137
177
|
if (t.freeform) {
|
|
138
178
|
budget?.chargeRetained(new TextEncoder().encode(t.name).byteLength, { kind: "retained_collectors" });
|
|
@@ -42,6 +42,8 @@ import { describeImagesInPlace, planVisionSidecar, shouldResolveOpenAiVisionSide
|
|
|
42
42
|
import { createAdapterEventQueue, preflightAdapterEvents } from "../../adapters/run-turn-queue";
|
|
43
43
|
import {
|
|
44
44
|
applyCodexAuthContextToProvider,
|
|
45
|
+
createCodexReserveDispatchGuard,
|
|
46
|
+
unwrapUpstreamRetryEvidenceError,
|
|
45
47
|
CodexMainProfileDrainingError,
|
|
46
48
|
headersForCodexAuthContext,
|
|
47
49
|
materializeCodexUpstreamAuthAsync,
|
|
@@ -93,7 +95,12 @@ import {
|
|
|
93
95
|
import type { DataPlaneAdmission } from "../auth-cors";
|
|
94
96
|
import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
95
97
|
import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint } from "../../providers/openai-tiers";
|
|
98
|
+
import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
|
|
99
|
+
import { isCodexReserveRequestEligible } from "../../codex/loopback-target";
|
|
96
100
|
import { slugsEquivalent } from "../../providers/slug-codec";
|
|
101
|
+
import { decideTier, tierValueAfterDecision } from "../../providers/fastwire";
|
|
102
|
+
import { fastPolicyForModel } from "../../providers/service-tier";
|
|
103
|
+
import { parseFastOnlyRowId } from "../fast-row";
|
|
97
104
|
import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../providers/openai-virtual-models";
|
|
98
105
|
import { isUsageDebugEnabled } from "../../usage/debug";
|
|
99
106
|
import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress";
|
|
@@ -219,6 +226,9 @@ export function compactResponseTooLargeError(): Response {
|
|
|
219
226
|
|
|
220
227
|
async function refreshNativeMainCompactContext(args: {
|
|
221
228
|
req: Request;
|
|
229
|
+
config: OcxConfig;
|
|
230
|
+
modelId?: string;
|
|
231
|
+
admission?: DataPlaneAdmission;
|
|
222
232
|
authCtx: CodexAuthContext;
|
|
223
233
|
provider: OcxProviderConfig;
|
|
224
234
|
codexAccountMode?: CodexAccountMode;
|
|
@@ -228,7 +238,7 @@ async function refreshNativeMainCompactContext(args: {
|
|
|
228
238
|
| { ok: true; authCtx: CodexAuthContext; provider: OcxProviderConfig; headers: Headers }
|
|
229
239
|
| { ok: false; response: Response }
|
|
230
240
|
> {
|
|
231
|
-
const { req, authCtx, provider, codexAccountMode, substituteMainCredential, options } = args;
|
|
241
|
+
const { req, config, authCtx, provider, codexAccountMode, substituteMainCredential, options } = args;
|
|
232
242
|
if (authCtx.kind !== "main-pool") {
|
|
233
243
|
return { ok: false, response: formatErrorResponse(401, "authentication_error", "No native main credential to refresh") };
|
|
234
244
|
}
|
|
@@ -252,6 +262,9 @@ async function refreshNativeMainCompactContext(args: {
|
|
|
252
262
|
);
|
|
253
263
|
const headers = new Headers({ "content-type": "application/json" });
|
|
254
264
|
const selected = await materializeCodexUpstreamAuthAsync(req.headers, refreshedAuthCtx, {
|
|
265
|
+
admission: args.admission,
|
|
266
|
+
config,
|
|
267
|
+
modelId: args.modelId,
|
|
255
268
|
substituteMainCredential,
|
|
256
269
|
signal: req.signal,
|
|
257
270
|
nativeMainRefreshDependencies: options.nativeMainRefreshDependencies,
|
|
@@ -270,7 +283,9 @@ async function refreshNativeMainCompactContext(args: {
|
|
|
270
283
|
if (req.signal.aborted) {
|
|
271
284
|
return { ok: false, response: formatErrorResponse(499, "client_cancelled", "Client cancelled compact request") };
|
|
272
285
|
}
|
|
273
|
-
return { ok: false, response:
|
|
286
|
+
return { ok: false, response: mapCodexAuthContextErrorToResponse(error, {
|
|
287
|
+
now: Date.now(),
|
|
288
|
+
}) ?? nativeMainRefreshFailureResponse(error) };
|
|
274
289
|
}
|
|
275
290
|
}
|
|
276
291
|
|
|
@@ -286,6 +301,9 @@ function isTerminalCompactPoolRefreshFailure(error: unknown): boolean {
|
|
|
286
301
|
*/
|
|
287
302
|
async function refreshPoolCompactContext(args: {
|
|
288
303
|
req: Request;
|
|
304
|
+
config: OcxConfig;
|
|
305
|
+
modelId?: string;
|
|
306
|
+
admission?: DataPlaneAdmission;
|
|
289
307
|
authCtx: CodexAuthContext & { kind: "pool" };
|
|
290
308
|
provider: OcxProviderConfig;
|
|
291
309
|
codexAccountMode?: CodexAccountMode;
|
|
@@ -295,7 +313,7 @@ async function refreshPoolCompactContext(args: {
|
|
|
295
313
|
| { ok: true; authCtx: CodexAuthContext; provider: OcxProviderConfig; headers: Headers }
|
|
296
314
|
| { ok: false; response: Response; quarantine: boolean; quarantineGeneration?: number }
|
|
297
315
|
> {
|
|
298
|
-
const { req, authCtx, provider, codexAccountMode, substituteMainCredential, options } = args;
|
|
316
|
+
const { req, config, authCtx, provider, codexAccountMode, substituteMainCredential, options } = args;
|
|
299
317
|
const reauthResponse = () => formatErrorResponse(
|
|
300
318
|
401,
|
|
301
319
|
"authentication_error",
|
|
@@ -328,6 +346,9 @@ async function refreshPoolCompactContext(args: {
|
|
|
328
346
|
);
|
|
329
347
|
const headers = new Headers({ "content-type": "application/json" });
|
|
330
348
|
const selected = await materializeCodexUpstreamAuthAsync(req.headers, refreshedAuthCtx, {
|
|
349
|
+
admission: args.admission,
|
|
350
|
+
config,
|
|
351
|
+
modelId: args.modelId,
|
|
331
352
|
substituteMainCredential,
|
|
332
353
|
signal: req.signal,
|
|
333
354
|
nativeMainRefreshDependencies: options.nativeMainRefreshDependencies,
|
|
@@ -375,11 +396,13 @@ async function resolveAlternateCompactContext(args: {
|
|
|
375
396
|
selectedModelId: string | undefined;
|
|
376
397
|
excludeAccountId: string | null;
|
|
377
398
|
turnAdmissionLease?: AdmissionLease;
|
|
399
|
+
admission?: DataPlaneAdmission;
|
|
378
400
|
}): Promise<{ authCtx: CodexAuthContext; provider: OcxProviderConfig; headers: Headers } | null> {
|
|
379
401
|
const { req, config, route, selectedModelId, excludeAccountId, turnAdmissionLease } = args;
|
|
380
402
|
if (!route.codexAccountMode || !excludeAccountId) return null;
|
|
381
403
|
try {
|
|
382
404
|
const authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
|
|
405
|
+
admission: args.admission,
|
|
383
406
|
...(selectedModelId ? { modelId: selectedModelId } : {}),
|
|
384
407
|
excludeAccountId,
|
|
385
408
|
requestScopedMainCredential: hasForwardableCodexBearer(req.headers, config),
|
|
@@ -391,7 +414,7 @@ async function resolveAlternateCompactContext(args: {
|
|
|
391
414
|
if (authCtx.accountId === excludeAccountId) return null;
|
|
392
415
|
const provider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
|
|
393
416
|
const headers = new Headers({ "content-type": "application/json" });
|
|
394
|
-
const selected = headersForCodexAuthContext(req.headers, authCtx);
|
|
417
|
+
const selected = headersForCodexAuthContext(req.headers, authCtx, config, selectedModelId, args.admission);
|
|
395
418
|
for (const name of FORWARD_HEADERS) {
|
|
396
419
|
const value = selected.get(name);
|
|
397
420
|
if (value) headers.set(name, value);
|
|
@@ -503,6 +526,16 @@ export async function handleResponsesCompact(
|
|
|
503
526
|
if (typeof raw.model !== "string" || raw.model.length === 0) {
|
|
504
527
|
return formatErrorResponse(400, "invalid_request_error", "compaction request requires a model");
|
|
505
528
|
}
|
|
529
|
+
// Correct the IDENTITY before routing, or the synthetic id does not route at all. Held in
|
|
530
|
+
// a local rather than written back to `raw.model`: assigning to the property widens it out
|
|
531
|
+
// of the `string` narrowing the guard above just established.
|
|
532
|
+
const compactFastRow = parseFastOnlyRowId(config, () => raw.model as string);
|
|
533
|
+
const compactModel = compactFastRow ? compactFastRow.baseId : raw.model;
|
|
534
|
+
if (compactFastRow) (raw as Record<string, unknown>).model = compactModel;
|
|
535
|
+
// The client's own selector, kept for the request log: `raw.model` is rewritten to the
|
|
536
|
+
// base id above, and logCtx.requestedModel is assigned from it further down, so without
|
|
537
|
+
// this the log would lose which id the client actually asked for.
|
|
538
|
+
const compactRequestedModel = compactFastRow ? compactFastRow.baseId + "--fast" : raw.model;
|
|
506
539
|
|
|
507
540
|
let route;
|
|
508
541
|
try {
|
|
@@ -512,7 +545,7 @@ export async function handleResponsesCompact(
|
|
|
512
545
|
// Codex selects a bare native model for compaction even when the operator
|
|
513
546
|
// routes ordinary turns elsewhere (#2901); the compaction-scoped router
|
|
514
547
|
// may land that on the configured default provider instead of 404.
|
|
515
|
-
route = routeCompactionModel(config,
|
|
548
|
+
route = routeCompactionModel(config, compactModel, evidenceFromBody(raw));
|
|
516
549
|
} catch (err) {
|
|
517
550
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
518
551
|
// Persist the evaluation trace (per-candidate exclusions + the
|
|
@@ -529,7 +562,7 @@ export async function handleResponsesCompact(
|
|
|
529
562
|
// exactly the selector form back down the native compact endpoint this guard exists to avoid.
|
|
530
563
|
// `route.modelId` is the same value `applyCodexAccountGatedWireNormalization` uses in core.ts.
|
|
531
564
|
const accountGatedCompactWireModel = codexAccountGatedCanonicalWireModel(selectedModelId);
|
|
532
|
-
logCtx.requestedModel =
|
|
565
|
+
logCtx.requestedModel = compactRequestedModel;
|
|
533
566
|
logCtx.model = selectedModelId;
|
|
534
567
|
logCtx.routeDecision = route.routeDecision;
|
|
535
568
|
logCtx.provider = route.codexAccountNamespace
|
|
@@ -544,13 +577,40 @@ export async function handleResponsesCompact(
|
|
|
544
577
|
} else {
|
|
545
578
|
logCtx.resolvedModel = route.modelId;
|
|
546
579
|
}
|
|
580
|
+
if (compactFastRow) {
|
|
581
|
+
// Resolved AFTER the virtual-model rewrite above, and against `route.modelId`, which is
|
|
582
|
+
// now the WIRE model: `resolveOpenAiCompactModel` maps an alias like `gpt-5.6-sol-pro`
|
|
583
|
+
// onto a different wire id, and capability overrides are keyed by exact model id, so
|
|
584
|
+
// deciding before the rewrite could set `priority` on a wire model that does not support
|
|
585
|
+
// it. `capabilityProvider` is passed for the same reason core.ts:2103 passes it.
|
|
586
|
+
const decision = decideTier(
|
|
587
|
+
fastPolicyForModel(
|
|
588
|
+
route.provider,
|
|
589
|
+
route.modelId,
|
|
590
|
+
route.providerName,
|
|
591
|
+
"responses",
|
|
592
|
+
config.providers[route.providerName],
|
|
593
|
+
),
|
|
594
|
+
config.fastMode,
|
|
595
|
+
"priority",
|
|
596
|
+
);
|
|
597
|
+
// The WHOLE decision: native compact spreads `raw` into the forwarded body, so on a drop
|
|
598
|
+
// a caller's pre-existing service_tier must be REMOVED rather than left to ride along
|
|
599
|
+
// past the suppression.
|
|
600
|
+
const serviceTier = tierValueAfterDecision(decision, "priority");
|
|
601
|
+
if (serviceTier === undefined) delete (raw as Record<string, unknown>).service_tier;
|
|
602
|
+
else (raw as Record<string, unknown>).service_tier = serviceTier;
|
|
603
|
+
}
|
|
547
604
|
|
|
548
605
|
// #1686: a bearer-presented admission secret is one of ours, so the stored main credential
|
|
549
606
|
// is substituted below instead of the caller bearer being forwarded.
|
|
550
607
|
// #2132: and only when the route is a native Codex one, which is the only route that can
|
|
551
608
|
// consume that credential. See the longer note in core.ts resolveResponsesCodexAuth.
|
|
609
|
+
const customReserveForward = selectedModelId === NATIVE_RESERVE_MODEL
|
|
610
|
+
&& isCodexReserveRequestEligible(config, admission)
|
|
611
|
+
&& isCanonicalOpenAiForwardProvider(route.provider);
|
|
552
612
|
const substituteMainCredential = admission?.source === "bearer"
|
|
553
|
-
&& route.codexAccountMode !== undefined;
|
|
613
|
+
&& (route.codexAccountMode !== undefined || customReserveForward);
|
|
554
614
|
const requestScopedMainCredential = route.codexAccountMode !== undefined
|
|
555
615
|
&& !substituteMainCredential
|
|
556
616
|
&& hasForwardableCodexBearer(req.headers, config);
|
|
@@ -598,8 +658,9 @@ export async function handleResponsesCompact(
|
|
|
598
658
|
let compactProvider = route.provider;
|
|
599
659
|
let headers = new Headers({ "content-type": "application/json" });
|
|
600
660
|
try {
|
|
601
|
-
if (route.codexAccountMode) {
|
|
602
|
-
authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
|
|
661
|
+
if (route.codexAccountMode || customReserveForward) {
|
|
662
|
+
if (route.codexAccountMode) authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
|
|
663
|
+
admission,
|
|
603
664
|
accountId: route.codexAccountId,
|
|
604
665
|
modelId: selectedModelId,
|
|
605
666
|
substituteMainCredentialForDirect: substituteMainCredential,
|
|
@@ -610,6 +671,10 @@ export async function handleResponsesCompact(
|
|
|
610
671
|
});
|
|
611
672
|
logCtx.accountLogLabel = codexAuthContextLogLabel(authCtx, config);
|
|
612
673
|
const selected = await materializeCodexUpstreamAuthAsync(req.headers, authCtx, {
|
|
674
|
+
admission,
|
|
675
|
+
config: isCanonicalOpenAiForwardProvider(route.provider) ? config : undefined,
|
|
676
|
+
modelId: selectedModelId,
|
|
677
|
+
beginCodexAccountSelection: codexAccountSelectionForTurn(turnAdmissionLease),
|
|
613
678
|
substituteMainCredential,
|
|
614
679
|
signal: req.signal,
|
|
615
680
|
nativeMainRefreshDependencies: options.nativeMainRefreshDependencies,
|
|
@@ -726,6 +791,7 @@ export async function handleResponsesCompact(
|
|
|
726
791
|
sendProvider: OcxProviderConfig,
|
|
727
792
|
sendHeaders: Headers,
|
|
728
793
|
recovery: "normal" | "single",
|
|
794
|
+
sendAuthCtx: CodexAuthContext,
|
|
729
795
|
): Promise<Response> => {
|
|
730
796
|
const doFetch = (upstreamRecovery?: UpstreamSendRecovery) => fetchWithHeaderTimeout(
|
|
731
797
|
compactUrl,
|
|
@@ -740,6 +806,8 @@ export async function handleResponsesCompact(
|
|
|
740
806
|
providerFetch(sendProvider, undefined, {
|
|
741
807
|
providerName: route.providerName,
|
|
742
808
|
modelId: route.modelId,
|
|
809
|
+
beforeDispatch: isCanonicalOpenAiForwardProvider(sendProvider)
|
|
810
|
+
? createCodexReserveDispatchGuard(sendAuthCtx, config, selectedModelId, admission) : undefined,
|
|
743
811
|
}),
|
|
744
812
|
// Every credential-bearing forward send gets manual redirects, not only
|
|
745
813
|
// pool sends: direct mode carries the caller's credential too (#914).
|
|
@@ -758,17 +826,30 @@ export async function handleResponsesCompact(
|
|
|
758
826
|
// The account each outcome belongs to. Reassigned only when the alternate send below
|
|
759
827
|
// actually happens, so every recorder call names the context that produced it.
|
|
760
828
|
let outcomeCtx = authCtx;
|
|
829
|
+
const localDispatchRefusal = (error: unknown): Response | undefined => {
|
|
830
|
+
const response = mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(error), {
|
|
831
|
+
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
832
|
+
});
|
|
833
|
+
if (response) {
|
|
834
|
+
releaseUpstreamHostAdmission(compactHostAdmissionLease);
|
|
835
|
+
compactHostAdmissionLease = null;
|
|
836
|
+
releaseCodexAuthContextProbeLease(outcomeCtx);
|
|
837
|
+
}
|
|
838
|
+
return response;
|
|
839
|
+
};
|
|
761
840
|
let upstream: Response;
|
|
762
841
|
let storedPool401ReplayAttempted = false;
|
|
763
842
|
try {
|
|
764
843
|
// Same connect timeout + keep-alive reset + transient-5xx recovery as /v1/responses —
|
|
765
844
|
// compact hits the same ChatGPT host and must soft-avoid / clear affinity (#186).
|
|
766
|
-
upstream = await sendCompactAttempt(compactProvider, headers, "normal");
|
|
845
|
+
upstream = await sendCompactAttempt(compactProvider, headers, "normal", authCtx);
|
|
767
846
|
} catch (err) {
|
|
768
847
|
if (req.signal.aborted) {
|
|
769
848
|
recordCompactPoolOutcome(outcomeCtx, 499);
|
|
770
849
|
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
771
850
|
}
|
|
851
|
+
const localRefusal = localDispatchRefusal(err);
|
|
852
|
+
if (localRefusal) return localRefusal;
|
|
772
853
|
const outcome = classifyTransportFailureKind(err);
|
|
773
854
|
// Host-level evidence stands regardless of pool membership (#914 review).
|
|
774
855
|
if (outcome === "connect_neutral") {
|
|
@@ -801,6 +882,9 @@ export async function handleResponsesCompact(
|
|
|
801
882
|
const poolReplay = poolAuthCtx
|
|
802
883
|
? await refreshPoolCompactContext({
|
|
803
884
|
req,
|
|
885
|
+
admission,
|
|
886
|
+
config,
|
|
887
|
+
modelId: selectedModelId,
|
|
804
888
|
authCtx: poolAuthCtx,
|
|
805
889
|
provider: compactProvider,
|
|
806
890
|
codexAccountMode: route.codexAccountMode,
|
|
@@ -811,6 +895,9 @@ export async function handleResponsesCompact(
|
|
|
811
895
|
const replay = poolReplay
|
|
812
896
|
?? await refreshNativeMainCompactContext({
|
|
813
897
|
req,
|
|
898
|
+
admission,
|
|
899
|
+
config,
|
|
900
|
+
modelId: selectedModelId,
|
|
814
901
|
authCtx,
|
|
815
902
|
provider: compactProvider,
|
|
816
903
|
codexAccountMode: route.codexAccountMode,
|
|
@@ -841,12 +928,14 @@ export async function handleResponsesCompact(
|
|
|
841
928
|
headers = replay.headers;
|
|
842
929
|
logCtx.accountLogLabel = codexAuthContextLogLabel(replay.authCtx, config);
|
|
843
930
|
try {
|
|
844
|
-
upstream = await sendCompactAttempt(compactProvider, headers, "single");
|
|
931
|
+
upstream = await sendCompactAttempt(compactProvider, headers, "single", authCtx);
|
|
845
932
|
} catch (err) {
|
|
846
933
|
if (req.signal.aborted) {
|
|
847
934
|
recordCompactPoolOutcome(outcomeCtx, 499);
|
|
848
935
|
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
849
936
|
}
|
|
937
|
+
const localRefusal = localDispatchRefusal(err);
|
|
938
|
+
if (localRefusal) return localRefusal;
|
|
850
939
|
recordCompactPoolOutcome(outcomeCtx, classifyTransportFailureKind(err));
|
|
851
940
|
return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
|
|
852
941
|
}
|
|
@@ -874,6 +963,7 @@ export async function handleResponsesCompact(
|
|
|
874
963
|
// throws, the first rejection is still intact and can be returned to the client.
|
|
875
964
|
const alternate = await resolveAlternateCompactContext({
|
|
876
965
|
req,
|
|
966
|
+
admission,
|
|
877
967
|
config,
|
|
878
968
|
route,
|
|
879
969
|
selectedModelId,
|
|
@@ -900,6 +990,7 @@ export async function handleResponsesCompact(
|
|
|
900
990
|
authCtx.accountId,
|
|
901
991
|
upstream.headers,
|
|
902
992
|
authCtx.writerGeneration,
|
|
993
|
+
authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined,
|
|
903
994
|
);
|
|
904
995
|
}
|
|
905
996
|
recordCompactPoolOutcome(authCtx, upstream.status, {
|
|
@@ -911,12 +1002,14 @@ export async function handleResponsesCompact(
|
|
|
911
1002
|
outcomeCtx = alternate.authCtx;
|
|
912
1003
|
logCtx.accountLogLabel = codexAuthContextLogLabel(alternate.authCtx, config);
|
|
913
1004
|
try {
|
|
914
|
-
upstream = await sendCompactAttempt(alternate.provider, alternate.headers, "single");
|
|
1005
|
+
upstream = await sendCompactAttempt(alternate.provider, alternate.headers, "single", alternate.authCtx);
|
|
915
1006
|
} catch (err) {
|
|
916
1007
|
if (req.signal.aborted) {
|
|
917
1008
|
recordCompactPoolOutcome(outcomeCtx, 499);
|
|
918
1009
|
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
919
1010
|
}
|
|
1011
|
+
const localRefusal = localDispatchRefusal(err);
|
|
1012
|
+
if (localRefusal) return localRefusal;
|
|
920
1013
|
const outcome = classifyTransportFailureKind(err);
|
|
921
1014
|
// Host-level evidence stands regardless of pool membership (#914 review).
|
|
922
1015
|
if (outcome === "connect_neutral") {
|