@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
package/src/router.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./combos";
|
|
10
10
|
import type { NormalizedComboConfig } from "./combos/types";
|
|
11
11
|
import { hasOwnProvider } from "./config/provider-name";
|
|
12
|
-
import { resolveProviderApiKey } from "./providers/key-store";
|
|
12
|
+
import { providerUsesKeyAuthOverride, resolveProviderApiKey } from "./providers/key-store";
|
|
13
13
|
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
14
14
|
import { redactSecretString, redactUrlForLog } from "./lib/redact";
|
|
15
15
|
import {
|
|
@@ -42,10 +42,17 @@ import {
|
|
|
42
42
|
type RouteDecisionTraceV1,
|
|
43
43
|
type TraceCandidateInput,
|
|
44
44
|
} from "./routing/trace";
|
|
45
|
-
import { getRoutingProfile, resolvePolicyProfileId } from "./routing/profile";
|
|
45
|
+
import { getRoutingProfile, resolvePolicyProfileId, POLICY_NAMESPACE } from "./routing/profile";
|
|
46
46
|
import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
|
|
47
47
|
import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
|
|
48
48
|
|
|
49
|
+
export class UnknownRoutingPolicyError extends Error {
|
|
50
|
+
constructor(readonly profileId: string) {
|
|
51
|
+
super(`Unknown routing policy: ${profileId}`);
|
|
52
|
+
this.name = "UnknownRoutingPolicyError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
export class NoEligiblePolicyCandidateError extends Error {
|
|
50
57
|
/** Evaluation trace (with per-candidate exclusions) when nothing qualified. */
|
|
51
58
|
readonly trace?: RouteDecisionTraceV1;
|
|
@@ -300,10 +307,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
300
307
|
const repairLegacyMimoFreeAuth = providerName === "mimo-free"
|
|
301
308
|
&& staticModelCatalog
|
|
302
309
|
&& (provider.authMode === undefined || provider.authMode === "local");
|
|
303
|
-
const explicitKeyOverride = registryEntry
|
|
304
|
-
&& registryEntry.allowKeyAuthOverride === true
|
|
305
|
-
&& provider.authMode === "key"
|
|
306
|
-
&& resolvedApiKey !== undefined;
|
|
310
|
+
const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider, resolvedApiKey);
|
|
307
311
|
const canonicalAuthMode = explicitKeyOverride
|
|
308
312
|
? "key"
|
|
309
313
|
: repairLegacyMimoFreeAuth
|
|
@@ -604,11 +608,13 @@ function routeModelInternal(
|
|
|
604
608
|
// configured profile alias executes the policy evaluator and routes the
|
|
605
609
|
// selected candidate. Only explicit requests reach this branch; concrete
|
|
606
610
|
// recursive targets skip policy resolution entirely (bypassCombos) so an
|
|
607
|
-
// alias matching a selected candidate can never recurse
|
|
608
|
-
//
|
|
609
|
-
// provider/default resolution instead of failing.
|
|
611
|
+
// alias matching a selected candidate can never recurse. Missing reserved
|
|
612
|
+
// policy selectors fail before ordinary provider/default resolution.
|
|
610
613
|
const policyId = !bypassCombos ? resolvePolicyProfileId(config, modelId) : null;
|
|
611
614
|
const profile = policyId ? getRoutingProfile(config, policyId) : undefined;
|
|
615
|
+
if (!bypassCombos && !profile && (policyId !== null || modelId.startsWith(`${POLICY_NAMESPACE}/`))) {
|
|
616
|
+
throw new UnknownRoutingPolicyError(policyId ?? modelId.slice(POLICY_NAMESPACE.length + 1));
|
|
617
|
+
}
|
|
612
618
|
if (profile && policyId) {
|
|
613
619
|
// One clock read per decision keeps candidate evidence, exclusions, and
|
|
614
620
|
// scores mutually consistent and reproducible.
|
|
@@ -679,10 +685,39 @@ function routeModelInternal(
|
|
|
679
685
|
// no such provider exists.
|
|
680
686
|
if (slash > 0) {
|
|
681
687
|
const requestedProvider = modelId.slice(0, slash);
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
688
|
+
const requestedLower = requestedProvider.toLowerCase();
|
|
689
|
+
let provName: string | undefined;
|
|
690
|
+
|
|
691
|
+
if (hasOwnProvider(config.providers, requestedProvider)) {
|
|
692
|
+
provName = requestedProvider;
|
|
693
|
+
} else {
|
|
694
|
+
// Pass 1: explicit configured provider aliases (operator override always wins)
|
|
695
|
+
const configuredMatches = Object.entries(config.providers).filter(([, provider]) =>
|
|
696
|
+
typeof provider.alias === "string" && provider.alias.trim().toLowerCase() === requestedLower,
|
|
697
|
+
);
|
|
698
|
+
if (configuredMatches.length === 1) {
|
|
699
|
+
provName = configuredMatches[0]![0];
|
|
700
|
+
} else if (configuredMatches.length > 1) {
|
|
701
|
+
throw new Error("provider alias '" + requestedProvider + "' is ambiguous: " + configuredMatches.map(([n]) => n).sort().join(", "));
|
|
702
|
+
} else {
|
|
703
|
+
// Pass 2: built-in registry aliases, only for providers that do NOT have an explicit alias override
|
|
704
|
+
// and whose registry alias has not been claimed by another configured provider (#3531 review)
|
|
705
|
+
const registryMatches = Object.entries(config.providers).filter(([name, provider]) => {
|
|
706
|
+
if (provider.alias !== undefined) return false;
|
|
707
|
+
const regAlias = PROVIDER_REGISTRY.find(e => e.id === name)?.alias;
|
|
708
|
+
if (!regAlias || regAlias.toLowerCase() !== requestedLower) return false;
|
|
709
|
+
const claimedByOther = Object.entries(config.providers).some(([otherName, p]) =>
|
|
710
|
+
otherName !== name && typeof p.alias === "string" && p.alias.trim().toLowerCase() === requestedLower
|
|
711
|
+
);
|
|
712
|
+
return !claimedByOther;
|
|
713
|
+
});
|
|
714
|
+
if (registryMatches.length === 1) {
|
|
715
|
+
provName = registryMatches[0]![0];
|
|
716
|
+
} else if (registryMatches.length > 1) {
|
|
717
|
+
throw new Error("provider alias '" + requestedProvider + "' is ambiguous across registry fallbacks: " + registryMatches.map(([n]) => n).sort().join(", "));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
686
721
|
if (!provName) {
|
|
687
722
|
// A genuine slash-containing native model id still falls through unchanged.
|
|
688
723
|
} else {
|
package/src/routing/analytics.ts
CHANGED
|
@@ -213,9 +213,18 @@ export function candidateCapabilityEvidence(
|
|
|
213
213
|
|| provider?.parallelToolCalls === true
|
|
214
214
|
|| undefined;
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
// `noReasoningModels` is a POSITIVE statement that this model has no effort control, and
|
|
217
|
+
// every other consumer already reads it that way: configuredReasoningEfforts
|
|
218
|
+
// (reasoning-effort.ts), supportedLadderFor (server/effort-policy.ts) and the
|
|
219
|
+
// compatibility fingerprint (routing/compatibility/behavior.ts) all check it first.
|
|
220
|
+
// Routing evidence did not, which was harmless only while no registry ladder existed to
|
|
221
|
+
// contradict it — a provider-level ladder would otherwise report supported rungs for a
|
|
222
|
+
// model the operator explicitly disabled reasoning for.
|
|
223
|
+
const reasoningEfforts = modelInList(provider?.noReasoningModels, modelId)
|
|
224
|
+
? []
|
|
225
|
+
: modelRecordValue(provider?.modelReasoningEfforts, modelId)
|
|
226
|
+
?? modelRecordValue(registryEntry?.modelReasoningEfforts, modelId)
|
|
227
|
+
?? (isNative ? nativeReasoningEfforts(modelId) : undefined);
|
|
219
228
|
|
|
220
229
|
const tierSupport = provider
|
|
221
230
|
? serviceTierSupportForModel(provider, modelId, providerName)
|
|
@@ -236,7 +245,11 @@ export function candidateCapabilityEvidence(
|
|
|
236
245
|
...(typeof contextWindow === "number" ? { contextWindow } : {}),
|
|
237
246
|
...(typeof image === "boolean" ? { image } : {}),
|
|
238
247
|
...(typeof tools === "boolean" ? { tools } : {}),
|
|
239
|
-
|
|
248
|
+
// A DEFINED but empty ladder is known-negative evidence and must survive. Dropping it
|
|
249
|
+
// made the evaluator take its `!Array.isArray` branch and record "unknown", which is
|
|
250
|
+
// permissive — "we could not tell" rather than "this model has no effort control" — so
|
|
251
|
+
// an explicitly disabled model could still satisfy a reasoning-effort requirement.
|
|
252
|
+
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
240
253
|
...(serviceTier !== "unknown" ? { serviceTier } : {}),
|
|
241
254
|
...localRemote,
|
|
242
255
|
...(typeof encryptedCodexTasks === "boolean" ? { encryptedCodexTasks } : {}),
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { initialModelSelection } from "../providers/initial-model-selection";
|
|
2
3
|
import { extractAccountId } from "../oauth/chatgpt";
|
|
3
4
|
import { formatErrorResponse } from "../bridge";
|
|
4
5
|
import {
|
|
@@ -62,7 +63,7 @@ export function isLoopbackRequestHost(value: string | null): boolean {
|
|
|
62
63
|
// Scope of that guarantee: it holds for Hosts `parseHttpHost` can parse. An unparseable
|
|
63
64
|
// Host still returns true above — pre-existing behavior, not browser-reachable (a browser
|
|
64
65
|
// composes Host from its own connection), and pinned by a characterization test in
|
|
65
|
-
// tests/server-loopback-host-gate.test.ts. Tightening it is separate work.
|
|
66
|
+
// tests/server/server-loopback-host-gate.test.ts. Tightening it is separate work.
|
|
66
67
|
return isLoopbackHostname(parsed.hostname);
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -784,6 +785,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
784
785
|
modelSupportsServiceTier: "editor",
|
|
785
786
|
preserveResponsesReasoningContent: "editor",
|
|
786
787
|
decodesNativeCompactionBlobs: "editor",
|
|
788
|
+
allowEncryptedV2AgentTasks: "editor",
|
|
787
789
|
allowPrivateNetwork: "editor",
|
|
788
790
|
upstreamHttpVersion: "editor",
|
|
789
791
|
upstreamWebsocket: "editor",
|
|
@@ -797,6 +799,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
797
799
|
models: "editor",
|
|
798
800
|
liveModels: "editor",
|
|
799
801
|
selectedModels: "editor",
|
|
802
|
+
initialModelSelection: "runtime",
|
|
800
803
|
retainModels: "editor",
|
|
801
804
|
newModelPolicy: "editor",
|
|
802
805
|
modelPreset: "editor",
|
|
@@ -830,6 +833,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
830
833
|
modelPreferHostedTools: "editor",
|
|
831
834
|
supportsOpenAiWebSearchToolFields: "editor",
|
|
832
835
|
xaiResponsesXSearch: "editor",
|
|
836
|
+
xaiResponsesDefaultVersion: "runtime",
|
|
833
837
|
supportsResponsesCustomTools: "editor",
|
|
834
838
|
responsesSnapshotRepair: "editor",
|
|
835
839
|
reasoningEffortMap: "editor",
|
|
@@ -1001,6 +1005,8 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
1001
1005
|
if (name === "xai") {
|
|
1002
1006
|
dto.xaiResponsesOptInState = xaiResponsesOptInState(provider);
|
|
1003
1007
|
}
|
|
1008
|
+
const selection = initialModelSelection(provider);
|
|
1009
|
+
if (selection) dto.initialModelSelection = selection;
|
|
1004
1010
|
providers[name] = dto;
|
|
1005
1011
|
}
|
|
1006
1012
|
return {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
startStorageCleanupScheduler,
|
|
11
11
|
stopStorageCleanupScheduler,
|
|
12
12
|
} from "../storage/policy-scheduler";
|
|
13
|
+
import { startQuotaResetPoller, stopQuotaResetPoller } from "../quota/reset-poller";
|
|
13
14
|
import {
|
|
14
15
|
cancelQueuedStorageWorkerSpawns,
|
|
15
16
|
drainStorageWorkers,
|
|
@@ -59,11 +60,31 @@ function startProcessLoops(applyPolicy: PolicyApply): ProcessLoops {
|
|
|
59
60
|
stateStoreSweeper = startStateStoreSweeper();
|
|
60
61
|
setLivePolicyOwner(applyPolicy);
|
|
61
62
|
startStorageCleanupScheduler();
|
|
63
|
+
// Opt-in: the tick itself is a no-op unless config.quotaResetNotify is enabled with a
|
|
64
|
+
// sink, and the interval is unref'd, so a default install pays one dormant timer.
|
|
65
|
+
startQuotaResetPoller();
|
|
66
|
+
// The configured cadence is resolved out of band: reading it here would put a static edge
|
|
67
|
+
// to the config barrel on the load-time path the quota boundary guard pins.
|
|
68
|
+
void import("../quota/reset-poller")
|
|
69
|
+
.then(poller => poller.syncQuotaResetPollerCadence())
|
|
70
|
+
.catch(() => {
|
|
71
|
+
// The next tick adopts it.
|
|
72
|
+
});
|
|
73
|
+
// Install the delivery sink now rather than waiting out the first poll interval, which is 15
|
|
74
|
+
// minutes by default. Without this, an enabled install would observe nothing for its first
|
|
75
|
+
// quarter hour — including the live request path, which is gated on the sink existing.
|
|
76
|
+
// Fire-and-forget: startup must not await an optional subsystem.
|
|
77
|
+
void import("../quota/reset-activation")
|
|
78
|
+
.then(activation => activation.syncQuotaResetActivation())
|
|
79
|
+
.catch(() => {
|
|
80
|
+
// The next poll tick retries.
|
|
81
|
+
});
|
|
62
82
|
return { memoryWatchdog, stateStoreSweeper };
|
|
63
83
|
} catch (error) {
|
|
64
84
|
memoryWatchdog?.stop();
|
|
65
85
|
stateStoreSweeper?.stop();
|
|
66
86
|
stopStorageCleanupScheduler();
|
|
87
|
+
stopQuotaResetPoller();
|
|
67
88
|
setLivePolicyOwner(null);
|
|
68
89
|
throw error;
|
|
69
90
|
}
|
|
@@ -75,6 +96,7 @@ function stopProcessLoops(): void {
|
|
|
75
96
|
loops?.memoryWatchdog.stop();
|
|
76
97
|
loops?.stateStoreSweeper.stop();
|
|
77
98
|
stopStorageCleanupScheduler();
|
|
99
|
+
stopQuotaResetPoller();
|
|
78
100
|
setLivePolicyOwner(null);
|
|
79
101
|
}
|
|
80
102
|
|
|
@@ -179,4 +201,4 @@ export function acquireServerBackgroundLifecycle(
|
|
|
179
201
|
releaseFlight = Promise.resolve();
|
|
180
202
|
},
|
|
181
203
|
};
|
|
182
|
-
}
|
|
204
|
+
}
|
|
@@ -22,7 +22,7 @@ import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPo
|
|
|
22
22
|
import { redactSecretString } from "../lib/redact";
|
|
23
23
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
24
24
|
import { estimateTokens } from "../lib/token-estimate";
|
|
25
|
-
import { NoEligiblePolicyCandidateError, routeModel } from "../router";
|
|
25
|
+
import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
26
26
|
import { evidenceFromBody } from "../routing/request-evidence";
|
|
27
27
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
28
28
|
import type { OcxConfig } from "../types";
|
|
@@ -47,6 +47,9 @@ import {
|
|
|
47
47
|
} from "../lib/translator-budget";
|
|
48
48
|
import { handleNativeChatCompletions, isNativeChatRouteEligible } from "./chat-native";
|
|
49
49
|
import { parseRequestEffortRowId } from "./effort-row";
|
|
50
|
+
import { parseSyntheticRowId } from "./fast-row";
|
|
51
|
+
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
52
|
+
import { CODEX_RESERVE_HELPER_UNSUPPORTED_MESSAGE, isCodexReserveHelperUnsupported } from "../codex/loopback-target";
|
|
50
53
|
|
|
51
54
|
type Rec = Record<string, unknown>;
|
|
52
55
|
|
|
@@ -105,8 +108,15 @@ async function handleChatCompletionsWithBudget(
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
const requestedModel = chatBody.model as string;
|
|
108
|
-
const effortRow =
|
|
111
|
+
const { fastRow, effortRow } = parseSyntheticRowId(requestedModel, config);
|
|
109
112
|
if (effortRow) chatBody.model = effortRow.baseId;
|
|
113
|
+
if (fastRow) {
|
|
114
|
+
chatBody.model = fastRow.baseId;
|
|
115
|
+
// A caller intent; decideTier rules on it downstream. Unlike an effort row this does NOT
|
|
116
|
+
// block the native-chat shortcut below: native chat carries service_tier itself and runs
|
|
117
|
+
// the same policy, so blocking it would degrade the request for no reason.
|
|
118
|
+
chatBody.service_tier = "priority";
|
|
119
|
+
}
|
|
110
120
|
const stream = chatBody.stream === true;
|
|
111
121
|
// Best-effort Grok attribution: the managed fence stamps this header on every model
|
|
112
122
|
// it registers (extra_headers, sent verbatim by upstream Grok). Dashboard usage
|
|
@@ -138,6 +148,11 @@ async function handleChatCompletionsWithBudget(
|
|
|
138
148
|
}
|
|
139
149
|
if (!effortRow && isNativeChatRouteEligible(route, chatBody)) chatNativeRoute = route;
|
|
140
150
|
} catch (err) {
|
|
151
|
+
if (err instanceof UnknownRoutingPolicyError) {
|
|
152
|
+
logCtx.requestedModel = requestedModel;
|
|
153
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
154
|
+
return chatCompletionsErrorResponse(404, err.message, "invalid_request_error");
|
|
155
|
+
}
|
|
141
156
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
142
157
|
logCtx.routeDecision = err.trace;
|
|
143
158
|
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
@@ -205,6 +220,13 @@ async function handleChatCompletionsWithBudget(
|
|
|
205
220
|
else internalBody.reasoning = next;
|
|
206
221
|
}
|
|
207
222
|
|
|
223
|
+
const visionDescribeTerminal = req.headers.get("x-opencodex-vision-describe") === "1";
|
|
224
|
+
// Concrete helper targets must fail before optional stored-main credential enrichment.
|
|
225
|
+
// Unresolved combos are checked after their concrete child route is selected in Responses.
|
|
226
|
+
if (settledRoute && !settledRoute.combo && isCanonicalOpenAiForwardProvider(settledRoute.provider)
|
|
227
|
+
&& isCodexReserveHelperUnsupported(config, settledRoute.modelId, logIds?.admission, visionDescribeTerminal)) {
|
|
228
|
+
return chatCompletionsErrorResponse(400, CODEX_RESERVE_HELPER_UNSUPPORTED_MESSAGE, "invalid_request_error");
|
|
229
|
+
}
|
|
208
230
|
const headers = new Headers({ "content-type": "application/json" });
|
|
209
231
|
for (const name of FORWARD_HEADERS) {
|
|
210
232
|
if (name === "authorization" && !directRoute) continue;
|
|
@@ -271,7 +293,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
271
293
|
// Terminal vision-describe marker (roadmap 180): the bridge rebuilds
|
|
272
294
|
// headers from the FORWARD_HEADERS allowlist, which would drop the raw
|
|
273
295
|
// header — so the fact is detected here and carried as an option flag.
|
|
274
|
-
...(
|
|
296
|
+
...(visionDescribeTerminal ? { visionDescribeTerminal: true } : {}),
|
|
275
297
|
translatorBudget,
|
|
276
298
|
...(logIds ? { onFirstOutput: () => recordFirstOutput(logCtx, logIds.start) } : {}),
|
|
277
299
|
onNativePassthroughTerminal: status => finalizeNativeLog(httpStatusForRequestLogTerminal(status, logCtx), { terminalStatus: status, closeReason: "terminal" }),
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "../claude/outbound";
|
|
27
27
|
import { clearableDeadline, idleDeadline } from "../lib/abort";
|
|
28
28
|
import { estimateTokens } from "../lib/token-estimate";
|
|
29
|
-
import { NoEligiblePolicyCandidateError, routeModel } from "../router";
|
|
29
|
+
import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
30
30
|
import { evidenceFromBody } from "../routing/request-evidence";
|
|
31
31
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
32
32
|
import type { OcxConfig } from "../types";
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
isDataPlaneAdmissionSecret,
|
|
41
41
|
isProxyAdmissionSecret,
|
|
42
42
|
type RequestPolicyView,
|
|
43
|
+
type DataPlaneAdmission,
|
|
43
44
|
} from "./auth-cors";
|
|
44
45
|
import type { AdmissionLease } from "../lib/admission";
|
|
45
46
|
import { tryClaimNativeMainProfileForTurn } from "../codex/native-main-admission";
|
|
@@ -54,9 +55,30 @@ import {
|
|
|
54
55
|
parseRequestEffortRowId,
|
|
55
56
|
type ParsedEffortRowId,
|
|
56
57
|
} from "./effort-row";
|
|
58
|
+
import {
|
|
59
|
+
parseFastOnlyRowId,
|
|
60
|
+
parseSyntheticRowId,
|
|
61
|
+
type ParsedFastRowId,
|
|
62
|
+
} from "./fast-row";
|
|
57
63
|
|
|
58
64
|
type Rec = Record<string, unknown>;
|
|
59
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Decode a Claude selector that may carry the fast marker.
|
|
68
|
+
*
|
|
69
|
+
* The exact form is tried first, so a real model whose alias genuinely ends in the marker
|
|
70
|
+
* keeps winning. Only then is the marker treated as synthetic and the bare base decoded:
|
|
71
|
+
* a Desktop 3P alias is a HASH registered WITHOUT the marker, so an exact lookup can never
|
|
72
|
+
* resolve a synthetic one.
|
|
73
|
+
*/
|
|
74
|
+
function decodeClaudeFastSelector(raw: string, cc?: OcxConfig["claudeCode"]): string {
|
|
75
|
+
const exact = resolveInboundModel(raw, cc);
|
|
76
|
+
if (exact !== raw || !raw.endsWith("--fast")) return exact;
|
|
77
|
+
const bare = raw.slice(0, -"--fast".length);
|
|
78
|
+
const decodedBase = resolveInboundModel(bare, cc);
|
|
79
|
+
return decodedBase === bare ? exact : `${decodedBase}--fast`;
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
function isRec(v: unknown): v is Rec {
|
|
61
83
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
62
84
|
}
|
|
@@ -571,7 +593,7 @@ export async function handleClaudeMessages(
|
|
|
571
593
|
req: Request,
|
|
572
594
|
config: OcxConfig,
|
|
573
595
|
logCtx: RequestLogContext,
|
|
574
|
-
logIds?: { requestId: string; start: number; turnAdmissionLease?: AdmissionLease },
|
|
596
|
+
logIds?: { requestId: string; start: number; turnAdmissionLease?: AdmissionLease; admission?: DataPlaneAdmission },
|
|
575
597
|
requestPolicy: RequestPolicyView = config,
|
|
576
598
|
): Promise<Response> {
|
|
577
599
|
const translatorBudget = createTranslatorBudget();
|
|
@@ -591,7 +613,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
591
613
|
config: OcxConfig,
|
|
592
614
|
logCtx: RequestLogContext,
|
|
593
615
|
translatorBudget: TranslatorBudget,
|
|
594
|
-
logIds?: { requestId: string; start: number; turnAdmissionLease?: AdmissionLease },
|
|
616
|
+
logIds?: { requestId: string; start: number; turnAdmissionLease?: AdmissionLease; admission?: DataPlaneAdmission },
|
|
595
617
|
requestPolicy: RequestPolicyView = config,
|
|
596
618
|
): Promise<Response> {
|
|
597
619
|
logCtx.surface = "claude";
|
|
@@ -606,6 +628,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
606
628
|
let cacheKeySource: ClaudeCacheKeySource = null;
|
|
607
629
|
let effortOverride: string | null = null;
|
|
608
630
|
let effortRow: ParsedEffortRowId | null = null;
|
|
631
|
+
let fastRow: ParsedFastRowId | null = null;
|
|
609
632
|
let requestedModel = "";
|
|
610
633
|
try {
|
|
611
634
|
anthropicBody = await readAnthropicBody(req, translatorBudget);
|
|
@@ -628,11 +651,20 @@ async function handleClaudeMessagesWithBudget(
|
|
|
628
651
|
}
|
|
629
652
|
if (isRec(anthropicBody) && typeof anthropicBody.model === "string") {
|
|
630
653
|
requestedModel = anthropicBody.model;
|
|
631
|
-
|
|
654
|
+
// Decode for Fast only. A Claude alias is `claude-ocx-<provider>--<model>`, so it
|
|
655
|
+
// already uses `--` as its own separator: stripping the marker off the RAW alias would
|
|
656
|
+
// turn `claude-ocx-p--foo--fast` into `claude-ocx-p--foo` and route a DIFFERENT model.
|
|
657
|
+
// Effort parsing keeps the raw selector, so its behaviour is untouched.
|
|
658
|
+
({ fastRow, effortRow } = parseSyntheticRowId(
|
|
659
|
+
requestedModel,
|
|
660
|
+
config,
|
|
661
|
+
() => decodeClaudeFastSelector(requestedModel, config.claudeCode),
|
|
662
|
+
));
|
|
632
663
|
if (effortRow) {
|
|
633
664
|
anthropicBody.model = effortRow.baseId;
|
|
634
665
|
effortOverride = effortRow.effort;
|
|
635
666
|
}
|
|
667
|
+
if (fastRow) anthropicBody.model = fastRow.baseId;
|
|
636
668
|
}
|
|
637
669
|
// Debug capture (opt-in allowlist scalars) BEFORE the passthrough branch so
|
|
638
670
|
// native, routed, and disabled-alias paths are all observable (devlog 130 B1).
|
|
@@ -657,7 +689,10 @@ async function handleClaudeMessagesWithBudget(
|
|
|
657
689
|
);
|
|
658
690
|
if (claudeConversationId) logCtx.conversationId = claudeConversationId;
|
|
659
691
|
}
|
|
660
|
-
|
|
692
|
+
// A fast row blocks passthrough, unlike the chat case: this path forwards to Anthropic's
|
|
693
|
+
// own API, whose wire has no service_tier field and whose FastWire kind has an empty
|
|
694
|
+
// adapter set by design, so the tier would be silently dropped.
|
|
695
|
+
if (!effortRow && !fastRow && isRec(anthropicBody) && wantsNativePassthrough(req, config, requestPolicy, anthropicBody.model)) {
|
|
661
696
|
return await anthropicNativePassthrough(req, config, logCtx, logIds, anthropicBody, "/v1/messages");
|
|
662
697
|
}
|
|
663
698
|
if (isRec(anthropicBody) && effortOverride) {
|
|
@@ -669,6 +704,10 @@ async function handleClaudeMessagesWithBudget(
|
|
|
669
704
|
}
|
|
670
705
|
const translation = anthropicToResponsesTranslation(anthropicBody, config.claudeCode);
|
|
671
706
|
internalBody = translation.body;
|
|
707
|
+
// The Anthropic translator builds its body from model/input/store/stream plus sampling
|
|
708
|
+
// fields only, so the caller intent is applied to the TRANSLATED body rather than the
|
|
709
|
+
// inbound one.
|
|
710
|
+
if (fastRow) internalBody.service_tier = "priority";
|
|
672
711
|
translatorBudget.chargeRetained(new TextEncoder().encode(JSON.stringify(internalBody)).byteLength, { kind: "request_copies" });
|
|
673
712
|
cacheKeySource = translation.cacheKeySource;
|
|
674
713
|
} catch (err) {
|
|
@@ -725,6 +764,11 @@ async function handleClaudeMessagesWithBudget(
|
|
|
725
764
|
if (ladder !== undefined && ladder.length === 0) delete internalBody.reasoning;
|
|
726
765
|
}
|
|
727
766
|
} catch (err) {
|
|
767
|
+
if (err instanceof UnknownRoutingPolicyError) {
|
|
768
|
+
logCtx.requestedModel = requestedModel;
|
|
769
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
770
|
+
return anthropicErrorResponse(404, err.message, "invalid_request_error");
|
|
771
|
+
}
|
|
728
772
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
729
773
|
logCtx.routeDecision = err.trace;
|
|
730
774
|
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 404, { closeReason: "non_stream" });
|
|
@@ -784,6 +828,9 @@ async function handleClaudeMessagesWithBudget(
|
|
|
784
828
|
addFinalRequestLog(logIds.requestId, logIds.start, logCtx, status, meta);
|
|
785
829
|
};
|
|
786
830
|
const upstream = await handleResponses(internalReq, buildClaudeReplayConfig(config), logCtx, {
|
|
831
|
+
// Routing keeps Claude-only sidecar overrides; admission policy must follow the live owner.
|
|
832
|
+
codexAuthPolicy: config,
|
|
833
|
+
...(logIds?.admission ? { admission: logIds.admission } : {}),
|
|
787
834
|
...(logIds?.turnAdmissionLease ? { turnAdmissionLease: logIds.turnAdmissionLease } : {}),
|
|
788
835
|
abortSignal: req.signal,
|
|
789
836
|
promptCacheKeyIsSharedCohort: cacheKeySource === "system",
|
|
@@ -1032,6 +1079,16 @@ export async function handleClaudeCountTokens(
|
|
|
1032
1079
|
model = stripOneMillionMarker(countRoute);
|
|
1033
1080
|
raw.model = model;
|
|
1034
1081
|
}
|
|
1082
|
+
// Fast-only: count_tokens never parsed an effort row, so it must not start. It returns a
|
|
1083
|
+
// token estimate and sends no tier, so only the IDENTITY is corrected - without this the
|
|
1084
|
+
// synthetic id reaches native passthrough as a model Anthropic has never heard of.
|
|
1085
|
+
const countFastRow = parseFastOnlyRowId(
|
|
1086
|
+
config, () => decodeClaudeFastSelector(model, config.claudeCode),
|
|
1087
|
+
);
|
|
1088
|
+
if (countFastRow) {
|
|
1089
|
+
model = countFastRow.baseId;
|
|
1090
|
+
raw.model = model;
|
|
1091
|
+
}
|
|
1035
1092
|
captureClaudeInbound("count_tokens", raw, resolveInboundModel(model, config.claudeCode), req.headers.get("anthropic-beta") ?? undefined);
|
|
1036
1093
|
if (wantsNativePassthrough(req, config, requestPolicy, model)) {
|
|
1037
1094
|
return await anthropicNativePassthrough(req, config, { model, provider: "anthropic-native", surface: "claude" }, undefined, raw, "/v1/messages/count_tokens");
|
package/src/server/effort-row.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface EffortRowOptions {
|
|
|
29
29
|
supportsReasoning?: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function isKnownId(knownIds: EffortRowKnownIds | undefined, id: string): boolean {
|
|
32
|
+
export function isKnownId(knownIds: EffortRowKnownIds | undefined, id: string): boolean {
|
|
33
33
|
return typeof knownIds === "function" ? knownIds(id) : knownIds?.has(id) === true;
|
|
34
34
|
}
|
|
35
35
|
|