@bitkyc08/opencodex 2.90.0 → 2.92.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/bin/ocx.mjs +37 -3
- package/gui/dist/assets/App-DMps4OLk.css +1 -0
- package/gui/dist/assets/{App-DgxkE6Md.js → App-sUf_8afJ.js} +18 -18
- package/gui/dist/assets/{Tray-DbTNc_88.js → Tray-BSsxznDm.js} +1 -1
- package/gui/dist/assets/index-BlFE_VB_.js +94 -0
- package/gui/dist/assets/quota-summary-BRlbXgw0.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/workbuddy-color.svg +29 -0
- package/package.json +4 -3
- package/src/adapters/anthropic.ts +2 -5
- package/src/adapters/claude-cli/usage-admission.ts +14 -0
- package/src/adapters/google-vertex-endpoint.ts +9 -1
- package/src/adapters/google-vertex-key-project.ts +223 -0
- package/src/adapters/google.ts +13 -3
- package/src/adapters/openai-chat/tool-name-registry.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -2
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/adapters/openai-responses/request-strips.ts +92 -0
- package/src/adapters/openai-responses/transport.ts +6 -0
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/workbuddy-schema.ts +61 -0
- package/src/adapters/workbuddy.ts +68 -0
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +1 -1
- package/src/chat/inbound.ts +20 -2
- package/src/chat/outbound.ts +47 -8
- package/src/chat/reasoning-details.ts +137 -0
- package/src/cli/capabilities-agents-routing.ts +53 -3
- package/src/cli/capabilities-base.ts +15 -2
- package/src/cli/capabilities-observe-system.ts +4 -4
- package/src/cli/codex-cli-update.ts +8 -0
- package/src/cli/codex-shim-autorestore.ts +5 -0
- package/src/cli/combo-result.ts +6 -0
- package/src/cli/combo-stats.ts +3 -0
- package/src/cli/combo.ts +66 -42
- package/src/cli/dispatch.ts +30 -0
- package/src/cli/fallback.ts +127 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +1 -2
- package/src/cli/launcher-context.ts +49 -2
- package/src/cli/link-apply-restart.ts +72 -0
- package/src/cli/link-child.ts +6 -2
- package/src/cli/link-cleanup.ts +95 -0
- package/src/cli/link-enrollment.ts +4 -0
- package/src/cli/link.ts +123 -35
- package/src/cli/provider.ts +46 -3
- package/src/cli/registry.ts +13 -1
- package/src/cli/restart-failure.ts +4 -0
- package/src/cli/runtime-api.ts +1 -1
- package/src/cli/status-codex-settings.ts +56 -0
- package/src/cli/status-fallback.ts +29 -0
- package/src/cli/status-install.ts +52 -0
- package/src/cli/status.ts +20 -1
- package/src/cli/system-restart-client.ts +32 -1
- package/src/cli/update-restart-child.ts +4 -3
- package/src/cli/update-restart-home.ts +20 -8
- package/src/cli/usage-compare.ts +68 -12
- package/src/client/connect.ts +82 -6
- package/src/client/home-unreachable.ts +16 -0
- package/src/client/hub-relay.ts +8 -2
- package/src/client/link-home.ts +119 -6
- package/src/client/link-ingress.ts +1 -1
- package/src/client/link-parity-reporter.ts +107 -0
- package/src/client/link-readiness.ts +97 -0
- package/src/client/link-relay-transport.ts +84 -9
- package/src/client/link-relay.ts +98 -15
- package/src/client/link-tunnel.ts +45 -18
- package/src/client/machine-listener.ts +103 -3
- package/src/client/runtime-restart.ts +127 -0
- package/src/client/runtime.ts +155 -25
- package/src/codex/account-store.ts +17 -3
- package/src/codex/app-server-restart-service.ts +6 -5
- package/src/codex/auth-context.ts +10 -6
- package/src/codex/catalog/effort.ts +9 -2
- package/src/codex/catalog/gather-capture.ts +4 -2
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/provider-models.ts +14 -8
- package/src/codex/chatgpt-refresh-failure.ts +6 -2
- package/src/codex/cli-install-provenance.ts +418 -0
- package/src/codex/cli-installation-targets.ts +5 -1
- package/src/codex/cli-update-plan.ts +43 -16
- package/src/codex/desktop-app/picker-freshness.ts +10 -8
- package/src/codex/desktop-compatibility/usage-activation.ts +2 -1
- package/src/codex/desktop-compatibility/usage-controller.ts +7 -1
- package/src/codex/desktop-compatibility/windows-key-protection.ts +18 -6
- package/src/codex/pool-refresh-backoff.ts +3 -1
- package/src/codex/quota-history.ts +7 -0
- package/src/codex/quota.ts +10 -0
- package/src/codex/subagent-model-fallback.ts +24 -1
- package/src/codex/user-identity.ts +35 -6
- package/src/codex/windows-current-sid.ts +162 -0
- package/src/combos/index.ts +1 -0
- package/src/combos/jev-quota-activation.ts +5 -1
- package/src/combos/jev-quota-route.ts +1 -1
- package/src/combos/jev-quota.ts +9 -1
- package/src/combos/jev-session-pin-config.ts +51 -0
- package/src/combos/jev.ts +6 -0
- package/src/combos/resolve.ts +12 -0
- package/src/combos/types.ts +19 -0
- package/src/config/diagnostics.ts +5 -0
- package/src/config/load-degrade.ts +2 -0
- package/src/config/provider-validation.ts +7 -0
- package/src/config/schema/config-schema.ts +3 -0
- package/src/config/schema/fallback-policy.ts +40 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +375 -195
- package/src/lib/authorization-scheme.ts +95 -0
- package/src/lib/decision-quota-sink.ts +10 -0
- package/src/lib/provider-client-headers.ts +1 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/system-restart-contract.ts +24 -0
- package/src/lib/test-home-guard.ts +78 -11
- package/src/lib/translator-budget.ts +4 -4
- package/src/lib/upstream-retry.ts +4 -1
- package/src/link/child-parity.ts +124 -0
- package/src/link/codex-settings.ts +37 -0
- package/src/link/compatibility.ts +119 -0
- package/src/link/diagnostics.ts +193 -0
- package/src/link/management-routes.ts +2 -0
- package/src/link/peer-report.ts +100 -0
- package/src/link/relay-auth.ts +6 -0
- package/src/link/remote-platform.ts +78 -0
- package/src/link/ssh-argv.ts +19 -0
- package/src/link/status-projection.ts +63 -2
- package/src/link/supervisor.ts +45 -12
- package/src/link/tunnel-history.ts +109 -0
- package/src/link/tunnel-state.ts +80 -17
- package/src/messaging/remote-files.ts +24 -1
- package/src/oauth/generic-account-failover.ts +91 -7
- package/src/oauth/index.ts +16 -1
- package/src/oauth/kimi.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +5 -0
- package/src/oauth/nous.ts +4 -1
- package/src/oauth/store.ts +6 -2
- package/src/oauth/token-guardian.ts +7 -0
- package/src/oauth/types.ts +9 -0
- package/src/oauth/workbuddy.ts +139 -0
- package/src/protocols/codecs/chat.ts +3 -3
- package/src/protocols/encoders/adapter-events.ts +26 -2
- package/src/protocols/encoders/chat.ts +55 -3
- package/src/providers/antigravity-quota-identity.ts +37 -0
- package/src/providers/base-url-choices.ts +8 -5
- package/src/providers/derive.ts +23 -3
- package/src/providers/mistral-transport.ts +37 -4
- package/src/providers/model-discovery.ts +2 -0
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/quota/account-cache.ts +3 -2
- package/src/providers/quota/antigravity.ts +27 -22
- package/src/providers/quota/report-cache.ts +13 -4
- package/src/providers/quota/vendor-probes-key.ts +60 -0
- package/src/providers/quota/workbuddy.ts +83 -0
- package/src/providers/quota-decision-antigravity.ts +64 -0
- package/src/providers/quota-decision-publication.ts +11 -0
- package/src/providers/quota-decision-snapshot.ts +21 -4
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +3 -1
- package/src/providers/registry/entries-core.ts +9 -0
- package/src/providers/registry/entries-extended.ts +20 -7
- package/src/providers/registry/model-ids.ts +3 -0
- package/src/providers/registry/model-seeds.ts +27 -28
- package/src/providers/registry/types.ts +19 -1
- package/src/providers/resolved-model-policy.ts +4 -4
- package/src/providers/routed-config.ts +15 -0
- package/src/providers/stale-model-roster-migration.ts +83 -15
- package/src/providers/workbuddy-credit-balance.ts +81 -0
- package/src/providers/workbuddy-models.ts +37 -0
- package/src/providers/workbuddy.ts +60 -0
- package/src/quota-pool/assemble.ts +326 -0
- package/src/quota-pool/contract.ts +58 -0
- package/src/quota-pool/identity.ts +203 -0
- package/src/remote/features.ts +74 -0
- package/src/remote/protocol.ts +2 -0
- package/src/remote/served-by.ts +57 -0
- package/src/responses/parser.ts +7 -0
- package/src/responses/reasoning-envelope.ts +12 -0
- package/src/responses/reasoning-replay-cache.ts +18 -14
- package/src/router.ts +2 -0
- package/src/routing/analytics.ts +2 -3
- package/src/routing/fallback-policy.ts +290 -0
- package/src/routing/fallback-state.ts +179 -0
- package/src/server/audio-client.ts +2 -1
- package/src/server/audio-dictation.ts +2 -1
- package/src/server/audio-upstream.ts +2 -1
- package/src/server/auth-cors.ts +7 -5
- package/src/server/background-lifecycle.ts +4 -0
- package/src/server/chat-completions.ts +22 -2
- package/src/server/chat-native-reasoning.ts +10 -3
- package/src/server/chat-native.ts +13 -2
- package/src/server/claude-messages.ts +3 -0
- package/src/server/gui-session.ts +2 -1
- package/src/server/index/link-listener.ts +11 -1
- package/src/server/index/link-management.ts +47 -0
- package/src/server/index/link-relay-sessions.ts +20 -7
- package/src/server/index/optional-listeners.ts +11 -2
- package/src/server/index/serve-options.ts +19 -2
- package/src/server/live.ts +5 -0
- package/src/server/local-management-read-client.ts +3 -1
- package/src/server/management/combo-routes.ts +10 -4
- package/src/server/management/context.ts +1 -1
- package/src/server/management/fallback-routes.ts +75 -0
- package/src/server/management/link-enrollment.ts +21 -3
- package/src/server/management/link-route-state.ts +2 -0
- package/src/server/management/link-routes.ts +16 -2
- package/src/server/management/provider-routes.ts +1 -1
- package/src/server/management/quota-pool-routes.ts +24 -0
- package/src/server/management/route-registry.ts +3 -0
- package/src/server/management-api.ts +16 -0
- package/src/server/management-auth.ts +2 -1
- package/src/server/port-reclaim.ts +51 -9
- package/src/server/request-log-conversation.ts +49 -0
- package/src/server/request-log.ts +2 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/anthropic-thinking-recovery.ts +1 -0
- package/src/server/responses/compact.ts +12 -4
- package/src/server/responses/core-auth.ts +11 -1
- package/src/server/responses/core-combo.ts +125 -32
- package/src/server/responses/core-lifetime.ts +2 -2
- package/src/server/responses/core-normalize.ts +6 -3
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +2 -1
- package/src/server/responses/encrypted-payload.ts +3 -3
- package/src/server/responses/jev-combo-choice.ts +39 -0
- package/src/server/responses/jev-session-pin.ts +203 -0
- package/src/server/responses/main-hard-lock-routing.ts +54 -2
- package/src/server/responses/passthrough-delivery.ts +4 -0
- package/src/server/responses/policy-fallback.ts +44 -1
- package/src/server/responses/request-prepare.ts +19 -6
- package/src/server/responses/request-transport.ts +25 -6
- package/src/server/responses/run-turn-execution.ts +2 -4
- package/src/server/responses/sidecar-execution.ts +2 -4
- package/src/server/responses-phase-inference.ts +267 -0
- package/src/server/restart-replacement.ts +50 -2
- package/src/server/sse-payload-rewrite.ts +14 -1
- package/src/server/ws-bridge.ts +5 -0
- package/src/service/cli.ts +25 -2
- package/src/service/definition.ts +20 -8
- package/src/service/diagnostics.ts +20 -6
- package/src/service/status-json.ts +116 -0
- package/src/types/config.ts +13 -0
- package/src/types/fallback.ts +38 -0
- package/src/types/provider.ts +9 -1
- package/src/types/request.ts +1 -0
- package/src/types/wire.ts +1 -0
- package/src/update/codex-cli-update-launch-policy.d.mts +9 -0
- package/src/update/codex-cli-update-launch-policy.mjs +30 -0
- package/src/usage/jev-stats.ts +54 -14
- package/gui/dist/assets/App-dnkJ85te.css +0 -1
- package/gui/dist/assets/index-TmaHa54u.js +0 -94
- package/gui/dist/assets/quota-summary-B16x5DZZ.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { effectiveAzureOutputBudget } from "../../adapters/azure";
|
|
2
2
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
3
|
-
import type
|
|
3
|
+
import { comboCooldownRetryAfterSeconds, type CodexAccountModelRefusal } from "../../combos/failover";
|
|
4
|
+
import { fallbackTriggerForStatus, guardFallbackHop } from "../../routing/fallback-policy";
|
|
4
5
|
import {
|
|
5
6
|
isCodexReasoningEffort,
|
|
6
7
|
isDeclaredReasoningEffort,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
isComboTargetInCooldown,
|
|
33
34
|
snapshotComboQuotaCooldowns,
|
|
34
35
|
comboExclusionReasons,
|
|
36
|
+
comboTargetSkipTrigger,
|
|
35
37
|
noteComboSuccess,
|
|
36
38
|
comboFailureDecision,
|
|
37
39
|
advanceComboAfterFailure,
|
|
@@ -66,7 +68,7 @@ import {
|
|
|
66
68
|
import { codexAccountLogLabel, oauthAccountLogLabel } from "../../codex/account-label";
|
|
67
69
|
import { isGenericFailoverProvider, rotateGenericOAuthAccountOn429 } from "../../oauth/generic-account-failover";
|
|
68
70
|
import { codexQuotaScopeForModel, getCodexQuotaHealthSnapshot } from "../../codex/routing";
|
|
69
|
-
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
71
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
70
72
|
import type { AgentTaskRecoveryFailureReason } from "./agent-task-recovery";
|
|
71
73
|
import {
|
|
72
74
|
agentTaskRecoveryConfig,
|
|
@@ -74,6 +76,7 @@ import {
|
|
|
74
76
|
recoverEncryptedAgentTaskWithResult,
|
|
75
77
|
} from "./agent-task-recovery";
|
|
76
78
|
import { isThreadSpawnRequest, supportedLadderFor } from "../effort-policy";
|
|
79
|
+
import { jevChoiceEfforts } from "./jev-combo-choice";
|
|
77
80
|
import { applyDroidResponsesReasoningDefault } from "../droid-reasoning-default";
|
|
78
81
|
import { isPlainObject } from "../../lib/plain-data";
|
|
79
82
|
import {
|
|
@@ -100,6 +103,7 @@ import type { ResponsesTerminalStatus } from "../../bridge";
|
|
|
100
103
|
import { beginRequestAttempt, sealRequestAttemptIdentity, finishRequestAttempt } from "../request-log";
|
|
101
104
|
import { rememberComboForLane } from "./combo-session-recall";
|
|
102
105
|
import { COMBO_TOOL_LOOP_PIN_REASON, toolLoopPinnedPick } from "./combo-tool-loop-pin";
|
|
106
|
+
import { JEV_SESSION_PIN_ROUTE_REASON, jevSessionPinScope } from "./jev-session-pin";
|
|
103
107
|
import { runTurnAdapterSseResponses } from "./core-lifetime";
|
|
104
108
|
import { normalizePersistedJevDecision } from "../../usage/jev-stats";
|
|
105
109
|
import {
|
|
@@ -116,6 +120,7 @@ import { createComboProtocolLanes, dispatchNativeComboChild } from "./core-combo
|
|
|
116
120
|
import { createJevModelInvoker } from "./jev-model-invoke";
|
|
117
121
|
import { comboRequestedEffortLabel } from "./combo-requested-effort";
|
|
118
122
|
import { clientWireOf } from "../inference/client-wire";
|
|
123
|
+
import { resolveContextPrincipal } from "../auth-cors";
|
|
119
124
|
|
|
120
125
|
/**
|
|
121
126
|
* Sends one combo target may run on its own before the ladder moves on. A target is a whole
|
|
@@ -124,6 +129,20 @@ import { clientWireOf } from "../inference/client-wire";
|
|
|
124
129
|
*/
|
|
125
130
|
export const COMBO_TARGET_BASE_SENDS = CODEX_TEXT_GUARDED_BUDGET_POLICY.baseSendAllowance;
|
|
126
131
|
|
|
132
|
+
/**
|
|
133
|
+
* OCX-11: where a combo target physically dispatches, after provider aliases and
|
|
134
|
+
* blockedModelRedirects. A target that does not resolve keeps its configured selector; its
|
|
135
|
+
* dispatch fails on its own.
|
|
136
|
+
*/
|
|
137
|
+
function resolvedComboDestination(config: OcxConfig, target: { provider: string; model: string }): { provider: string; model: string } {
|
|
138
|
+
try {
|
|
139
|
+
const route = routeConcreteModel(config, `${target.provider}/${target.model}`);
|
|
140
|
+
return { provider: route.providerName, model: route.modelId };
|
|
141
|
+
} catch {
|
|
142
|
+
return { provider: target.provider, model: target.model };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
127
146
|
function cooledPoolAccountLabel(config: OcxConfig, providerName: string, modelId: string, label: string | undefined): string | undefined {
|
|
128
147
|
if (!label) return undefined;
|
|
129
148
|
const instance = configuredAnthropicInstance(config, providerName);
|
|
@@ -300,30 +319,6 @@ interface JevComboChoice {
|
|
|
300
319
|
candidate: JevCandidate;
|
|
301
320
|
}
|
|
302
321
|
|
|
303
|
-
/** A target's JEV effort choices, or undefined when an explicit allowlist has none still supported. */
|
|
304
|
-
function jevChoiceEfforts(
|
|
305
|
-
config: OcxConfig,
|
|
306
|
-
target: NonNullable<ReturnType<typeof getCombo>>["targets"][number],
|
|
307
|
-
): OcxComboDefaultEffort[] | undefined {
|
|
308
|
-
let ladder: string[] | undefined;
|
|
309
|
-
try {
|
|
310
|
-
const route = routeConcreteModel(config, targetKey(target));
|
|
311
|
-
ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
312
|
-
} catch {
|
|
313
|
-
// Preserve the existing routing-failure surface. Unknown capability becomes the explicit
|
|
314
|
-
// no-effort choice rather than broadening JEV's effort allowlist.
|
|
315
|
-
ladder = undefined;
|
|
316
|
-
}
|
|
317
|
-
const supportedEfforts = (ladder ?? []).filter(isCodexReasoningEffort) as OcxComboDefaultEffort[];
|
|
318
|
-
const configuredEfforts = target.reasoningEfforts;
|
|
319
|
-
const reasoningEfforts = configuredEfforts === undefined
|
|
320
|
-
? supportedEfforts
|
|
321
|
-
: configuredEfforts.filter(effort => supportedEfforts.includes(effort));
|
|
322
|
-
// An explicit allowlist is restrictive. If catalog capabilities drift until no configured
|
|
323
|
-
// effort remains supported, omit the target instead of silently broadening JEV's choices.
|
|
324
|
-
return configuredEfforts !== undefined && reasoningEfforts.length === 0 ? undefined : reasoningEfforts;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
322
|
/** Enumerate the current ordinary Combo eligibility set without retaining attempted picks. */
|
|
328
323
|
function eligibleJevComboChoices(
|
|
329
324
|
config: OcxConfig,
|
|
@@ -342,15 +337,15 @@ function eligibleJevComboChoices(
|
|
|
342
337
|
excluded.add(key);
|
|
343
338
|
// The TypeSafe row owns a decision credential, not an inference transport.
|
|
344
339
|
if (pick.target.provider === JEV_PROVIDER_ID) continue;
|
|
345
|
-
const
|
|
346
|
-
if (!
|
|
340
|
+
const identity = jevChoiceEfforts(config, pick.target, combo.decisionQuotaSignals === true);
|
|
341
|
+
if (!identity) continue;
|
|
347
342
|
choices.push({
|
|
348
343
|
pick: { ...pick, attempted: [key] },
|
|
349
344
|
candidate: {
|
|
350
345
|
key,
|
|
351
346
|
provider: pick.target.provider,
|
|
352
347
|
model: pick.target.model,
|
|
353
|
-
|
|
348
|
+
...identity,
|
|
354
349
|
modelProfile: pick.target.modelProfile,
|
|
355
350
|
},
|
|
356
351
|
});
|
|
@@ -389,6 +384,10 @@ export async function executeComboResponses(
|
|
|
389
384
|
if (!combo) {
|
|
390
385
|
return formatErrorResponse(404, "invalid_request_error", `Unknown combo: ${comboId}`);
|
|
391
386
|
}
|
|
387
|
+
// Resolve against the original request once; every bridge candidate receives the same trusted
|
|
388
|
+
// result after its provisional Authorization is removed, including an explicitly keyless result.
|
|
389
|
+
const replayPrincipal = options.replayPrincipal
|
|
390
|
+
?? { clientPrincipalId: resolveContextPrincipal(req, config, options.admission) };
|
|
392
391
|
// PF-07: present only for a Chat combo with `nativeChatCombos` on; otherwise every child
|
|
393
392
|
// takes the bridge below exactly as before.
|
|
394
393
|
const protocolLanes = createComboProtocolLanes({
|
|
@@ -465,6 +464,7 @@ export async function executeComboResponses(
|
|
|
465
464
|
const unavailable = mandatoryResponsesReasoningReplayUnavailable({
|
|
466
465
|
body,
|
|
467
466
|
clientThreadId: reasoningReplayConversationId,
|
|
467
|
+
clientPrincipalId: replayPrincipal.clientPrincipalId,
|
|
468
468
|
providerName: route.providerName,
|
|
469
469
|
provider: route.provider,
|
|
470
470
|
adapterName: route.provider.adapter,
|
|
@@ -607,6 +607,10 @@ export async function executeComboResponses(
|
|
|
607
607
|
eligible: targetEligible,
|
|
608
608
|
now: initialNow,
|
|
609
609
|
});
|
|
610
|
+
// Opt-in JEV session pin (#7525); override routes neither read nor write it, like the lane recall.
|
|
611
|
+
const jevPin = options.compactionRoutingOverride || options.mainHardLockFallbackOverride
|
|
612
|
+
? undefined
|
|
613
|
+
: jevSessionPinScope({ combo, comboId, admission: options.admission, lane: sessionLaneIdFromRequest(req.headers) });
|
|
610
614
|
|
|
611
615
|
if (unreadableEncryptedAgentTask && !pick) {
|
|
612
616
|
pick = await pickWithWait({ now: initialNow });
|
|
@@ -638,13 +642,37 @@ export async function executeComboResponses(
|
|
|
638
642
|
? clientCancelledResponse()
|
|
639
643
|
: comboUnavailable(comboId, undefined, comboExclusionReasons(config, comboId, targetEligible));
|
|
640
644
|
}
|
|
645
|
+
// OCX-11: an ordered failover combo that starts on a later target than its first one, for any
|
|
646
|
+
// reason (cooling down, provider disabled, cached quota spent, ineligible for this request),
|
|
647
|
+
// makes a proactive hop, so the fallback policy decides before anything is sent. Both ends are
|
|
648
|
+
// the resolved destinations, after provider aliases and blockedModelRedirects.
|
|
649
|
+
const primaryTarget = combo.targets[0];
|
|
650
|
+
// An allowed hop is recorded only once its dispatch is admitted below.
|
|
651
|
+
let pendingHopCommit: (() => void) | undefined;
|
|
652
|
+
if (!toolLoopPin && (combo.strategy ?? "failover") === "failover" && primaryTarget
|
|
653
|
+
&& targetKey(primaryTarget) !== targetKey(pick.target)) {
|
|
654
|
+
const primaryCooling = isComboTargetInCooldown(comboId, primaryTarget, initialNow);
|
|
655
|
+
const proactive = guardFallbackHop(config, {
|
|
656
|
+
site: "combo",
|
|
657
|
+
route: `combo/${comboId}`,
|
|
658
|
+
aliases: [requestedModel],
|
|
659
|
+
from: resolvedComboDestination(config, primaryTarget),
|
|
660
|
+
to: resolvedComboDestination(config, pick.target),
|
|
661
|
+
trigger: primaryCooling ? "cooldown" : comboTargetSkipTrigger(config, primaryTarget, initialNow),
|
|
662
|
+
admission: options.admission,
|
|
663
|
+
retryAfter: primaryCooling ? comboCooldownRetryAfterSeconds(comboId, initialNow) ?? null : null,
|
|
664
|
+
now: initialNow,
|
|
665
|
+
});
|
|
666
|
+
if (!proactive.ok) return proactive.response;
|
|
667
|
+
pendingHopCommit = proactive.commit;
|
|
668
|
+
}
|
|
641
669
|
let jevDecision: JevDecision | undefined;
|
|
642
670
|
if (combo.strategy === "jev" && !toolLoopPin) {
|
|
643
671
|
const choices = eligibleJevComboChoices(config, comboId, targetEligible, Date.now());
|
|
644
672
|
const first = choices[0];
|
|
645
673
|
if (!first) {
|
|
646
674
|
return comboUnavailable(comboId, undefined, comboExclusionReasons(config, comboId, target =>
|
|
647
|
-
targetEligible(target) && target.provider !== JEV_PROVIDER_ID && jevChoiceEfforts(config, target) !== undefined));
|
|
675
|
+
targetEligible(target) && target.provider !== JEV_PROVIDER_ID && jevChoiceEfforts(config, target, false) !== undefined));
|
|
648
676
|
}
|
|
649
677
|
const resolvedFailOpenEffort = resolveEffortAtOrBelow(
|
|
650
678
|
"medium",
|
|
@@ -657,8 +685,13 @@ export async function executeComboResponses(
|
|
|
657
685
|
: null,
|
|
658
686
|
};
|
|
659
687
|
const decisionStartedAt = Date.now();
|
|
688
|
+
const pinReuse = jevPin?.reuse(
|
|
689
|
+
choices.map(choice => choice.candidate),
|
|
690
|
+
combo.decisionQuotaSignals === true ? { config, tiers: combo.decisionQuotaTiers } : undefined,
|
|
691
|
+
);
|
|
660
692
|
let decision: JevDecision;
|
|
661
|
-
|
|
693
|
+
if (pinReuse?.decision) decision = pinReuse.decision;
|
|
694
|
+
else try {
|
|
662
695
|
decision = await resolveJevComboDecision({
|
|
663
696
|
body,
|
|
664
697
|
candidates: choices.map(choice => choice.candidate),
|
|
@@ -692,6 +725,7 @@ export async function executeComboResponses(
|
|
|
692
725
|
latencyMs: Math.max(0, Date.now() - decisionStartedAt),
|
|
693
726
|
};
|
|
694
727
|
}
|
|
728
|
+
if (pinReuse?.released) decision = { ...decision, pinReason: pinReuse.released };
|
|
695
729
|
jevDecision = decision;
|
|
696
730
|
const selected = choices.find(choice => choice.candidate.key === decision.targetKey) ?? first;
|
|
697
731
|
pick = { ...selected.pick, attempted: [targetKey(selected.pick.target)] };
|
|
@@ -712,6 +746,8 @@ export async function executeComboResponses(
|
|
|
712
746
|
: {}),
|
|
713
747
|
...(decision.usage ? { usage: decision.usage } : {}),
|
|
714
748
|
...(decision.quota ? { quota: decision.quota } : {}),
|
|
749
|
+
...(decision.pinned ? { pinned: true } : {}),
|
|
750
|
+
...(decision.pinReason ? { pinReason: decision.pinReason } : {}),
|
|
715
751
|
});
|
|
716
752
|
console.debug("[combo] JEV decision", {
|
|
717
753
|
backend: decision.backend,
|
|
@@ -719,6 +755,7 @@ export async function executeComboResponses(
|
|
|
719
755
|
effort: decision.effort,
|
|
720
756
|
gate: decision.gate,
|
|
721
757
|
latencyMs: decision.latencyMs,
|
|
758
|
+
...(decision.pinReason ? { pinReason: decision.pinReason } : {}),
|
|
722
759
|
...(decision.confidence !== undefined ? { confidence: decision.confidence } : {}),
|
|
723
760
|
...(decision.chosenProbability !== undefined
|
|
724
761
|
? { chosenProbability: decision.chosenProbability }
|
|
@@ -731,7 +768,7 @@ export async function executeComboResponses(
|
|
|
731
768
|
const decision = comboRouteDecisionTrace(config, comboId, pick, requestedModel);
|
|
732
769
|
const selectedReason = options.memoryModelPhase
|
|
733
770
|
? memoryModelRouteReason(options.memoryModelPhase)
|
|
734
|
-
: toolLoopPin ? COMBO_TOOL_LOOP_PIN_REASON : undefined;
|
|
771
|
+
: toolLoopPin ? COMBO_TOOL_LOOP_PIN_REASON : jevDecision?.pinned ? JEV_SESSION_PIN_ROUTE_REASON : undefined;
|
|
735
772
|
logCtx.routeDecision = selectedReason
|
|
736
773
|
? { ...decision, selected: { ...decision.selected, reason: selectedReason } }
|
|
737
774
|
: decision;
|
|
@@ -760,6 +797,9 @@ export async function executeComboResponses(
|
|
|
760
797
|
// no-more-targets branch; a budget refusal ends the ladder one iteration later, where that
|
|
761
798
|
// iteration's own `childLog` is already out of scope.
|
|
762
799
|
let lastFailedChildLog: RequestLogContext | undefined;
|
|
800
|
+
// The target behind `lastFailure`, so the next dispatch can tell a hop from a same-target retry.
|
|
801
|
+
let lastFailedTarget: { provider: string; model: string } | undefined;
|
|
802
|
+
let lastFailedRetryAfter: string | undefined;
|
|
763
803
|
// The exhausted-combo mapping below runs outside the loop, where `failure.upstreamCode`
|
|
764
804
|
// is gone, so carry the loop's own classification decision instead of re-deriving a
|
|
765
805
|
// weaker one from the status alone (#4149).
|
|
@@ -812,7 +852,45 @@ export async function executeComboResponses(
|
|
|
812
852
|
while (pick) {
|
|
813
853
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
814
854
|
const firstComboTarget = comboTargetsDispatched === 0;
|
|
855
|
+
// The physical destination after provider aliases and blockedModelRedirects: hops are
|
|
856
|
+
// classified by where the request really goes, not by the selector the combo names.
|
|
815
857
|
const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
858
|
+
// The hard-lock rewrite may have previewed a different random pick. Enforce this actual
|
|
859
|
+
// first destination against the locked source, after aliases and redirects resolve.
|
|
860
|
+
if (firstComboTarget && options.mainHardLockFallbackOverride) {
|
|
861
|
+
const hop = guardFallbackHop(config, {
|
|
862
|
+
site: "combo",
|
|
863
|
+
route: `combo/${comboId}`,
|
|
864
|
+
aliases: [requestedModel],
|
|
865
|
+
from: { provider: OPENAI_CODEX_PROVIDER_ID, model: options.mainHardLockFallbackOverride.sourceModel },
|
|
866
|
+
to: { provider: targetRoute.providerName, model: targetRoute.modelId },
|
|
867
|
+
trigger: "hard_lock",
|
|
868
|
+
admission: options.admission,
|
|
869
|
+
});
|
|
870
|
+
if (!hop.ok) return hop.response;
|
|
871
|
+
const declaredCommit = pendingHopCommit;
|
|
872
|
+
pendingHopCommit = declaredCommit ? () => { declaredCommit(); hop.commit(); } : hop.commit;
|
|
873
|
+
}
|
|
874
|
+
// OCX-11: every later dispatch after a failure is a hop when the target changed. One check
|
|
875
|
+
// here covers the ordinary failover pick and the recovery picks alike.
|
|
876
|
+
if (!firstComboTarget && lastFailure && lastFailedTarget) {
|
|
877
|
+
const hop = guardFallbackHop(config, {
|
|
878
|
+
site: "combo",
|
|
879
|
+
route: `combo/${comboId}`,
|
|
880
|
+
aliases: [requestedModel],
|
|
881
|
+
from: lastFailedTarget,
|
|
882
|
+
to: { provider: targetRoute.providerName, model: targetRoute.modelId },
|
|
883
|
+
trigger: fallbackTriggerForStatus(lastFailure.status),
|
|
884
|
+
admission: options.admission,
|
|
885
|
+
failureStatus: lastFailure.status,
|
|
886
|
+
retryAfter: lastFailedRetryAfter ?? null,
|
|
887
|
+
});
|
|
888
|
+
if (!hop.ok) {
|
|
889
|
+
if (lastFailedChildLog) adoptFailedChildLog(lastFailedChildLog);
|
|
890
|
+
return hop.response;
|
|
891
|
+
}
|
|
892
|
+
pendingHopCommit = hop.commit;
|
|
893
|
+
}
|
|
816
894
|
// The inherited spend tracker observes this parent log, before the child has its own label.
|
|
817
895
|
logCtx.spendPoolId = targetRoute.providerName;
|
|
818
896
|
// Combo prepays before its child normalizes the route; the parent tracker must
|
|
@@ -851,6 +929,9 @@ export async function executeComboResponses(
|
|
|
851
929
|
// an earlier one already recorded one.
|
|
852
930
|
return exhaustedFailure();
|
|
853
931
|
}
|
|
932
|
+
// Admitted: only now is a guarded hop to this target recorded as taken.
|
|
933
|
+
pendingHopCommit?.();
|
|
934
|
+
pendingHopCommit = undefined;
|
|
854
935
|
const initialSend = hopDecision?.allowed ? { permit: hopDecision.permit, producerOwned: false } : undefined;
|
|
855
936
|
try {
|
|
856
937
|
comboTargetsDispatched += 1;
|
|
@@ -946,6 +1027,7 @@ export async function executeComboResponses(
|
|
|
946
1027
|
&& provider && provider.disabled !== true
|
|
947
1028
|
&& currentCombo?.targets.some(target => targetKey(target) === targetKey(completedTarget))) {
|
|
948
1029
|
rememberComboForLane(sessionLaneIdFromRequest(req.headers), comboId, completedTarget, model, writerGeneration);
|
|
1030
|
+
jevPin?.complete(targetKey(completedTarget), initialJevDecision, writerGeneration);
|
|
949
1031
|
}
|
|
950
1032
|
options.onResponseComplete?.(model);
|
|
951
1033
|
},
|
|
@@ -958,6 +1040,8 @@ export async function executeComboResponses(
|
|
|
958
1040
|
if (childLog.terminalIncompleteReason !== undefined) logCtx.terminalIncompleteReason = childLog.terminalIncompleteReason;
|
|
959
1041
|
if (childLog.terminalErrorCode !== undefined) logCtx.terminalErrorCode = childLog.terminalErrorCode;
|
|
960
1042
|
if (childLog.upstreamError !== undefined) logCtx.upstreamError = childLog.upstreamError;
|
|
1043
|
+
// A committed stream that ends failed is a target failure too; incomplete is an answer.
|
|
1044
|
+
if (status === "failed") jevPin?.forget();
|
|
961
1045
|
options.onNativePassthroughTerminal?.(status);
|
|
962
1046
|
},
|
|
963
1047
|
});
|
|
@@ -997,6 +1081,7 @@ export async function executeComboResponses(
|
|
|
997
1081
|
},
|
|
998
1082
|
}) : await requestDispatchers.handleResponses(childRequest, config, childLog, {
|
|
999
1083
|
...options,
|
|
1084
|
+
replayPrincipal,
|
|
1000
1085
|
// A bridge child is a concrete route; the native source belongs to this loop only.
|
|
1001
1086
|
...(options.protocolSource ? { protocolSource: undefined } : {}),
|
|
1002
1087
|
// After the spread: the child must run on THIS target's ladder, not on the holder the
|
|
@@ -1031,6 +1116,7 @@ export async function executeComboResponses(
|
|
|
1031
1116
|
retainCancelledAttempt();
|
|
1032
1117
|
return clientCancelledResponse();
|
|
1033
1118
|
}
|
|
1119
|
+
jevPin?.forget();
|
|
1034
1120
|
finishRequestAttempt(attempt, 502, Date.now() - started, childLog.usage);
|
|
1035
1121
|
throw error;
|
|
1036
1122
|
}
|
|
@@ -1055,6 +1141,7 @@ export async function executeComboResponses(
|
|
|
1055
1141
|
retainCancelledAttempt();
|
|
1056
1142
|
return clientCancelledResponse();
|
|
1057
1143
|
}
|
|
1144
|
+
jevPin?.forget();
|
|
1058
1145
|
finishRequestAttempt(attempt, 502, Date.now() - started, childLog.usage);
|
|
1059
1146
|
throw error;
|
|
1060
1147
|
}
|
|
@@ -1111,6 +1198,7 @@ export async function executeComboResponses(
|
|
|
1111
1198
|
retainCancelledAttempt();
|
|
1112
1199
|
return clientCancelledResponse();
|
|
1113
1200
|
}
|
|
1201
|
+
jevPin?.forget();
|
|
1114
1202
|
finishRequestAttempt(attempt, 502, Date.now() - started, childLog.usage);
|
|
1115
1203
|
throw error;
|
|
1116
1204
|
}
|
|
@@ -1142,11 +1230,16 @@ export async function executeComboResponses(
|
|
|
1142
1230
|
}
|
|
1143
1231
|
attemptRetained = true;
|
|
1144
1232
|
lastFailure = failure.response;
|
|
1233
|
+
// A real target failure ends the session pin; the next turn is decided afresh (#7525).
|
|
1234
|
+
jevPin?.forget();
|
|
1145
1235
|
if (!quotaFailure && (lastFailure.status === 429 || lastFailure.status === 402)) {
|
|
1146
1236
|
quotaFailure = lastFailure;
|
|
1147
1237
|
quotaFailedChildLog = childLog;
|
|
1148
1238
|
}
|
|
1149
1239
|
lastFailedChildLog = childLog;
|
|
1240
|
+
// The failed child's resolved destination, so the next hop compares physical targets.
|
|
1241
|
+
lastFailedTarget = { provider: targetRoute.providerName, model: targetRoute.modelId };
|
|
1242
|
+
lastFailedRetryAfter = failure.retryAfter;
|
|
1150
1243
|
// A replacement that answers 200 is unmarked, and its zero-output failure only exists once
|
|
1151
1244
|
// preflight has rebuilt the stream as a fresh Response. A spent grant never hops: a status the
|
|
1152
1245
|
// client would resend becomes the refusal, and anything else reaches the client as it is.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
2
2
|
import { carryResponseToolOwnership } from "./response-tool-ownership";
|
|
3
3
|
import { finalizeTranslatorBudgetResponse } from "../../lib/translator-budget";
|
|
4
|
-
import { isNonReplayableResponse, markResponseNonReplayable } from "../../lib/upstream-retry";
|
|
4
|
+
import { carryReplayRefusal, isNonReplayableResponse, markResponseNonReplayable } from "../../lib/upstream-retry";
|
|
5
5
|
import {
|
|
6
6
|
isNativePassthroughSseResponse,
|
|
7
7
|
markNativePassthroughSseResponse,
|
|
@@ -76,7 +76,7 @@ export function finalizeAccountLease(response: Response, release: () => void, on
|
|
|
76
76
|
if (isEagerRelaySseResponse(response)) markEagerRelaySseResponse(wrapped);
|
|
77
77
|
if (isPreinspectedJsonResponse(response)) markPreinspectedJsonResponse(wrapped);
|
|
78
78
|
if (runTurnAdapterSseResponses.has(response)) runTurnAdapterSseResponses.add(wrapped);
|
|
79
|
-
return carryResponseToolOwnership(response, wrapped);
|
|
79
|
+
return carryResponseToolOwnership(response, carryReplayRefusal(response, wrapped));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
|
|
@@ -358,14 +358,17 @@ export async function applyFinalRouteRequestNormalization(args: {
|
|
|
358
358
|
|
|
359
359
|
{
|
|
360
360
|
const { nativeEffortClamp, shouldApplyNativeEffortClamp } = await import("../../codex/catalog");
|
|
361
|
+
const raw = parsed._rawBody as { reasoning?: { effort?: string } } | undefined;
|
|
362
|
+
// The parser folds `ultra` to `max` only in options.reasoning; the passthrough adapter
|
|
363
|
+
// serializes the raw body, so a raw `ultra` must reach the clamp itself (#7590).
|
|
364
|
+
const rawUltra = raw?.reasoning && typeof raw.reasoning === "object" && raw.reasoning.effort === "ultra";
|
|
361
365
|
const clamped = shouldApplyNativeEffortClamp(route.providerName, route.provider, finalSelectedModelId)
|
|
362
|
-
? nativeEffortClamp(route.modelId, parsed.options.reasoning)
|
|
366
|
+
? nativeEffortClamp(route.modelId, rawUltra ? "ultra" : parsed.options.reasoning)
|
|
363
367
|
: null;
|
|
364
368
|
if (clamped) {
|
|
365
369
|
parsed.options.reasoning = clamped;
|
|
366
|
-
const raw = parsed._rawBody as { reasoning?: { effort?: string } } | undefined;
|
|
367
370
|
if (raw?.reasoning && typeof raw.reasoning === "object") raw.reasoning.effort = clamped;
|
|
368
|
-
logCtx.requestedEffort = `${logCtx.requestedEffort ?? "max"}->${clamped}`;
|
|
371
|
+
logCtx.requestedEffort = `${logCtx.requestedEffort ?? (rawUltra ? "ultra" : "max")}->${clamped}`;
|
|
369
372
|
}
|
|
370
373
|
}
|
|
371
374
|
// Chat ingress cannot strip effort against a provisional policy pick. Apply the
|
|
@@ -547,6 +547,7 @@ export async function attemptOpaqueBlobRecovery<Failure = Response>(
|
|
|
547
547
|
const rejectedScope = args.parsed._reasoningReplayScope
|
|
548
548
|
? {
|
|
549
549
|
clientThreadId: args.parsed._reasoningReplayScope.clientThreadId,
|
|
550
|
+
clientPrincipalId: args.parsed._reasoningReplayScope.clientPrincipalId,
|
|
550
551
|
...(args.parsed._reasoningReplayScope.current
|
|
551
552
|
? { current: { ...args.parsed._reasoningReplayScope.current } }
|
|
552
553
|
: {}),
|
|
@@ -128,6 +128,11 @@ export interface HandleResponsesOptions {
|
|
|
128
128
|
* which is why it refused the whole env_key flow instead of serving it.
|
|
129
129
|
*/
|
|
130
130
|
admission?: DataPlaneAdmission;
|
|
131
|
+
/**
|
|
132
|
+
* Internal caller identity captured before Combo strips provisional credentials. An existing
|
|
133
|
+
* holder with no principal preserves keyless replay; never populate it from client data.
|
|
134
|
+
*/
|
|
135
|
+
replayPrincipal?: Readonly<{ clientPrincipalId: string | undefined }>;
|
|
131
136
|
/** Called at most once after the complete client body is read and accepted for dispatch. */
|
|
132
137
|
onRequestBodyRead?: () => void;
|
|
133
138
|
/** Internal handoff for retry wrappers that must reuse the already-accounted request body. */
|
|
@@ -344,6 +344,7 @@ function routeReasoningReplayIdentity(args: {
|
|
|
344
344
|
export function mandatoryResponsesReasoningReplayUnavailable(args: {
|
|
345
345
|
body: unknown;
|
|
346
346
|
clientThreadId: string | undefined;
|
|
347
|
+
clientPrincipalId?: string;
|
|
347
348
|
providerName: string;
|
|
348
349
|
provider: OcxProviderConfig;
|
|
349
350
|
adapterName: string;
|
|
@@ -359,7 +360,7 @@ export function mandatoryResponsesReasoningReplayUnavailable(args: {
|
|
|
359
360
|
) return false;
|
|
360
361
|
|
|
361
362
|
const current = routeReasoningReplayIdentity(args);
|
|
362
|
-
return reasoningReplayServingIdentityChanged({ clientThreadId, current });
|
|
363
|
+
return reasoningReplayServingIdentityChanged({ clientThreadId, clientPrincipalId: args.clientPrincipalId, current });
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
|
|
@@ -265,13 +265,13 @@ export function hasUnreadableEncryptedAgentTask(input: unknown): boolean {
|
|
|
265
265
|
// codex-rs appends one agent_message (any of the four codex-rs message types) at the
|
|
266
266
|
// current input tail. Historical
|
|
267
267
|
// agent messages may be adjacent in full-history bodies; they must not poison the
|
|
268
|
-
// later task. compaction_trigger/additional_tools are trailing
|
|
269
|
-
// a newer user turn.
|
|
268
|
+
// later task. compaction_trigger/additional_tools/configuration_update are trailing
|
|
269
|
+
// metadata rather than a newer user turn; Codex may record an effort update after the task.
|
|
270
270
|
let index = input.length - 1;
|
|
271
271
|
while (index >= 0) {
|
|
272
272
|
const item = input[index];
|
|
273
273
|
const type = item && typeof item === "object" ? (item as { type?: unknown }).type : undefined;
|
|
274
|
-
if (type !== "compaction_trigger" && type !== "additional_tools") break;
|
|
274
|
+
if (type !== "compaction_trigger" && type !== "additional_tools" && type !== "configuration_update") break;
|
|
275
275
|
index -= 1;
|
|
276
276
|
}
|
|
277
277
|
const item = input[index];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getCombo, targetKey, type JevCandidate } from "../../combos";
|
|
2
|
+
import { isCodexReasoningEffort } from "../../reasoning-effort";
|
|
3
|
+
import { routeConcreteModel } from "../../router";
|
|
4
|
+
import { supportedLadderFor } from "../effort-policy";
|
|
5
|
+
import type { OcxComboDefaultEffort, OcxConfig } from "../../types";
|
|
6
|
+
|
|
7
|
+
/** A JEV target's effort choices plus, when quota signals are on, its resolved native identity. */
|
|
8
|
+
export interface JevChoiceIdentity {
|
|
9
|
+
reasoningEfforts: OcxComboDefaultEffort[];
|
|
10
|
+
quotaIdentity?: JevCandidate["quotaIdentity"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** A target's JEV effort choices, or undefined when an explicit allowlist has none still supported. */
|
|
14
|
+
export function jevChoiceEfforts(
|
|
15
|
+
config: OcxConfig,
|
|
16
|
+
target: NonNullable<ReturnType<typeof getCombo>>["targets"][number],
|
|
17
|
+
quotaSignals: boolean,
|
|
18
|
+
): JevChoiceIdentity | undefined {
|
|
19
|
+
let ladder: string[] | undefined;
|
|
20
|
+
// null tells the quota reader the route did not resolve, so it reads unknown rather than the configured alias.
|
|
21
|
+
let quotaIdentity: JevCandidate["quotaIdentity"] = quotaSignals ? null : undefined;
|
|
22
|
+
try {
|
|
23
|
+
const route = routeConcreteModel(config, targetKey(target));
|
|
24
|
+
ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
25
|
+
if (quotaSignals) quotaIdentity = { provider: route.providerName, model: route.modelId };
|
|
26
|
+
} catch {
|
|
27
|
+
// Preserve the existing routing-failure surface. Unknown capability becomes the explicit
|
|
28
|
+
// no-effort choice rather than broadening JEV's effort allowlist.
|
|
29
|
+
ladder = undefined;
|
|
30
|
+
}
|
|
31
|
+
const supportedEfforts = (ladder ?? []).filter(isCodexReasoningEffort) as OcxComboDefaultEffort[];
|
|
32
|
+
const configuredEfforts = target.reasoningEfforts;
|
|
33
|
+
const reasoningEfforts = configuredEfforts === undefined
|
|
34
|
+
? supportedEfforts
|
|
35
|
+
: configuredEfforts.filter(effort => supportedEfforts.includes(effort));
|
|
36
|
+
// An explicit allowlist is restrictive. If catalog capabilities drift until no configured
|
|
37
|
+
// effort remains supported, omit the target instead of silently broadening JEV's choices.
|
|
38
|
+
return configuredEfforts !== undefined && reasoningEfforts.length === 0 ? undefined : { reasoningEfforts, quotaIdentity };
|
|
39
|
+
}
|