@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- 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 +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- 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/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- 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/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- 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-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- 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/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- 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/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- 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/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -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 +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -61,6 +61,9 @@ export function buildTools(tools: unknown[] | undefined): OcxTool[] | undefined
|
|
|
61
61
|
parameters: normalizeParameters(t.parameters),
|
|
62
62
|
};
|
|
63
63
|
if (t.strict !== undefined) tool.strict = t.strict as boolean;
|
|
64
|
+
if (Array.isArray(t.allowed_callers)) {
|
|
65
|
+
tool.allowedCallers = (t.allowed_callers as unknown[]).filter((c): c is string => typeof c === "string");
|
|
66
|
+
}
|
|
64
67
|
if (namespace) tool.namespace = namespace;
|
|
65
68
|
out.push(tool);
|
|
66
69
|
};
|
package/src/responses/parser.ts
CHANGED
|
@@ -245,7 +245,9 @@ export function parseRequest(
|
|
|
245
245
|
case "system": {
|
|
246
246
|
pendingReasoning.length = 0;
|
|
247
247
|
const text = inputContentParts(msg.content);
|
|
248
|
-
const flat = typeof text === "string"
|
|
248
|
+
const flat = typeof text === "string"
|
|
249
|
+
? text
|
|
250
|
+
: text.map(p => (p.type === "text" || p.type === "document" ? p.text : "")).join("");
|
|
249
251
|
if (flat.length > 0) systemPrompt.push(flat);
|
|
250
252
|
break;
|
|
251
253
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { unwrapFreeformToolInput } from "./apply-patch-envelope";
|
|
2
|
+
import { JSON_ESCAPES, scanFreeformWrapper } from "./freeform-wrapper-scan";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether a body could still grow into one complete outer Markdown fence.
|
|
6
|
+
*
|
|
7
|
+
* `stripMarkdownCodeFence` removes such a fence at completion for exactly the two tools that
|
|
8
|
+
* own the grammar, so a fenced body's streamed bytes and its completed input disagree unless
|
|
9
|
+
* the stream holds. A buffer that does not open with a fence can never acquire one, so
|
|
10
|
+
* ordinary bodies are unaffected; a buffer that does keeps its preview suppressed for the
|
|
11
|
+
* whole call, because a closing fence can still be followed by more text that withdraws it.
|
|
12
|
+
*/
|
|
13
|
+
function mayBecomeFencedBody(text: string, toolName: string): boolean {
|
|
14
|
+
if (toolName !== "exec" && toolName !== "apply_patch") return false;
|
|
15
|
+
const head = text.trimStart();
|
|
16
|
+
if (head === "") return true;
|
|
17
|
+
return head.startsWith("```") || "```".startsWith(head);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const LOW_SURROGATE_ESCAPE = /^\\u[dD][c-fC-F][0-9a-fA-F]{2}$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The decoded prefix of a JSON string body, stopping at the first byte it cannot resolve.
|
|
24
|
+
*
|
|
25
|
+
* Every stop is a hold rather than a guess, because `JSON.parse` decides the completed value
|
|
26
|
+
* and anything invented here would be retracted. Three of them are not obvious:
|
|
27
|
+
*
|
|
28
|
+
* An escape JSON does not define makes the whole wrapper unparseable no matter what arrives
|
|
29
|
+
* next, so completion falls back to the raw text. Returning `null` for that case stops the
|
|
30
|
+
* preview rather than continuing to decode a value the completed item will never carry.
|
|
31
|
+
* `\\b` and `\\f` are defined, and were previously decoded to the letters b and f.
|
|
32
|
+
*
|
|
33
|
+
* A lone high surrogate is not a character. Emitting one alone puts an unpaired code unit in a
|
|
34
|
+
* delta the client has to decode by itself, so the pair is emitted together or not at all.
|
|
35
|
+
*/
|
|
36
|
+
function decodeJsonStringPrefix(body: string): string | null {
|
|
37
|
+
let out = "";
|
|
38
|
+
for (let i = 0; i < body.length; i++) {
|
|
39
|
+
const c = body[i];
|
|
40
|
+
if (c === '"') break; // unescaped closing quote: value complete
|
|
41
|
+
if (c !== "\\") {
|
|
42
|
+
// A literal control character is not legal inside a JSON string, so `JSON.parse` will
|
|
43
|
+
// reject this wrapper and completion will return the raw text. Emitting the decoded value
|
|
44
|
+
// first is the disagreement this decoder exists to prevent, so stop instead.
|
|
45
|
+
if (c!.charCodeAt(0) <= 0x1f) return null;
|
|
46
|
+
out += c;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const n = body[i + 1];
|
|
50
|
+
if (n === undefined) break; // escape split across chunks: wait for more
|
|
51
|
+
if (n === "u") {
|
|
52
|
+
const hex = body.slice(i + 2, i + 6);
|
|
53
|
+
if (hex.length < 4) break; // split across chunks: wait for more
|
|
54
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null; // never parses
|
|
55
|
+
const code = parseInt(hex, 16);
|
|
56
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
57
|
+
const low = body.slice(i + 6, i + 12);
|
|
58
|
+
if (!LOW_SURROGATE_ESCAPE.test(low)) break;
|
|
59
|
+
out += String.fromCharCode(code, parseInt(low.slice(2), 16));
|
|
60
|
+
i += 11;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
out += String.fromCharCode(code);
|
|
64
|
+
i += 5;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const escaped = JSON_ESCAPES.get(n);
|
|
68
|
+
if (escaped === undefined) return null; // never parses
|
|
69
|
+
out += escaped;
|
|
70
|
+
i += 1;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The value to stream so far, or `null` to HOLD because nothing can be decided yet.
|
|
77
|
+
*
|
|
78
|
+
* `input` is decidable from its prefix: `unwrapFreeformToolInput` returns it whenever the
|
|
79
|
+
* key is present, whatever else the object carries, so its value can be unescaped
|
|
80
|
+
* progressively and never retracted.
|
|
81
|
+
*
|
|
82
|
+
* One case escapes that claim and is accepted rather than fixed: a duplicate `input` key.
|
|
83
|
+
* `JSON.parse` keeps the last one, so `{"input":"a","input":"b"}` streams a and completes with
|
|
84
|
+
* b. Closing it means holding every canonical wrapper until its object parses, which is the
|
|
85
|
+
* progressive streaming this path exists to provide. The completed item stays authoritative,
|
|
86
|
+
* and no model emits a duplicate key in practice.
|
|
87
|
+
*
|
|
88
|
+
* A wrapper that turns invalid AFTER streaming has committed has the same shape and the same
|
|
89
|
+
* answer. `{"input":"a` followed by `\\qb"}` has already published a when the undefined escape
|
|
90
|
+
* arrives, and completion returns the raw text because nothing parses. The preview stops
|
|
91
|
+
* there: no rewind, and no decoded text the completed item does not contain. Bounding the
|
|
92
|
+
* damage is what is available without giving up progressive streaming, and the args are
|
|
93
|
+
* unusable in that case whichever representation wins.
|
|
94
|
+
*
|
|
95
|
+
* A fallback key is not decidable. It only unwraps when it is the SINGLE string field, and a
|
|
96
|
+
* second key can still arrive — so a value emitted early would have to be taken back. That is
|
|
97
|
+
* the rewind this holds instead: stream nothing until the object closes, then publish the one
|
|
98
|
+
* repaired body. The routed passthrough in `responses-custom-tool-repair.ts` already holds
|
|
99
|
+
* any object prefix for the same reason (#5047).
|
|
100
|
+
*
|
|
101
|
+
* An object that has not reached a canonical key YET is in exactly that position, and used to
|
|
102
|
+
* be treated as raw because it did not match the literal `{"input":"`. It holds now: `input`
|
|
103
|
+
* can still arrive after other properties, or wearing an escaped spelling, and completion would
|
|
104
|
+
* then unwrap a body whose wrapper syntax had already been published as deltas (#5151). The
|
|
105
|
+
* cost of holding is preview on an object body that turns out not to be a wrapper; the cost of
|
|
106
|
+
* not holding was publishing bytes the completed item removes.
|
|
107
|
+
*/
|
|
108
|
+
export function progressiveFreeformInput(args: string, toolName: string): string | null {
|
|
109
|
+
const scan = scanFreeformWrapper(args);
|
|
110
|
+
if (scan.kind === "input") {
|
|
111
|
+
const decoded = decodeJsonStringPrefix(args.slice(scan.valueStart));
|
|
112
|
+
if (decoded === null) return null;
|
|
113
|
+
return mayBecomeFencedBody(decoded, toolName) ? null : decoded;
|
|
114
|
+
}
|
|
115
|
+
if (scan.kind === "raw") return mayBecomeFencedBody(args, toolName) ? null : args;
|
|
116
|
+
|
|
117
|
+
// Undecided: some wrapper may still apply, and only the completed object says which one.
|
|
118
|
+
// The parse runs exactly where the scan SAW the object close, so it reads a buffer the scan
|
|
119
|
+
// already walked and its cost is bounded by the same clamp. A hold from either limit stays
|
|
120
|
+
// held: an incomplete object has nothing to parse, and re-reading a budget-exhausted buffer
|
|
121
|
+
// on every delta is quadratic work for a delta that would arrive in the same instant as the
|
|
122
|
+
// authoritative completion behind it.
|
|
123
|
+
if (!scan.parse) return null;
|
|
124
|
+
try {
|
|
125
|
+
JSON.parse(args);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return unwrapFreeformToolInput(args, toolName);
|
|
130
|
+
}
|
|
@@ -63,6 +63,36 @@ export function encodeReasoningEnvelope(envelope: ReasoningEnvelope, budget?: Tr
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Whether a thinking part's `signature` is an attestation some provider issued, rather than
|
|
68
|
+
* the serialized reasoning item the parser parks in the same field.
|
|
69
|
+
*
|
|
70
|
+
* `src/responses/parser.ts` stores `JSON.stringify(reasoningItem)` on an UNSIGNED thinking
|
|
71
|
+
* part so the opaque item survives a same-provider round trip. Every adapter that forwards a
|
|
72
|
+
* signature upstream is forwarding an opaque attestation, and a provider asked to verify our
|
|
73
|
+
* own parser state cannot recognize it. The predicate lives beside the envelope code that owns
|
|
74
|
+
* this field's representation rather than in one adapter, because a private second copy is how
|
|
75
|
+
* the side that writes the field and a side that reads it come to disagree about what it holds.
|
|
76
|
+
*
|
|
77
|
+
* This is a deny-list for exactly that one shape, not a guess at what an attestation looks
|
|
78
|
+
* like. `src/adapters/anthropic.ts` applies a stricter allow-list on top of it, because an
|
|
79
|
+
* Anthropic signature has a known base64 spelling; that shape is a fact about Anthropic's wire
|
|
80
|
+
* and is not assumed of any other provider's token here.
|
|
81
|
+
*/
|
|
82
|
+
export function isProviderIssuedThinkingSignature(
|
|
83
|
+
signature: string | undefined,
|
|
84
|
+
): signature is string {
|
|
85
|
+
if (typeof signature !== "string" || signature.length === 0) return false;
|
|
86
|
+
if (!signature.startsWith("{")) return true;
|
|
87
|
+
try {
|
|
88
|
+
const parsed: unknown = JSON.parse(signature);
|
|
89
|
+
return !parsed || typeof parsed !== "object" || Array.isArray(parsed)
|
|
90
|
+
|| (parsed as { type?: unknown }).type !== "reasoning";
|
|
91
|
+
} catch {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
/** Decode an ocxr1 envelope; returns null for native (OpenAI-encrypted) blobs or garbage. */
|
|
67
97
|
export function decodeReasoningEnvelope(encryptedContent: string, budget?: TranslatorBudget): ReasoningEnvelope | null {
|
|
68
98
|
if (!encryptedContent.startsWith(OCX_REASONING_PREFIX)) return null;
|
package/src/responses/schema.ts
CHANGED
|
@@ -122,6 +122,9 @@ export const toolSchema = z.object({
|
|
|
122
122
|
description: z.string().optional(),
|
|
123
123
|
parameters: z.record(z.string(), z.unknown()).optional(),
|
|
124
124
|
strict: z.boolean().optional(),
|
|
125
|
+
// Unknown keys are stripped here, so a field the parser is expected to read has to be
|
|
126
|
+
// declared: an undeclared allowed_callers never reached buildTools at all (#5210).
|
|
127
|
+
allowed_callers: z.array(z.string()).optional(),
|
|
125
128
|
});
|
|
126
129
|
|
|
127
130
|
const builtinToolSchema = z.object({ type: z.string() }).loose();
|
package/src/responses/state.ts
CHANGED
|
@@ -135,11 +135,10 @@ export type ResidentInput = Omit<ResidentResponseState, "kind" | "sizeBytes">;
|
|
|
135
135
|
|
|
136
136
|
export type PreviousResponseReplayFailure = {
|
|
137
137
|
code: "previous_response_not_found";
|
|
138
|
-
reason: "spill_missing" | "spill_corrupt" | "spill_failed" | "spill_too_large";
|
|
138
|
+
reason: "spill_missing" | "spill_corrupt" | "spill_failed" | "spill_too_large" | "scope_mismatch";
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const states = new Map<string, StoredResponseState>();
|
|
142
|
-
const replayScopeMismatches = new WeakSet<object>();
|
|
143
142
|
let storedResponseBytes = 0;
|
|
144
143
|
let residentResponseBytes = 0;
|
|
145
144
|
let oldestResidentId: string | undefined;
|
|
@@ -1044,11 +1043,6 @@ function normalizedClientThreadId(value: unknown): string | undefined {
|
|
|
1044
1043
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
1045
1044
|
}
|
|
1046
1045
|
|
|
1047
|
-
function withoutPreviousResponseId(request: Record<string, unknown>): Record<string, unknown> {
|
|
1048
|
-
const { previous_response_id: _previousResponseId, ...freshRequest } = request;
|
|
1049
|
-
return freshRequest;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
1046
|
export function expandPreviousResponseInput(body: unknown, clientThreadId?: string): unknown {
|
|
1053
1047
|
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
|
|
1054
1048
|
const request = body as Record<string, unknown>;
|
|
@@ -1068,10 +1062,9 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
1068
1062
|
// A Codex task must never inherit another task's continuation, nor a legacy unscoped entry.
|
|
1069
1063
|
// Unscoped callers retain backward-compatible replay only with other unscoped entries.
|
|
1070
1064
|
if (requestThreadId !== storedThreadId) {
|
|
1071
|
-
|
|
1072
|
-
replayScopeMismatches.add(freshRequest);
|
|
1065
|
+
replayFailures.set(request, { code: "previous_response_not_found", reason: "scope_mismatch" });
|
|
1073
1066
|
replayScopeMismatchDrops += 1;
|
|
1074
|
-
return
|
|
1067
|
+
return body;
|
|
1075
1068
|
}
|
|
1076
1069
|
// The client already replayed this history verbatim. Prepending the stored copy would
|
|
1077
1070
|
// double it, and the doubled turn is stored again, so the next turn triples (#1412 saw
|
|
@@ -1136,9 +1129,9 @@ export function copyPreviousResponseReplayProvenance(source: unknown, target: un
|
|
|
1136
1129
|
replayedInputPrefixLengths.set(target, prefixLength);
|
|
1137
1130
|
}
|
|
1138
1131
|
|
|
1139
|
-
/** True when
|
|
1132
|
+
/** True when this exact request could not replay because its task scope did not match. */
|
|
1140
1133
|
export function previousResponseScopeMismatch(body: unknown): boolean {
|
|
1141
|
-
return
|
|
1134
|
+
return previousResponseReplayFailure(body)?.reason === "scope_mismatch";
|
|
1142
1135
|
}
|
|
1143
1136
|
|
|
1144
1137
|
export function previousResponseConversationId(responseId: string | undefined): string | undefined {
|
|
@@ -19,6 +19,21 @@ export function dottedAliasIsUnambiguous(namespace: string, name: string): boole
|
|
|
19
19
|
return !namespace.includes("__") && !name.includes("__");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Charset the Responses API enforces on a tool-call `name`.
|
|
24
|
+
*
|
|
25
|
+
* A name outside it is not merely rejected for the turn that carries it: Codex stores the item in
|
|
26
|
+
* the conversation, and every later request that replays that history is refused before it runs
|
|
27
|
+
* (`Invalid 'input[N].name': string does not match pattern '^[a-zA-Z0-9_-]+$'`). One relayed item
|
|
28
|
+
* therefore ends the conversation permanently, which is why the emit boundary treats an invalid
|
|
29
|
+
* name as a defect to repair rather than as a name to forward. See #5095.
|
|
30
|
+
*/
|
|
31
|
+
const RESPONSES_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
32
|
+
|
|
33
|
+
export function isSchemaValidResponsesToolName(name: string): boolean {
|
|
34
|
+
return RESPONSES_TOOL_NAME_PATTERN.test(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
export function wireToolInnerName(tool: unknown): string | undefined {
|
|
23
38
|
if (!isPlainObject(tool)) return undefined;
|
|
24
39
|
const nestedFunction = tool.type === "function" && isPlainObject(tool.function)
|
|
@@ -76,4 +91,3 @@ export function collectAmbiguousDottedAliases(specGroups: readonly unknown[]): S
|
|
|
76
91
|
for (const [alias, owner] of owners) if (owner === null) ambiguous.add(alias);
|
|
77
92
|
return ambiguous;
|
|
78
93
|
}
|
|
79
|
-
|
package/src/router.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { peekAuthStore } from "./oauth/store";
|
|
2
4
|
import {
|
|
3
5
|
getCombo,
|
|
4
6
|
isComboTargetInCooldown,
|
|
@@ -15,10 +17,12 @@ import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
|
15
17
|
import { redactSecretString, redactUrlForLog } from "./lib/redact";
|
|
16
18
|
import {
|
|
17
19
|
PROVIDER_REGISTRY,
|
|
18
|
-
mergeRegistryStaticHeaders,
|
|
19
20
|
providerCodexAccountMode,
|
|
20
|
-
|
|
21
|
+
registryEntryForProviderDestination,
|
|
21
22
|
} from "./providers/registry";
|
|
23
|
+
// Imported from the module directly rather than through the registry facade, which is at its
|
|
24
|
+
// file-size cap.
|
|
25
|
+
import { registryModelIdKeys } from "./providers/registry/model-ids";
|
|
22
26
|
import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
|
|
23
27
|
import { cloneFastWire } from "./providers/fastwire";
|
|
24
28
|
import {
|
|
@@ -29,13 +33,12 @@ import {
|
|
|
29
33
|
isCanonicalOpenAiForwardProvider,
|
|
30
34
|
LEGACY_CHATGPT_PROVIDER_ID,
|
|
31
35
|
LEGACY_OPENAI_MULTI_PROVIDER_ID,
|
|
32
|
-
OPENAI_API_PROVIDER_ID,
|
|
33
36
|
OPENAI_CODEX_PROVIDER_ID,
|
|
34
37
|
} from "./providers/openai-tiers";
|
|
35
38
|
import { decodeRoutedModelIdOrThrow, encodeRoutedModelId } from "./providers/slug-codec";
|
|
36
|
-
import { resolveModelAlias } from "./providers/default-aliases";
|
|
39
|
+
import { effectiveProviderAliasDecision, resolveModelAlias } from "./providers/default-aliases";
|
|
37
40
|
import { resolveBlockedModelRedirect } from "./lib/shadow-call";
|
|
38
|
-
import {
|
|
41
|
+
import { getRoutingCached } from "./codex/model-cache";
|
|
39
42
|
import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
|
|
40
43
|
import {
|
|
41
44
|
buildRouteDecisionTrace,
|
|
@@ -46,6 +49,7 @@ import {
|
|
|
46
49
|
import { getRoutingProfile, resolvePolicyProfileId, POLICY_NAMESPACE } from "./routing/profile";
|
|
47
50
|
import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
|
|
48
51
|
import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
|
|
52
|
+
import { resolveModelPolicy, type ResolvedModelPolicy } from "./providers/resolved-model-policy";
|
|
49
53
|
|
|
50
54
|
export class UnknownRoutingPolicyError extends Error {
|
|
51
55
|
constructor(readonly profileId: string) {
|
|
@@ -69,6 +73,8 @@ export interface RouteResult {
|
|
|
69
73
|
providerName: string;
|
|
70
74
|
provider: OcxProviderConfig;
|
|
71
75
|
modelId: string;
|
|
76
|
+
/** Immutable static policy for the current final wire model. */
|
|
77
|
+
staticPolicy: ResolvedModelPolicy;
|
|
72
78
|
/** Which deterministic routing path produced this route (RI-01). */
|
|
73
79
|
routeKind: RouteDecisionKind;
|
|
74
80
|
/** Stable wire reason code for the selected route (RI-01). */
|
|
@@ -83,6 +89,29 @@ export interface RouteResult {
|
|
|
83
89
|
routeDecision?: RouteDecisionTraceV1;
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
export function captureRouteStaticPolicy(
|
|
93
|
+
providerName: string,
|
|
94
|
+
modelId: string,
|
|
95
|
+
provider: OcxProviderConfig,
|
|
96
|
+
effectiveAlias?: string | null,
|
|
97
|
+
inboundWire: "responses" | "chat" | "anthropic" = "responses",
|
|
98
|
+
): ResolvedModelPolicy {
|
|
99
|
+
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
100
|
+
const transportMatchedRegistry = !!registryEntry
|
|
101
|
+
&& providerMatchesRegistryTransportWithStaticGuards(providerName, provider);
|
|
102
|
+
return resolveModelPolicy({
|
|
103
|
+
providerName,
|
|
104
|
+
modelId,
|
|
105
|
+
provider,
|
|
106
|
+
registryEntry,
|
|
107
|
+
transportMatchedRegistry,
|
|
108
|
+
inboundWire,
|
|
109
|
+
modelCapabilities: provider.modelCapabilities?.[modelId],
|
|
110
|
+
...(provider.authMode ? { effectiveAuth: { authMode: provider.authMode } } : {}),
|
|
111
|
+
...(effectiveAlias !== undefined ? { effectiveAlias } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
|
|
87
116
|
{
|
|
88
117
|
providerNames: ["anthropic"],
|
|
@@ -117,86 +146,31 @@ export function knownModelIdsForProvider(
|
|
|
117
146
|
: undefined;
|
|
118
147
|
for (const id of registry?.models ?? []) ids.add(id);
|
|
119
148
|
// 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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
// static models list but names `moonshotai/kimi-k2.6` in its effort/window maps). Which
|
|
150
|
+
// maps count is classified by the registry itself rather than listed here, so an id declared
|
|
151
|
+
// only in a map this function forgot is no longer undecodable, and a new model-keyed field
|
|
152
|
+
// fails typecheck until its keys are given a meaning.
|
|
153
|
+
for (const id of registry ? registryModelIdKeys(registry) : []) ids.add(id);
|
|
154
|
+
const cachedModels = getRoutingCached(provName, () => {
|
|
155
|
+
// This callback runs only for a scoped entry, not for each provider in an alias scan.
|
|
156
|
+
const routed = routedProviderConfig(provName, prov);
|
|
157
|
+
let key = routed.apiKey;
|
|
158
|
+
if (routed.authMode === "oauth") {
|
|
159
|
+
const set = peekAuthStore()[provName];
|
|
160
|
+
const account = set?.accounts.find(row => row.id === set.activeAccountId);
|
|
161
|
+
if (!account || account.needsReauth || !Number.isFinite(account.credential.expires)
|
|
162
|
+
|| account.credential.expires <= Date.now()) return undefined;
|
|
163
|
+
key = account.credential.access;
|
|
164
|
+
}
|
|
165
|
+
return key ? createHash("sha256").update(key).digest("hex") : undefined;
|
|
166
|
+
});
|
|
167
|
+
for (const cached of cachedModels ?? []) ids.add(cached.id);
|
|
134
168
|
for (const model of config?.customModels ?? []) {
|
|
135
169
|
if (model.provider === provName && model.modelId) ids.add(model.modelId);
|
|
136
170
|
}
|
|
137
171
|
return [...ids];
|
|
138
172
|
}
|
|
139
173
|
|
|
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
174
|
/** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
|
|
201
175
|
function isSameEndpoint(a: string, b: string): boolean {
|
|
202
176
|
return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
|
|
@@ -302,7 +276,20 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
302
276
|
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
|
|
303
277
|
if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
|
|
304
278
|
assertProviderDestinationAllowed(providerName, provider);
|
|
305
|
-
|
|
279
|
+
// A row whose adapter no longer matches its registry entry still reaches the Responses
|
|
280
|
+
// adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
|
|
281
|
+
// saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
|
|
282
|
+
// to the provider-wide wire, so it is filled here too; without it that continuation forwards
|
|
283
|
+
// the reasoning item the upstream answers 400 to. Destination matching refuses templated and
|
|
284
|
+
// overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
|
|
285
|
+
const destination = registryEntryForProviderDestination(provider);
|
|
286
|
+
return {
|
|
287
|
+
...provider,
|
|
288
|
+
apiKey: usableResolvedApiKey(provider.apiKey),
|
|
289
|
+
...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
|
|
290
|
+
? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
|
|
291
|
+
: {}),
|
|
292
|
+
};
|
|
306
293
|
}
|
|
307
294
|
const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
|
|
308
295
|
const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
|
|
@@ -317,46 +304,42 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
317
304
|
: registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
|
|
318
305
|
? registryEntry.authKind
|
|
319
306
|
: provider.authMode === "forward" ? undefined : provider.authMode;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
:
|
|
327
|
-
|
|
307
|
+
const staticPolicy = resolveModelPolicy({
|
|
308
|
+
providerName,
|
|
309
|
+
modelId: "__provider_static__",
|
|
310
|
+
provider,
|
|
311
|
+
registryEntry,
|
|
312
|
+
transportMatchedRegistry: true,
|
|
313
|
+
...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
|
|
314
|
+
}).provider;
|
|
315
|
+
const reasoningEffortMap = staticPolicy.reasoningEffortMap;
|
|
316
|
+
const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
|
|
317
|
+
const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
|
|
318
|
+
const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
|
|
319
|
+
const modelContextWindows = staticPolicy.modelContextWindows;
|
|
320
|
+
const modelInputModalities = staticPolicy.modelInputModalities;
|
|
328
321
|
// Registry static headers are documented as applying to every upstream request, so they are
|
|
329
322
|
// filled at resolve time rather than only at seed time: a config written before a header
|
|
330
323
|
// existed, or one carrying any header of its own, would otherwise never receive it. User
|
|
331
324
|
// 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);
|
|
325
|
+
const headers = staticPolicy.headers;
|
|
326
|
+
const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
|
|
327
|
+
const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
|
|
328
|
+
const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
|
|
329
|
+
const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
|
|
330
|
+
const noVisionModels = staticPolicy.noVisionModels;
|
|
331
|
+
const noReasoningModels = staticPolicy.noReasoningModels;
|
|
332
|
+
const noTemperatureModels = staticPolicy.noTemperatureModels;
|
|
333
|
+
const noTopPModels = staticPolicy.noTopPModels;
|
|
334
|
+
const noPenaltyModels = staticPolicy.noPenaltyModels;
|
|
335
|
+
const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
|
|
336
|
+
const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
|
|
337
|
+
const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
|
|
338
|
+
const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
|
|
339
|
+
const reasoningSplitModels = staticPolicy.reasoningSplitModels;
|
|
340
|
+
const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
|
|
341
|
+
const thinkingToggleModels = staticPolicy.thinkingToggleModels;
|
|
342
|
+
const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
|
|
360
343
|
const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
|
|
361
344
|
const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
|
|
362
345
|
const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
|
|
@@ -417,6 +400,9 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
417
400
|
...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
|
|
418
401
|
? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
|
|
419
402
|
: {}),
|
|
403
|
+
...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
|
|
404
|
+
? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
|
|
405
|
+
: {}),
|
|
420
406
|
// The request path resolves through routedProviderConfig() and never calls
|
|
421
407
|
// enrichProviderFromRegistry(), so a saved provider row written before the
|
|
422
408
|
// registry learned this flag must be backfilled here or route.provider never
|
|
@@ -565,10 +551,15 @@ function routeResult(
|
|
|
565
551
|
const effectiveModelId = redirected ?? modelId;
|
|
566
552
|
const effectiveRouteReason = redirected ? "blocked-model-redirect" : routeReason;
|
|
567
553
|
const codexAccountMode = providerCodexAccountMode(providerName, provider);
|
|
554
|
+
const routedProvider = routedProviderConfig(providerName, provider);
|
|
555
|
+
const effectiveAlias = config
|
|
556
|
+
? effectiveProviderAliasDecision(providerName, provider, config)
|
|
557
|
+
: undefined;
|
|
568
558
|
return {
|
|
569
559
|
providerName,
|
|
570
|
-
provider:
|
|
560
|
+
provider: routedProvider,
|
|
571
561
|
modelId: effectiveModelId,
|
|
562
|
+
staticPolicy: captureRouteStaticPolicy(providerName, effectiveModelId, routedProvider, effectiveAlias),
|
|
572
563
|
routeKind,
|
|
573
564
|
routeReason: effectiveRouteReason,
|
|
574
565
|
...(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);
|