@bitkyc08/opencodex 2.58.0 → 2.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
package/src/router.ts
CHANGED
|
@@ -15,10 +15,12 @@ import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
|
15
15
|
import { redactSecretString, redactUrlForLog } from "./lib/redact";
|
|
16
16
|
import {
|
|
17
17
|
PROVIDER_REGISTRY,
|
|
18
|
-
mergeRegistryStaticHeaders,
|
|
19
18
|
providerCodexAccountMode,
|
|
20
|
-
|
|
19
|
+
registryEntryForProviderDestination,
|
|
21
20
|
} from "./providers/registry";
|
|
21
|
+
// Imported from the module directly rather than through the registry facade, which is at its
|
|
22
|
+
// file-size cap.
|
|
23
|
+
import { registryModelIdKeys } from "./providers/registry/model-ids";
|
|
22
24
|
import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
|
|
23
25
|
import { cloneFastWire } from "./providers/fastwire";
|
|
24
26
|
import {
|
|
@@ -29,11 +31,10 @@ import {
|
|
|
29
31
|
isCanonicalOpenAiForwardProvider,
|
|
30
32
|
LEGACY_CHATGPT_PROVIDER_ID,
|
|
31
33
|
LEGACY_OPENAI_MULTI_PROVIDER_ID,
|
|
32
|
-
OPENAI_API_PROVIDER_ID,
|
|
33
34
|
OPENAI_CODEX_PROVIDER_ID,
|
|
34
35
|
} from "./providers/openai-tiers";
|
|
35
36
|
import { decodeRoutedModelIdOrThrow, encodeRoutedModelId } from "./providers/slug-codec";
|
|
36
|
-
import { resolveModelAlias } from "./providers/default-aliases";
|
|
37
|
+
import { effectiveProviderAliasDecision, resolveModelAlias } from "./providers/default-aliases";
|
|
37
38
|
import { resolveBlockedModelRedirect } from "./lib/shadow-call";
|
|
38
39
|
import { getStaleCached } from "./codex/model-cache";
|
|
39
40
|
import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
import { getRoutingProfile, resolvePolicyProfileId, POLICY_NAMESPACE } from "./routing/profile";
|
|
47
48
|
import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
|
|
48
49
|
import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
|
|
50
|
+
import { resolveModelPolicy, type ResolvedModelPolicy } from "./providers/resolved-model-policy";
|
|
49
51
|
|
|
50
52
|
export class UnknownRoutingPolicyError extends Error {
|
|
51
53
|
constructor(readonly profileId: string) {
|
|
@@ -69,6 +71,8 @@ export interface RouteResult {
|
|
|
69
71
|
providerName: string;
|
|
70
72
|
provider: OcxProviderConfig;
|
|
71
73
|
modelId: string;
|
|
74
|
+
/** Immutable static policy for the current final wire model. */
|
|
75
|
+
staticPolicy: ResolvedModelPolicy;
|
|
72
76
|
/** Which deterministic routing path produced this route (RI-01). */
|
|
73
77
|
routeKind: RouteDecisionKind;
|
|
74
78
|
/** Stable wire reason code for the selected route (RI-01). */
|
|
@@ -83,6 +87,29 @@ export interface RouteResult {
|
|
|
83
87
|
routeDecision?: RouteDecisionTraceV1;
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
export function captureRouteStaticPolicy(
|
|
91
|
+
providerName: string,
|
|
92
|
+
modelId: string,
|
|
93
|
+
provider: OcxProviderConfig,
|
|
94
|
+
effectiveAlias?: string | null,
|
|
95
|
+
inboundWire: "responses" | "chat" | "anthropic" = "responses",
|
|
96
|
+
): ResolvedModelPolicy {
|
|
97
|
+
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
98
|
+
const transportMatchedRegistry = !!registryEntry
|
|
99
|
+
&& providerMatchesRegistryTransportWithStaticGuards(providerName, provider);
|
|
100
|
+
return resolveModelPolicy({
|
|
101
|
+
providerName,
|
|
102
|
+
modelId,
|
|
103
|
+
provider,
|
|
104
|
+
registryEntry,
|
|
105
|
+
transportMatchedRegistry,
|
|
106
|
+
inboundWire,
|
|
107
|
+
modelCapabilities: provider.modelCapabilities?.[modelId],
|
|
108
|
+
...(provider.authMode ? { effectiveAuth: { authMode: provider.authMode } } : {}),
|
|
109
|
+
...(effectiveAlias !== undefined ? { effectiveAlias } : {}),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
|
|
87
114
|
{
|
|
88
115
|
providerNames: ["anthropic"],
|
|
@@ -117,19 +144,11 @@ export function knownModelIdsForProvider(
|
|
|
117
144
|
: undefined;
|
|
118
145
|
for (const id of registry?.models ?? []) ids.add(id);
|
|
119
146
|
// Registry model-keyed hint maps double as known native ids (e.g. NVIDIA carries no
|
|
120
|
-
// static models list but names `moonshotai/kimi-k2.6` in its effort/window maps).
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
registry?.modelDefaultReasoningEfforts,
|
|
126
|
-
registry?.modelReasoningEffortMap,
|
|
127
|
-
registry?.modelMaxOutputTokens,
|
|
128
|
-
registry?.modelSupportsServiceTier,
|
|
129
|
-
registry?.modelSupportsVerbosity,
|
|
130
|
-
]) {
|
|
131
|
-
for (const id of Object.keys(map ?? {})) ids.add(id);
|
|
132
|
-
}
|
|
147
|
+
// static models list but names `moonshotai/kimi-k2.6` in its effort/window maps). Which
|
|
148
|
+
// maps count is classified by the registry itself rather than listed here, so an id declared
|
|
149
|
+
// only in a map this function forgot is no longer undecodable, and a new model-keyed field
|
|
150
|
+
// fails typecheck until its keys are given a meaning.
|
|
151
|
+
for (const id of registry ? registryModelIdKeys(registry) : []) ids.add(id);
|
|
133
152
|
for (const cached of getStaleCached(provName) ?? []) ids.add(cached.id);
|
|
134
153
|
for (const model of config?.customModels ?? []) {
|
|
135
154
|
if (model.provider === provName && model.modelId) ids.add(model.modelId);
|
|
@@ -137,66 +156,6 @@ export function knownModelIdsForProvider(
|
|
|
137
156
|
return [...ids];
|
|
138
157
|
}
|
|
139
158
|
|
|
140
|
-
// Merge registry-default effort maps under user values so built-in provider configs can
|
|
141
|
-
// carry real upstream aliases without a disk migration. User overrides win per-key.
|
|
142
|
-
function mergeRecord(
|
|
143
|
-
seed: Record<string, string> | undefined,
|
|
144
|
-
user: Record<string, string> | undefined,
|
|
145
|
-
): Record<string, string> | undefined {
|
|
146
|
-
if (!seed && !user) return undefined;
|
|
147
|
-
return { ...(seed ?? {}), ...(user ?? {}) };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function mergeNestedRecord(
|
|
151
|
-
seed: Record<string, Record<string, string>> | undefined,
|
|
152
|
-
user: Record<string, Record<string, string>> | undefined,
|
|
153
|
-
): Record<string, Record<string, string>> | undefined {
|
|
154
|
-
if (!seed && !user) return undefined;
|
|
155
|
-
const out: Record<string, Record<string, string>> = {};
|
|
156
|
-
for (const [key, value] of Object.entries(seed ?? {})) out[key] = { ...value };
|
|
157
|
-
for (const [key, value] of Object.entries(user ?? {})) out[key] = { ...(out[key] ?? {}), ...value };
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function mergeStringArray(
|
|
162
|
-
seed: string[] | undefined,
|
|
163
|
-
user: string[] | undefined,
|
|
164
|
-
): string[] | undefined {
|
|
165
|
-
if (!seed && !user) return undefined;
|
|
166
|
-
return [...new Set([...(seed ?? []), ...(user ?? [])])];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function mergeRecordFill<T>(
|
|
170
|
-
seed: Record<string, T> | undefined,
|
|
171
|
-
user: Record<string, T> | undefined,
|
|
172
|
-
): Record<string, T> | undefined {
|
|
173
|
-
if (!seed && !user) return undefined;
|
|
174
|
-
return { ...(seed ?? {}), ...(user ?? {}) };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function mergePositiveNumberCaps(
|
|
178
|
-
seed: Record<string, number> | undefined,
|
|
179
|
-
user: Record<string, number> | undefined,
|
|
180
|
-
): Record<string, number> | undefined {
|
|
181
|
-
if (!seed && !user) return undefined;
|
|
182
|
-
const out = { ...(seed ?? {}) };
|
|
183
|
-
for (const [key, value] of Object.entries(user ?? {})) {
|
|
184
|
-
out[key] = typeof out[key] === "number" ? Math.min(out[key]!, value) : value;
|
|
185
|
-
}
|
|
186
|
-
return out;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function mergeStringArrayRecord(
|
|
190
|
-
seed: Record<string, string[]> | undefined,
|
|
191
|
-
user: Record<string, string[]> | undefined,
|
|
192
|
-
): Record<string, string[]> | undefined {
|
|
193
|
-
if (!seed && !user) return undefined;
|
|
194
|
-
const out: Record<string, string[]> = {};
|
|
195
|
-
for (const [key, value] of Object.entries(seed ?? {})) out[key] = [...value];
|
|
196
|
-
for (const [key, value] of Object.entries(user ?? {})) out[key] = [...value];
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
159
|
/** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
|
|
201
160
|
function isSameEndpoint(a: string, b: string): boolean {
|
|
202
161
|
return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
|
|
@@ -302,7 +261,20 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
302
261
|
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
303
262
|
if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
|
|
304
263
|
assertProviderDestinationAllowed(providerName, provider);
|
|
305
|
-
|
|
264
|
+
// A row whose adapter no longer matches its registry entry still reaches the Responses
|
|
265
|
+
// adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
|
|
266
|
+
// saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
|
|
267
|
+
// to the provider-wide wire, so it is filled here too; without it that continuation forwards
|
|
268
|
+
// the reasoning item the upstream answers 400 to. Destination matching refuses templated and
|
|
269
|
+
// overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
|
|
270
|
+
const destination = registryEntryForProviderDestination(provider);
|
|
271
|
+
return {
|
|
272
|
+
...provider,
|
|
273
|
+
apiKey: usableResolvedApiKey(provider.apiKey),
|
|
274
|
+
...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
|
|
275
|
+
? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
|
|
276
|
+
: {}),
|
|
277
|
+
};
|
|
306
278
|
}
|
|
307
279
|
const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
|
|
308
280
|
const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
|
|
@@ -317,46 +289,42 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
317
289
|
: registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
|
|
318
290
|
? registryEntry.authKind
|
|
319
291
|
: provider.authMode === "forward" ? undefined : provider.authMode;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
:
|
|
327
|
-
|
|
292
|
+
const staticPolicy = resolveModelPolicy({
|
|
293
|
+
providerName,
|
|
294
|
+
modelId: "__provider_static__",
|
|
295
|
+
provider,
|
|
296
|
+
registryEntry,
|
|
297
|
+
transportMatchedRegistry: true,
|
|
298
|
+
...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
|
|
299
|
+
}).provider;
|
|
300
|
+
const reasoningEffortMap = staticPolicy.reasoningEffortMap;
|
|
301
|
+
const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
|
|
302
|
+
const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
|
|
303
|
+
const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
|
|
304
|
+
const modelContextWindows = staticPolicy.modelContextWindows;
|
|
305
|
+
const modelInputModalities = staticPolicy.modelInputModalities;
|
|
328
306
|
// Registry static headers are documented as applying to every upstream request, so they are
|
|
329
307
|
// filled at resolve time rather than only at seed time: a config written before a header
|
|
330
308
|
// existed, or one carrying any header of its own, would otherwise never receive it. User
|
|
331
309
|
// headers win, matched case-insensitively so an override replaces rather than duplicates.
|
|
332
|
-
const headers =
|
|
333
|
-
const modelMaxInputTokens =
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
const noTopPModels = mergeStringArray(registryEntry.noTopPModels, provider.noTopPModels);
|
|
351
|
-
const noPenaltyModels = mergeStringArray(registryEntry.noPenaltyModels, provider.noPenaltyModels);
|
|
352
|
-
const noJsonSchemaModels = mergeStringArray(registryEntry.noJsonSchemaModels, provider.noJsonSchemaModels);
|
|
353
|
-
const autoToolChoiceOnlyModels = mergeStringArray(registryEntry.autoToolChoiceOnlyModels, provider.autoToolChoiceOnlyModels);
|
|
354
|
-
const preserveReasoningContentModels = mergeStringArray(registryEntry.preserveReasoningContentModels, provider.preserveReasoningContentModels);
|
|
355
|
-
const requiresReasoningPlaceholderModels = mergeStringArray(registryEntry.requiresReasoningPlaceholderModels, provider.requiresReasoningPlaceholderModels);
|
|
356
|
-
const reasoningSplitModels = mergeStringArray(registryEntry.reasoningSplitModels, provider.reasoningSplitModels);
|
|
357
|
-
const reasoningDetailsModels = mergeStringArray(registryEntry.reasoningDetailsModels, provider.reasoningDetailsModels);
|
|
358
|
-
const thinkingToggleModels = mergeStringArray(registryEntry.thinkingToggleModels, provider.thinkingToggleModels);
|
|
359
|
-
const thinkingBudgetModels = mergeStringArray(registryEntry.thinkingBudgetModels, provider.thinkingBudgetModels);
|
|
310
|
+
const headers = staticPolicy.headers;
|
|
311
|
+
const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
|
|
312
|
+
const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
|
|
313
|
+
const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
|
|
314
|
+
const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
|
|
315
|
+
const noVisionModels = staticPolicy.noVisionModels;
|
|
316
|
+
const noReasoningModels = staticPolicy.noReasoningModels;
|
|
317
|
+
const noTemperatureModels = staticPolicy.noTemperatureModels;
|
|
318
|
+
const noTopPModels = staticPolicy.noTopPModels;
|
|
319
|
+
const noPenaltyModels = staticPolicy.noPenaltyModels;
|
|
320
|
+
const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
|
|
321
|
+
const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
|
|
322
|
+
const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
|
|
323
|
+
const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
|
|
324
|
+
const reasoningSplitModels = staticPolicy.reasoningSplitModels;
|
|
325
|
+
const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
|
|
326
|
+
const thinkingToggleModels = staticPolicy.thinkingToggleModels;
|
|
327
|
+
const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
|
|
360
328
|
const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
|
|
361
329
|
const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
|
|
362
330
|
const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
|
|
@@ -417,6 +385,9 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
417
385
|
...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
|
|
418
386
|
? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
|
|
419
387
|
: {}),
|
|
388
|
+
...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
|
|
389
|
+
? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
|
|
390
|
+
: {}),
|
|
420
391
|
// The request path resolves through routedProviderConfig() and never calls
|
|
421
392
|
// enrichProviderFromRegistry(), so a saved provider row written before the
|
|
422
393
|
// registry learned this flag must be backfilled here or route.provider never
|
|
@@ -565,10 +536,15 @@ function routeResult(
|
|
|
565
536
|
const effectiveModelId = redirected ?? modelId;
|
|
566
537
|
const effectiveRouteReason = redirected ? "blocked-model-redirect" : routeReason;
|
|
567
538
|
const codexAccountMode = providerCodexAccountMode(providerName, provider);
|
|
539
|
+
const routedProvider = routedProviderConfig(providerName, provider);
|
|
540
|
+
const effectiveAlias = config
|
|
541
|
+
? effectiveProviderAliasDecision(providerName, provider, config)
|
|
542
|
+
: undefined;
|
|
568
543
|
return {
|
|
569
544
|
providerName,
|
|
570
|
-
provider:
|
|
545
|
+
provider: routedProvider,
|
|
571
546
|
modelId: effectiveModelId,
|
|
547
|
+
staticPolicy: captureRouteStaticPolicy(providerName, effectiveModelId, routedProvider, effectiveAlias),
|
|
572
548
|
routeKind,
|
|
573
549
|
routeReason: effectiveRouteReason,
|
|
574
550
|
...(codexAccountMode ? { codexAccountMode } : {}),
|
|
@@ -200,6 +200,12 @@ export function resolveProductionBehaviorValues(
|
|
|
200
200
|
: reasoningEfforts.length > 0 || Boolean(registryEntry?.reasoningEfforts),
|
|
201
201
|
),
|
|
202
202
|
"reasoning.efforts": behaviorRow("provider_config", reasoningEfforts),
|
|
203
|
+
// Whether the configured ladder above is what the WIRE uses, or only what the catalog
|
|
204
|
+
// advertises. An adapter shipping its own per-model table (command-code) otherwise wins for
|
|
205
|
+
// models it knows, and it also repairs a refused rung by replaying without the effort. Two
|
|
206
|
+
// routes that disagree about this send different bytes and recover differently, so they are
|
|
207
|
+
// not the same compatibility subject and must not share evidence.
|
|
208
|
+
"reasoning.effortsAuthoritative": behaviorRow("provider_config", effective.modelReasoningEffortsAuthoritative === true),
|
|
203
209
|
"reasoning.defaultEffort": behaviorRow("provider_config", defaultReasoningEffort),
|
|
204
210
|
"reasoning.effortMap": behaviorRow("provider_config", reasoningEffortMap),
|
|
205
211
|
"reasoning.wireFormat": behaviorRow("provider_config", effective.reasoningWireFormat ?? null),
|
|
@@ -209,6 +215,9 @@ export function resolveProductionBehaviorValues(
|
|
|
209
215
|
}),
|
|
210
216
|
"reasoning.replayMode": behaviorRow("provider_config", {
|
|
211
217
|
preserveResponses: effective.preserveResponsesReasoningContent === true,
|
|
218
|
+
// Dropping replayed reasoning items changes the continuation body, so two routes that
|
|
219
|
+
// disagree about it are not the same compatibility subject and must not share evidence.
|
|
220
|
+
dropResponses: effective.dropResponsesReasoningItems === true,
|
|
212
221
|
preserveContent: includesModel(effective.preserveReasoningContentModels, modelId),
|
|
213
222
|
placeholder: includesModel(effective.requiresReasoningPlaceholderModels, modelId),
|
|
214
223
|
}),
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
2
2
|
import type { InboundWire } from "../../providers/registry";
|
|
3
|
+
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
4
|
+
import { providerMatchesRegistryTransportWithStaticGuards } from "../../providers/static-model-discovery";
|
|
5
|
+
import { resolveModelPolicy } from "../../providers/resolved-model-policy";
|
|
3
6
|
import { subjectIdForSubject } from "../../lab/digest";
|
|
4
7
|
import { buildRouteSubjectV1 } from "../../lab/subject/route-subject";
|
|
5
8
|
import { buildProtocolSubjectV1 } from "../../lab/subject/protocol-subject";
|
|
@@ -65,7 +68,19 @@ export function resolveCompatibilitySubjectsForInboundWire(
|
|
|
65
68
|
inboundWire: InboundWire,
|
|
66
69
|
configDir?: string,
|
|
67
70
|
): ResolvedPolicyCompatibilitySubjects {
|
|
68
|
-
const
|
|
71
|
+
const registryEntry = getProviderRegistryEntry(providerName);
|
|
72
|
+
const staticPolicy = resolveModelPolicy({
|
|
73
|
+
providerName,
|
|
74
|
+
modelId,
|
|
75
|
+
provider: routed,
|
|
76
|
+
registryEntry,
|
|
77
|
+
transportMatchedRegistry: !!registryEntry
|
|
78
|
+
&& providerMatchesRegistryTransportWithStaticGuards(providerName, routed),
|
|
79
|
+
inboundWire,
|
|
80
|
+
modelCapabilities: routed.modelCapabilities?.[modelId],
|
|
81
|
+
...(routed.authMode ? { effectiveAuth: { authMode: routed.authMode } } : {}),
|
|
82
|
+
});
|
|
83
|
+
const effective = resolveWireProtocolOverride(providerName, modelId, routed, inboundWire, staticPolicy);
|
|
69
84
|
const baseUrl = typeof effective.baseUrl === "string" ? effective.baseUrl.trim() : "";
|
|
70
85
|
const adapter = effective.adapter ?? "openai-responses";
|
|
71
86
|
const inboundProtocol = inboundProtocolForWire(inboundWire);
|
|
@@ -3,6 +3,7 @@ import type { OcxProviderConfig } from "../types";
|
|
|
3
3
|
import { isWirePinnedModel, MODEL_ADAPTER_OVERRIDE_ALLOWED, pinnedWireAdapter } from "../types";
|
|
4
4
|
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
5
5
|
import { type InboundWire, providerModelWireDefault } from "../providers/registry";
|
|
6
|
+
import type { ResolvedModelPolicy } from "../providers/resolved-model-policy";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Resolve the wire a single model should use: a hard pin first, then a configured
|
|
@@ -22,7 +23,15 @@ export function resolveWireProtocolOverride(
|
|
|
22
23
|
modelId: string,
|
|
23
24
|
providerConfig: OcxProviderConfig,
|
|
24
25
|
inbound: InboundWire = "responses",
|
|
26
|
+
staticPolicy?: ResolvedModelPolicy,
|
|
25
27
|
): OcxProviderConfig {
|
|
28
|
+
// RouteResult policy is recaptured for the original inbound protocol whenever routing selects
|
|
29
|
+
// or replaces a destination, so every downstream rebuild consumes the same adapter authority.
|
|
30
|
+
if (staticPolicy) {
|
|
31
|
+
return staticPolicy.model.adapter !== providerConfig.adapter
|
|
32
|
+
? { ...providerConfig, adapter: staticPolicy.model.adapter }
|
|
33
|
+
: providerConfig;
|
|
34
|
+
}
|
|
26
35
|
const pinned = pinnedWireAdapter(providerName, modelId);
|
|
27
36
|
if (pinned && providerConfig.adapter !== pinned) {
|
|
28
37
|
return { ...providerConfig, adapter: pinned };
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { providerRelativeSendPathConfigError } from "../config/provider-relative-send-path";
|
|
1
2
|
import { modelCapabilitiesConfigError } from "../config/provider-validation";
|
|
2
3
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
3
4
|
import { initialModelSelection } from "../providers/initial-model-selection";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
} from "../config/provider-validation";
|
|
30
31
|
import { providerDestinationConfigError } from "../lib/destination-policy";
|
|
31
32
|
import { redactSecretString } from "../lib/redact";
|
|
33
|
+
import { DECLARABLE_HOSTED_TOOL_TYPES } from "../responses/hosted-tool-policy";
|
|
32
34
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
33
35
|
import { providerConfigSeed } from "../providers/derive";
|
|
34
36
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -754,6 +756,10 @@ export function providerManagementConfigError(
|
|
|
754
756
|
}
|
|
755
757
|
const destinationError = providerDestinationConfigError(name, typed);
|
|
756
758
|
if (destinationError) return `provider ${name} ${destinationError}`;
|
|
759
|
+
for (const field of ["responsesPath", "chatCompletionsPath"] as const) {
|
|
760
|
+
const sendPathError = providerRelativeSendPathConfigError(field, raw[field]);
|
|
761
|
+
if (sendPathError) return `provider ${JSON.stringify(redactSecretString(name))} ${sendPathError}`;
|
|
762
|
+
}
|
|
757
763
|
const headersError = providerHeadersConfigError(typed.headers);
|
|
758
764
|
if (headersError) return `provider ${name} ${headersError}`;
|
|
759
765
|
const retryOn429Error = retryOn429PolicyConfigError(raw.retryOn429);
|
|
@@ -793,6 +799,8 @@ export function providerManagementConfigError(
|
|
|
793
799
|
}
|
|
794
800
|
const reasoningSummariesError = booleanRecordConfigError(raw.modelSupportsReasoningSummaries, "modelSupportsReasoningSummaries");
|
|
795
801
|
if (reasoningSummariesError) return `provider ${name} ${reasoningSummariesError}`;
|
|
802
|
+
const suppressSyntheticMaxError = booleanRecordConfigError(raw.modelSuppressSyntheticMax, "modelSuppressSyntheticMax");
|
|
803
|
+
if (suppressSyntheticMaxError) return `provider ${name} ${suppressSyntheticMaxError}`;
|
|
796
804
|
const reasoningSummaryDeliveryError = reasoningSummaryDeliveryRecordConfigError(
|
|
797
805
|
raw.modelReasoningSummaryDelivery,
|
|
798
806
|
raw.modelSupportsReasoningSummaries,
|
|
@@ -834,6 +842,22 @@ export function providerManagementConfigError(
|
|
|
834
842
|
"omitReasoningEffortWithToolsModels",
|
|
835
843
|
);
|
|
836
844
|
if (toolReasoningOptOutError) return `provider ${name} ${toolReasoningOptOutError}`;
|
|
845
|
+
const unsupportedHostedToolsError = nonBlankStringArrayConfigError(
|
|
846
|
+
raw.unsupportedHostedTools,
|
|
847
|
+
"unsupportedHostedTools",
|
|
848
|
+
);
|
|
849
|
+
if (unsupportedHostedToolsError) return `provider ${name} ${unsupportedHostedToolsError}`;
|
|
850
|
+
if (Array.isArray(raw.unsupportedHostedTools)) {
|
|
851
|
+
// Closed vocabulary, same reason as the config schema: an unrecognized name would be
|
|
852
|
+
// stored and then strip nothing, so the operator would keep getting the upstream 400
|
|
853
|
+
// this field exists to prevent.
|
|
854
|
+
const unknownTool = (raw.unsupportedHostedTools as unknown[])
|
|
855
|
+
.find(tool => typeof tool === "string" && !DECLARABLE_HOSTED_TOOL_TYPES.has(tool.trim()));
|
|
856
|
+
if (unknownTool !== undefined) {
|
|
857
|
+
return `provider ${name} unsupportedHostedTools must name only hosted tool types: `
|
|
858
|
+
+ `${[...DECLARABLE_HOSTED_TOOL_TYPES].join(", ")}`;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
837
861
|
const openRouterError = openRouterRoutingConfigError(typed);
|
|
838
862
|
if (openRouterError) return `provider ${name} ${openRouterError}`;
|
|
839
863
|
const vercelError = vercelGatewayRoutingConfigError(typed);
|
|
@@ -918,12 +942,15 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
918
942
|
supportsServiceTier: "editor",
|
|
919
943
|
modelSupportsServiceTier: "editor",
|
|
920
944
|
preserveResponsesReasoningContent: "editor",
|
|
945
|
+
dropResponsesReasoningItems: "editor",
|
|
946
|
+
modelReasoningEffortsAuthoritative: "editor",
|
|
921
947
|
decodesNativeCompactionBlobs: "editor",
|
|
922
948
|
allowEncryptedV2AgentTasks: "editor",
|
|
923
949
|
allowPrivateNetwork: "editor",
|
|
924
950
|
upstreamHttpVersion: "editor",
|
|
925
951
|
upstreamWebsocket: "editor",
|
|
926
952
|
directGeminiWireRenames: "editor",
|
|
953
|
+
googleToolSchemaPolicy: "editor",
|
|
927
954
|
disabled: "editor",
|
|
928
955
|
codexAccountMode: "editor",
|
|
929
956
|
apiKey: "redacted",
|
|
@@ -964,6 +991,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
964
991
|
refreshPolicy: "editor",
|
|
965
992
|
reasoningEfforts: "editor",
|
|
966
993
|
modelReasoningEfforts: "editor",
|
|
994
|
+
modelSuppressSyntheticMax: "editor",
|
|
967
995
|
modelDefaultReasoningEfforts: "editor",
|
|
968
996
|
pinnedReasoningEffort: "editor",
|
|
969
997
|
modelPinnedReasoningEfforts: "editor",
|
|
@@ -979,6 +1007,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
979
1007
|
xaiResponsesDefaultVersion: "runtime",
|
|
980
1008
|
zaiResponsesDefaultVersion: "runtime",
|
|
981
1009
|
supportsResponsesCustomTools: "editor",
|
|
1010
|
+
unsupportedHostedTools: "editor",
|
|
982
1011
|
responsesSnapshotRepair: "editor",
|
|
983
1012
|
webSearchBridge: "editor",
|
|
984
1013
|
reasoningEffortMap: "editor",
|
|
@@ -23,7 +23,7 @@ import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPo
|
|
|
23
23
|
import { redactSecretString } from "../lib/redact";
|
|
24
24
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
25
25
|
import { estimateTokens } from "../lib/token-estimate";
|
|
26
|
-
import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
26
|
+
import { captureRouteStaticPolicy, NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
27
27
|
import { evidenceFromBody } from "../routing/request-evidence";
|
|
28
28
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
29
29
|
import { resolveOpenCodeGoTransport } from "../providers/opencode-go-transport";
|
|
@@ -150,10 +150,18 @@ async function handleChatCompletionsWithBudget(
|
|
|
150
150
|
let chatNativeRoute: ReturnType<typeof routeModel> | null = null;
|
|
151
151
|
try {
|
|
152
152
|
const route = routeModel(config, chatBody.model as string, evidenceFromBody(chatBody));
|
|
153
|
-
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
route.
|
|
153
|
+
// Preserve the routed destination for Go recognition, then settle the wire before
|
|
154
|
+
// deriving protocol-scoped affinity. Recognition must not inspect the flipped adapter.
|
|
155
|
+
const routedProvider = route.provider;
|
|
156
|
+
route.staticPolicy = captureRouteStaticPolicy(
|
|
157
|
+
route.providerName, route.modelId, routedProvider, route.staticPolicy.effectiveAlias, "chat",
|
|
158
|
+
);
|
|
159
|
+
const wireProvider = resolveWireProtocolOverride(route.providerName, route.modelId, routedProvider, "chat", route.staticPolicy);
|
|
160
|
+
route.provider = resolveOpenCodeGoTransport(
|
|
161
|
+
wireProvider,
|
|
162
|
+
getOrAllocateRequestSessionLane(req),
|
|
163
|
+
routedProvider,
|
|
164
|
+
);
|
|
157
165
|
logCtx.model = route.modelId;
|
|
158
166
|
logCtx.providerAdapter = route.provider.adapter;
|
|
159
167
|
logCtx.requestedModel = requestedModel;
|
|
@@ -152,6 +152,8 @@ export function nativeChatSse(
|
|
|
152
152
|
let sawFinish = false;
|
|
153
153
|
let sawDone = false;
|
|
154
154
|
let firstOutput = false;
|
|
155
|
+
let sawTextOutput = false;
|
|
156
|
+
let sawToolCalls = false;
|
|
155
157
|
let settled = false;
|
|
156
158
|
let cancelled = false;
|
|
157
159
|
let cancelledBySignal = false;
|
|
@@ -264,7 +266,7 @@ export function nativeChatSse(
|
|
|
264
266
|
const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
|
|
265
267
|
const finished = choices.some(choice => isRec(choice) && typeof choice.finish_reason === "string" && choice.finish_reason.length > 0);
|
|
266
268
|
if (finished) sawFinish = true;
|
|
267
|
-
const
|
|
269
|
+
const hasText = choices.some(choice => {
|
|
268
270
|
if (!isRec(choice) || !isRec(choice.delta)) return false;
|
|
269
271
|
const delta = choice.delta;
|
|
270
272
|
return (typeof delta.content === "string" && delta.content.length > 0)
|
|
@@ -272,15 +274,22 @@ export function nativeChatSse(
|
|
|
272
274
|
|| (typeof delta.reasoning === "string" && delta.reasoning.length > 0)
|
|
273
275
|
|| (Array.isArray(delta.reasoning_details) && delta.reasoning_details.some(detail =>
|
|
274
276
|
isRec(detail) && typeof detail.text === "string" && detail.text.length > 0))
|
|
275
|
-
|| (typeof delta.refusal === "string" && delta.refusal.length > 0)
|
|
276
|
-
|| (Array.isArray(delta.tool_calls) && delta.tool_calls.some(tool => {
|
|
277
|
-
if (!isRec(tool)) return false;
|
|
278
|
-
const fn = isRec(tool.function) ? tool.function : {};
|
|
279
|
-
return (typeof tool.id === "string" && tool.id.length > 0)
|
|
280
|
-
|| (typeof fn.name === "string" && fn.name.length > 0)
|
|
281
|
-
|| (typeof fn.arguments === "string" && fn.arguments.length > 0);
|
|
282
|
-
}));
|
|
277
|
+
|| (typeof delta.refusal === "string" && delta.refusal.length > 0);
|
|
283
278
|
});
|
|
279
|
+
const hasTool = choices.some(choice => {
|
|
280
|
+
if (!isRec(choice) || !isRec(choice.delta)) return false;
|
|
281
|
+
const delta = choice.delta;
|
|
282
|
+
return Array.isArray(delta.tool_calls) && delta.tool_calls.some(tool => {
|
|
283
|
+
if (!isRec(tool)) return false;
|
|
284
|
+
const fn = isRec(tool.function) ? tool.function : {};
|
|
285
|
+
return (typeof tool.id === "string" && tool.id.length > 0)
|
|
286
|
+
|| (typeof fn.name === "string" && fn.name.length > 0)
|
|
287
|
+
|| (typeof fn.arguments === "string" && fn.arguments.length > 0);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
if (hasText) sawTextOutput = true;
|
|
291
|
+
if (hasTool) sawToolCalls = true;
|
|
292
|
+
const hasProgress = hasText || hasTool;
|
|
284
293
|
if (hasProgress || finished) pullDeadline = performance.now() + stallMs;
|
|
285
294
|
else if (performance.now() >= pullDeadline) throw stalled;
|
|
286
295
|
if (!firstOutput && hasProgress) {
|
|
@@ -358,6 +367,14 @@ export function nativeChatSse(
|
|
|
358
367
|
if (!sawDone) enqueue(controller, "data: [DONE]\n\n");
|
|
359
368
|
settle(200);
|
|
360
369
|
controller.close();
|
|
370
|
+
} else if (sawToolCalls) {
|
|
371
|
+
// Mid tool-call truncation must fail closed to prevent downstream executing partial arguments.
|
|
372
|
+
fail(controller, "upstream SSE ended mid tool call without a terminal signal — possible truncation", "upstream_sse_truncated");
|
|
373
|
+
} else if (sawTextOutput) {
|
|
374
|
+
// Upstream produced text or reasoning output but missed the terminal frame; recover cleanly with [DONE].
|
|
375
|
+
enqueue(controller, "data: [DONE]\n\n");
|
|
376
|
+
settle(200);
|
|
377
|
+
controller.close();
|
|
361
378
|
} else {
|
|
362
379
|
fail(controller, "upstream SSE ended before a terminal event", "upstream_sse_truncated");
|
|
363
380
|
}
|
|
@@ -50,7 +50,7 @@ import { enrichOpenCodeZenFreeTierMessage } from "../providers/opencode-zen-rate
|
|
|
50
50
|
import type { OcxProviderTransport } from "../providers/xai-transport";
|
|
51
51
|
import type { RouteResult } from "../router";
|
|
52
52
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
53
|
-
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel } from "./responses/fetch-helpers";
|
|
53
|
+
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, sendWithConnectionPolicy } from "./responses/fetch-helpers";
|
|
54
54
|
import { linkAbortSignal } from "./responses";
|
|
55
55
|
import {
|
|
56
56
|
addFinalRequestLog,
|
|
@@ -351,9 +351,15 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
351
351
|
if (!headers.has("accept-encoding") && encoding) headers.set("accept-encoding", encoding);
|
|
352
352
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
353
353
|
noteProviderAttemptSend(logCtx, route.providerName, activeProvider, logCtx.usageLogInputTokens, transportRecovery ?? recovery);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
// A reselected provider transport is still a physical send: the connection policy
|
|
355
|
+
// and manual-redirect ownership wrap the selected implementation (#4992).
|
|
356
|
+
const dispatched = await sendWithConnectionPolicy(
|
|
357
|
+
(activeProvider as OcxProviderTransport).fetch ?? execute,
|
|
358
|
+
request.url,
|
|
359
|
+
applyUpstreamRecoveryInit({
|
|
360
|
+
...init, method: request.method, headers, body: request.body,
|
|
361
|
+
}, transportRecovery),
|
|
362
|
+
);
|
|
357
363
|
if (!dispatched.ok) await recordKeyAttemptFailure(logCtx, dispatched, init.signal ?? upstream.signal);
|
|
358
364
|
return dispatched;
|
|
359
365
|
},
|