@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
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import type { ModelCapabilities, OcxProviderConfig } from "../types";
|
|
2
|
+
import { MODEL_ADAPTER_OVERRIDE_ALLOWED, pinnedWireAdapter } from "../types";
|
|
3
|
+
import { isCanonicalOpenAiForwardProvider } from "./openai-tiers";
|
|
4
|
+
import { resolveProviderAuthTransport } from "./fastwire";
|
|
5
|
+
import { registryEntrySupportsLiveModelDiscovery } from "./static-model-discovery";
|
|
6
|
+
import type { InboundWire, ProviderRegistryEntry, ResponsesTerminalRepairPolicy } from "./registry/types";
|
|
7
|
+
import {
|
|
8
|
+
anthropicFamilyContextWindow,
|
|
9
|
+
detachedClone,
|
|
10
|
+
legacyModelValue,
|
|
11
|
+
legacyModelSource,
|
|
12
|
+
mapFill,
|
|
13
|
+
nestedMapFill,
|
|
14
|
+
positiveCapMap,
|
|
15
|
+
recursivelyFreeze,
|
|
16
|
+
resolvedBaseUrl,
|
|
17
|
+
sameStringArray,
|
|
18
|
+
scalar,
|
|
19
|
+
stableUnion,
|
|
20
|
+
staticHeaders,
|
|
21
|
+
wireDefault,
|
|
22
|
+
type StaticPolicySource,
|
|
23
|
+
} from "./resolved-model-policy-merge";
|
|
24
|
+
export type { StaticPolicySource } from "./resolved-model-policy-merge";
|
|
25
|
+
export type StaticProviderPolicyField =
|
|
26
|
+
| "adapter" | "baseUrl" | "apiKeyTransport" | "headers" | "authMode" | "codexAccountMode"
|
|
27
|
+
| "responsesPath" | "chatCompletionsPath" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip"
|
|
28
|
+
| "defaultModel" | "models" | "liveModels" | "contextWindow" | "modelContextWindows"
|
|
29
|
+
| "modelDisplayNames" | "modelInputModalities" | "modelMaxInputTokens" | "defaultMaxOutputTokens"
|
|
30
|
+
| "modelMaxOutputTokens" | "reasoningEfforts" | "modelReasoningEfforts" | "modelReasoningEffortsAuthoritative"
|
|
31
|
+
| "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
32
|
+
| "reasoningWireFormat" | "noVisionModels" | "noReasoningModels" | "noTemperatureModels"
|
|
33
|
+
| "noTopPModels" | "noPenaltyModels" | "noJsonSchemaModels" | "parallelToolCalls"
|
|
34
|
+
| "promptCacheKey" | "chatServiceTier" | "openaiChatEofTolerance" | "statelessResponses"
|
|
35
|
+
| "requiresAdjacentResponsesToolResults" | "requiresPairedResponsesToolResults" | "annotateEmptyToolOutputs"
|
|
36
|
+
| "fastWire" | "supportsServiceTier" | "modelSupportsServiceTier" | "supportsOpenAiWebSearchToolFields"
|
|
37
|
+
| "supportsResponsesCustomTools" | "preserveResponsesReasoningContent" | "dropResponsesReasoningItems"
|
|
38
|
+
| "modelSupportsReasoningSummaries"
|
|
39
|
+
| "supportsVerbosity" | "modelSupportsVerbosity" | "responsesItemIdRepair" | "autoToolChoiceOnlyModels"
|
|
40
|
+
| "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels"
|
|
41
|
+
| "reasoningDetailsModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "showThinkingSummary"
|
|
42
|
+
| "escapeBuiltinToolNames" | "googleMode" | "project" | "location" | "modelCapabilities"
|
|
43
|
+
| "modelAutoCompactTokenLimits" | "modelSuppressSyntheticMax" | "modelReasoningSummaryDelivery"
|
|
44
|
+
| "codexToolMode" | "modelAdapters";
|
|
45
|
+
type StaticProviderPolicyShape = Pick<OcxProviderConfig, StaticProviderPolicyField>;
|
|
46
|
+
export type ResolvedProviderStaticPolicy = Readonly<Partial<StaticProviderPolicyShape>> & Readonly<{
|
|
47
|
+
adapter: string;
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
}>;
|
|
50
|
+
export interface ResolvedPerModelStaticPolicy {
|
|
51
|
+
readonly adapter: string;
|
|
52
|
+
readonly contextWindow?: number;
|
|
53
|
+
readonly inputModalities?: readonly string[];
|
|
54
|
+
readonly maxInputTokens?: number;
|
|
55
|
+
readonly maxOutputTokens?: number;
|
|
56
|
+
readonly reasoningEfforts?: readonly string[];
|
|
57
|
+
readonly defaultReasoningEffort?: string;
|
|
58
|
+
readonly supportsReasoningSummaries?: boolean;
|
|
59
|
+
readonly supportsVerbosity?: boolean;
|
|
60
|
+
readonly supportsServiceTier?: boolean;
|
|
61
|
+
readonly fastTierDescription?: string;
|
|
62
|
+
readonly directReasoningEffort?: boolean;
|
|
63
|
+
readonly responsesUpstreamStreaming?: boolean;
|
|
64
|
+
readonly responsesTerminalRepair?: Readonly<ResponsesTerminalRepairPolicy>;
|
|
65
|
+
}
|
|
66
|
+
export interface ResolvedModelPolicy {
|
|
67
|
+
readonly version: 1;
|
|
68
|
+
readonly providerName: string;
|
|
69
|
+
readonly modelId: string;
|
|
70
|
+
readonly transportMatchedRegistry: boolean;
|
|
71
|
+
readonly effectiveAlias?: string | null;
|
|
72
|
+
readonly provider: ResolvedProviderStaticPolicy;
|
|
73
|
+
readonly model: Readonly<ResolvedPerModelStaticPolicy>;
|
|
74
|
+
readonly provenance: Readonly<{
|
|
75
|
+
alias: StaticPolicySource;
|
|
76
|
+
provider: Readonly<Partial<Record<StaticProviderPolicyField, StaticPolicySource>>>;
|
|
77
|
+
model: Readonly<Partial<Record<keyof ResolvedPerModelStaticPolicy, StaticPolicySource>>>;
|
|
78
|
+
}>;
|
|
79
|
+
}
|
|
80
|
+
export interface ResolveModelPolicyInput {
|
|
81
|
+
readonly providerName: string;
|
|
82
|
+
/** Final wire identity after resolveModelAlias and resolveOpenAiVirtualModel; public selection stays diagnostic-only. */
|
|
83
|
+
readonly modelId: string;
|
|
84
|
+
readonly provider: Readonly<OcxProviderConfig>;
|
|
85
|
+
readonly registryEntry?: Readonly<ProviderRegistryEntry>;
|
|
86
|
+
readonly transportMatchedRegistry: boolean;
|
|
87
|
+
readonly inboundWire?: InboundWire;
|
|
88
|
+
/** Exact post-rewrite model capability declaration captured by the caller. */
|
|
89
|
+
readonly modelCapabilities?: Readonly<ModelCapabilities>;
|
|
90
|
+
/** Credential-free live admission result: key only after a usable override, otherwise the registry mode. */
|
|
91
|
+
readonly effectiveAuth?: Readonly<{ authMode: NonNullable<OcxProviderConfig["authMode"]> }>;
|
|
92
|
+
/** Capture-time collision-aware alias decision. Omit to use provider then registry fallback. */
|
|
93
|
+
readonly effectiveAlias?: string | null;
|
|
94
|
+
readonly effectiveAliasSource?: Exclude<StaticPolicySource, "hard-pin" | "provider-default">;
|
|
95
|
+
}
|
|
96
|
+
export interface ObservedModelLimits {
|
|
97
|
+
readonly contextWindow?: number;
|
|
98
|
+
readonly maxInputTokens?: number;
|
|
99
|
+
readonly maxOutputTokens?: number;
|
|
100
|
+
}
|
|
101
|
+
/** Call-local projection: observed values stay outside ResolvedModelPolicy and may only narrow caps. */
|
|
102
|
+
export function clampObservedModelLimits(
|
|
103
|
+
policy: Readonly<Pick<ResolvedPerModelStaticPolicy, "contextWindow" | "maxInputTokens" | "maxOutputTokens">>,
|
|
104
|
+
observed: Readonly<ObservedModelLimits>,
|
|
105
|
+
): Readonly<ObservedModelLimits> {
|
|
106
|
+
const clamp = (staticCap: number | undefined, observedValue: number | undefined): number | undefined => {
|
|
107
|
+
if (observedValue === undefined) return staticCap;
|
|
108
|
+
return staticCap === undefined ? observedValue : Math.min(staticCap, observedValue);
|
|
109
|
+
};
|
|
110
|
+
const contextWindow = clamp(policy.contextWindow, observed.contextWindow);
|
|
111
|
+
const clampedMaxInputTokens = clamp(policy.maxInputTokens, observed.maxInputTokens);
|
|
112
|
+
const maxInputTokens = clampedMaxInputTokens !== undefined && contextWindow !== undefined
|
|
113
|
+
? Math.min(clampedMaxInputTokens, contextWindow)
|
|
114
|
+
: clampedMaxInputTokens;
|
|
115
|
+
const maxOutputTokens = clamp(policy.maxOutputTokens, observed.maxOutputTokens);
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
118
|
+
...(maxInputTokens !== undefined ? { maxInputTokens } : {}),
|
|
119
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export function resolveModelPolicy(input: ResolveModelPolicyInput): ResolvedModelPolicy {
|
|
123
|
+
const entry = input.transportMatchedRegistry ? input.registryEntry : undefined;
|
|
124
|
+
const provider = input.provider;
|
|
125
|
+
const providerPolicy: Partial<StaticProviderPolicyShape> = {};
|
|
126
|
+
const providerProvenance: Partial<Record<StaticProviderPolicyField, StaticPolicySource>> = {};
|
|
127
|
+
const [baseUrl, baseUrlSource] = resolvedBaseUrl(entry, provider);
|
|
128
|
+
const put = <K extends StaticProviderPolicyField>(key: K, value: StaticProviderPolicyShape[K] | undefined, source: StaticPolicySource): void => {
|
|
129
|
+
if (value !== undefined) providerPolicy[key] = detachedClone(value) as never;
|
|
130
|
+
providerProvenance[key] = source;
|
|
131
|
+
};
|
|
132
|
+
const putScalar = <K extends StaticProviderPolicyField>(key: K, registryValue: StaticProviderPolicyShape[K] | undefined): void => {
|
|
133
|
+
const [value, source] = scalar(provider[key], registryValue);
|
|
134
|
+
put(key, value, source);
|
|
135
|
+
};
|
|
136
|
+
const putUnion = (key: StaticProviderPolicyField, registryValue: readonly string[] | undefined): void => {
|
|
137
|
+
const [value, source] = stableUnion(registryValue, provider[key] as string[] | undefined);
|
|
138
|
+
put(key, value as never, source);
|
|
139
|
+
};
|
|
140
|
+
const putMergedMap = <K extends StaticProviderPolicyField, T>(
|
|
141
|
+
key: K,
|
|
142
|
+
registryValue: Readonly<Record<string, T>> | undefined,
|
|
143
|
+
operatorValue: Readonly<Record<string, T>> | undefined,
|
|
144
|
+
): void => {
|
|
145
|
+
const [value, source] = mapFill(registryValue, operatorValue);
|
|
146
|
+
put(key, value as StaticProviderPolicyShape[K], source);
|
|
147
|
+
};
|
|
148
|
+
put("adapter", entry?.adapter ?? provider.adapter, entry ? "registry" : "operator");
|
|
149
|
+
put("baseUrl", baseUrl, baseUrlSource);
|
|
150
|
+
putScalar("apiKeyTransport", entry?.apiKeyTransport);
|
|
151
|
+
const [headers, headersSource] = staticHeaders(entry?.staticHeaders, provider.headers);
|
|
152
|
+
put("headers", headers, headersSource);
|
|
153
|
+
const resolvedAuthMode = input.effectiveAuth?.authMode
|
|
154
|
+
?? (entry?.authKind === "forward" || entry?.authKind === "oauth"
|
|
155
|
+
? entry.authKind
|
|
156
|
+
: provider.authMode === "forward" ? undefined : provider.authMode ?? entry?.authKind);
|
|
157
|
+
put("authMode", resolvedAuthMode, input.effectiveAuth
|
|
158
|
+
? "captured-auth"
|
|
159
|
+
: entry?.authKind === "forward" || entry?.authKind === "oauth" ? "registry"
|
|
160
|
+
: provider.authMode !== undefined ? "operator" : entry ? "registry" : "unknown");
|
|
161
|
+
putScalar("codexAccountMode", entry?.codexAccountMode);
|
|
162
|
+
const keyAuthDefaults = entry?.allowKeyAuthOverride === true
|
|
163
|
+
&& resolvedAuthMode === "key"
|
|
164
|
+
&& ["authorization_bearer", "x_api_key"].includes(resolveProviderAuthTransport(
|
|
165
|
+
provider.adapter,
|
|
166
|
+
resolvedAuthMode,
|
|
167
|
+
provider.apiKeyTransport,
|
|
168
|
+
))
|
|
169
|
+
? entry.keyAuthServiceTier
|
|
170
|
+
: undefined;
|
|
171
|
+
for (const key of [
|
|
172
|
+
"responsesPath", "chatCompletionsPath", "keyOptional", "freeTier", "modelSuffixBracketStrip",
|
|
173
|
+
"defaultModel", "models", "liveModels", "contextWindow", "defaultMaxOutputTokens",
|
|
174
|
+
"reasoningWireFormat", "parallelToolCalls", "promptCacheKey",
|
|
175
|
+
"openaiChatEofTolerance", "statelessResponses",
|
|
176
|
+
"requiresAdjacentResponsesToolResults", "requiresPairedResponsesToolResults",
|
|
177
|
+
"annotateEmptyToolOutputs", "fastWire", "supportsServiceTier",
|
|
178
|
+
"supportsOpenAiWebSearchToolFields", "supportsResponsesCustomTools",
|
|
179
|
+
"preserveResponsesReasoningContent", "dropResponsesReasoningItems",
|
|
180
|
+
"supportsVerbosity", "responsesItemIdRepair",
|
|
181
|
+
"showThinkingSummary", "escapeBuiltinToolNames", "googleMode", "project", "location",
|
|
182
|
+
] as const) putScalar(key, entry?.[key] as StaticProviderPolicyShape[typeof key] | undefined);
|
|
183
|
+
const legacyClinePassLadder = entry !== undefined && input.providerName === "cline-pass"
|
|
184
|
+
&& provider.reasoningWireFormat === "gateway-object"
|
|
185
|
+
&& sameStringArray(provider.reasoningEfforts, ["low"]);
|
|
186
|
+
put("reasoningEfforts", legacyClinePassLadder ? entry?.reasoningEfforts : provider.reasoningEfforts ?? entry?.reasoningEfforts,
|
|
187
|
+
legacyClinePassLadder || provider.reasoningEfforts === undefined ? entry?.reasoningEfforts ? "registry" : "unknown" : "operator");
|
|
188
|
+
put("chatServiceTier", provider.chatServiceTier ?? keyAuthDefaults?.chatServiceTier ?? entry?.chatServiceTier,
|
|
189
|
+
provider.chatServiceTier !== undefined ? "operator" : keyAuthDefaults?.chatServiceTier !== undefined ? "registry" : entry?.chatServiceTier !== undefined ? "registry" : "unknown");
|
|
190
|
+
put("supportsServiceTier", provider.supportsServiceTier ?? keyAuthDefaults?.supportsServiceTier ?? entry?.supportsServiceTier,
|
|
191
|
+
provider.supportsServiceTier !== undefined ? "operator" : keyAuthDefaults?.supportsServiceTier !== undefined ? "registry" : entry?.supportsServiceTier !== undefined ? "registry" : "unknown");
|
|
192
|
+
if (entry && !registryEntrySupportsLiveModelDiscovery(entry)) put("liveModels", false, "registry");
|
|
193
|
+
putMergedMap("modelDisplayNames", entry?.modelDisplayNames, provider.modelDisplayNames);
|
|
194
|
+
putMergedMap("modelInputModalities", entry?.modelInputModalities, provider.modelInputModalities);
|
|
195
|
+
putMergedMap("modelMaxOutputTokens", entry?.modelMaxOutputTokens, provider.modelMaxOutputTokens);
|
|
196
|
+
putMergedMap("modelReasoningEfforts", entry?.modelReasoningEfforts, provider.modelReasoningEfforts);
|
|
197
|
+
// Operator-only: no registry entry declares it, and it decides whether the map above is the
|
|
198
|
+
// wire contract or only the catalog's. A policy reader that omitted it would report the same
|
|
199
|
+
// effective ladder for two providers that send different efforts.
|
|
200
|
+
put("modelReasoningEffortsAuthoritative", provider.modelReasoningEffortsAuthoritative,
|
|
201
|
+
provider.modelReasoningEffortsAuthoritative !== undefined ? "operator" : "unknown");
|
|
202
|
+
putMergedMap("modelDefaultReasoningEfforts", entry?.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
|
|
203
|
+
const registryServiceTier = !entry?.modelServiceTierCapabilityBaseUrlGuard
|
|
204
|
+
|| entry.modelServiceTierCapabilityBaseUrlGuard(provider.baseUrl)
|
|
205
|
+
? entry?.modelSupportsServiceTier
|
|
206
|
+
: undefined;
|
|
207
|
+
const registryServiceTierDefaults = registryServiceTier || keyAuthDefaults?.modelSupportsServiceTier
|
|
208
|
+
? { ...(registryServiceTier ?? {}), ...(keyAuthDefaults?.modelSupportsServiceTier ?? {}) }
|
|
209
|
+
: undefined;
|
|
210
|
+
putMergedMap("modelSupportsServiceTier", registryServiceTierDefaults, provider.modelSupportsServiceTier);
|
|
211
|
+
putMergedMap("modelSupportsReasoningSummaries", entry?.modelSupportsReasoningSummaries, provider.modelSupportsReasoningSummaries);
|
|
212
|
+
putMergedMap("modelSupportsVerbosity", entry?.modelSupportsVerbosity, provider.modelSupportsVerbosity);
|
|
213
|
+
for (const key of [
|
|
214
|
+
"modelCapabilities", "modelAutoCompactTokenLimits", "modelSuppressSyntheticMax",
|
|
215
|
+
"modelReasoningSummaryDelivery", "codexToolMode", "modelAdapters",
|
|
216
|
+
] as const) putScalar(key, undefined);
|
|
217
|
+
for (const key of ["modelContextWindows", "modelMaxInputTokens"] as const) {
|
|
218
|
+
const [value, source] = input.providerName === "openai-apikey"
|
|
219
|
+
? positiveCapMap(entry?.[key], provider[key])
|
|
220
|
+
: mapFill(entry?.[key], provider[key]);
|
|
221
|
+
put(key, value, source);
|
|
222
|
+
}
|
|
223
|
+
const [effortMap, effortMapSource] = mapFill(entry?.reasoningEffortMap, provider.reasoningEffortMap);
|
|
224
|
+
put("reasoningEffortMap", effortMap, effortMapSource);
|
|
225
|
+
const [modelEffortMap, modelEffortMapSource] = nestedMapFill(entry?.modelReasoningEffortMap, provider.modelReasoningEffortMap);
|
|
226
|
+
put("modelReasoningEffortMap", modelEffortMap, modelEffortMapSource);
|
|
227
|
+
for (const key of [
|
|
228
|
+
"noVisionModels", "noReasoningModels", "noTemperatureModels", "noTopPModels",
|
|
229
|
+
"noPenaltyModels", "noJsonSchemaModels", "autoToolChoiceOnlyModels",
|
|
230
|
+
"preserveReasoningContentModels", "requiresReasoningPlaceholderModels",
|
|
231
|
+
"reasoningSplitModels", "reasoningDetailsModels", "thinkingToggleModels", "thinkingBudgetModels",
|
|
232
|
+
] as const) putUnion(key, entry?.[key]);
|
|
233
|
+
for (const directModel of entry?.directReasoningEffortModels ?? []) {
|
|
234
|
+
const staleBudget = [directModel, ...(entry?.thinkingBudgetModels ?? [])];
|
|
235
|
+
const routedStaleBudget = [...(entry?.thinkingBudgetModels ?? []), directModel];
|
|
236
|
+
if (sameStringArray(provider.thinkingBudgetModels, staleBudget)
|
|
237
|
+
|| sameStringArray(provider.thinkingBudgetModels, routedStaleBudget)) {
|
|
238
|
+
providerPolicy.thinkingBudgetModels = [...(entry?.thinkingBudgetModels ?? [])];
|
|
239
|
+
providerProvenance.thinkingBudgetModels = "registry";
|
|
240
|
+
}
|
|
241
|
+
const explicitEfforts = Object.entries(provider.modelReasoningEfforts ?? {})
|
|
242
|
+
.filter(([key]) => key.toLowerCase() === directModel.toLowerCase());
|
|
243
|
+
if (explicitEfforts.length > 0) {
|
|
244
|
+
for (const key of Object.keys(providerPolicy.modelReasoningEfforts ?? {})) {
|
|
245
|
+
if (key.toLowerCase() === directModel.toLowerCase()) delete providerPolicy.modelReasoningEfforts![key];
|
|
246
|
+
}
|
|
247
|
+
for (const [key, value] of explicitEfforts) (providerPolicy.modelReasoningEfforts ??= {})[key] = [...value];
|
|
248
|
+
} else if (entry?.modelReasoningEfforts?.[directModel] !== undefined) {
|
|
249
|
+
(providerPolicy.modelReasoningEfforts ??= {})[directModel] = [...entry.modelReasoningEfforts[directModel]!];
|
|
250
|
+
}
|
|
251
|
+
const explicitDefault = Object.entries(provider.modelDefaultReasoningEfforts ?? {})
|
|
252
|
+
.filter(([key]) => key.toLowerCase() === directModel.toLowerCase());
|
|
253
|
+
if (explicitDefault.length > 0) {
|
|
254
|
+
for (const key of Object.keys(providerPolicy.modelDefaultReasoningEfforts ?? {})) {
|
|
255
|
+
if (key.toLowerCase() === directModel.toLowerCase()) delete providerPolicy.modelDefaultReasoningEfforts![key];
|
|
256
|
+
}
|
|
257
|
+
for (const [key, value] of explicitDefault) (providerPolicy.modelDefaultReasoningEfforts ??= {})[key] = value;
|
|
258
|
+
} else if (entry?.modelDefaultReasoningEfforts?.[directModel] !== undefined) {
|
|
259
|
+
(providerPolicy.modelDefaultReasoningEfforts ??= {})[directModel] = entry.modelDefaultReasoningEfforts[directModel]!;
|
|
260
|
+
}
|
|
261
|
+
const explicitMap = Object.entries(provider.modelReasoningEffortMap ?? {})
|
|
262
|
+
.filter(([key]) => key.toLowerCase() === directModel.toLowerCase());
|
|
263
|
+
for (const key of Object.keys(providerPolicy.modelReasoningEffortMap ?? {})) {
|
|
264
|
+
if (key.toLowerCase() === directModel.toLowerCase()) delete providerPolicy.modelReasoningEffortMap![key];
|
|
265
|
+
}
|
|
266
|
+
if (explicitMap.length > 0) {
|
|
267
|
+
for (const [key, value] of explicitMap) (providerPolicy.modelReasoningEffortMap ??= {})[key] = { ...value };
|
|
268
|
+
} else {
|
|
269
|
+
(providerPolicy.modelReasoningEffortMap ??= {})[directModel] = {};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const modelValue = <T>(record: Readonly<Record<string, T>> | undefined): T | undefined =>
|
|
273
|
+
legacyModelValue(record, input.modelId);
|
|
274
|
+
const modelSource = <T>(operator: Readonly<Record<string, T>> | undefined, registry: Readonly<Record<string, T>> | undefined): StaticPolicySource =>
|
|
275
|
+
legacyModelSource(operator, registry, input.modelId);
|
|
276
|
+
const modelOrProviderSource = <T>(
|
|
277
|
+
operatorMap: Readonly<Record<string, T>> | undefined, registryMap: Readonly<Record<string, T>> | undefined,
|
|
278
|
+
operatorDefault: T | undefined, registryDefault: T | undefined,
|
|
279
|
+
): StaticPolicySource => {
|
|
280
|
+
const exact = modelSource(operatorMap, registryMap);
|
|
281
|
+
return exact !== "unknown" ? exact : operatorDefault !== undefined ? "operator" : registryDefault !== undefined ? "registry" : "unknown";
|
|
282
|
+
};
|
|
283
|
+
const configuredAdapter = provider.modelAdapters?.[input.modelId];
|
|
284
|
+
const pin = pinnedWireAdapter(input.providerName, input.modelId);
|
|
285
|
+
const normalizedModelId = input.modelId.trim().toLowerCase();
|
|
286
|
+
const registryWire = wireDefault(entry?.modelWireDefaults?.[normalizedModelId], provider, entry,
|
|
287
|
+
input.inboundWire ?? "responses", resolvedAuthMode);
|
|
288
|
+
const explicitWire = configuredAdapter && MODEL_ADAPTER_OVERRIDE_ALLOWED.has(configuredAdapter)
|
|
289
|
+
? configuredAdapter
|
|
290
|
+
: undefined;
|
|
291
|
+
const providerAdapter = providerPolicy.adapter ?? provider.adapter;
|
|
292
|
+
const effectiveProviderForWire = resolvedAuthMode === provider.authMode
|
|
293
|
+
? provider
|
|
294
|
+
: { ...provider, authMode: resolvedAuthMode };
|
|
295
|
+
const canonicalForward = isCanonicalOpenAiForwardProvider(effectiveProviderForWire);
|
|
296
|
+
const adapter = pin
|
|
297
|
+
?? (!canonicalForward ? explicitWire ?? registryWire : undefined)
|
|
298
|
+
?? providerAdapter;
|
|
299
|
+
const adapterSource: StaticPolicySource = pin
|
|
300
|
+
? "hard-pin"
|
|
301
|
+
: explicitWire && !canonicalForward
|
|
302
|
+
? "operator"
|
|
303
|
+
: registryWire && !canonicalForward
|
|
304
|
+
? "registry"
|
|
305
|
+
: "provider-default";
|
|
306
|
+
const declaredModalities = input.modelCapabilities?.inputModalities;
|
|
307
|
+
const capabilityModalities = Array.isArray(declaredModalities) && declaredModalities.length > 0
|
|
308
|
+
? [...declaredModalities]
|
|
309
|
+
: undefined;
|
|
310
|
+
const exactContextWindow = modelValue(providerPolicy.modelContextWindows);
|
|
311
|
+
const familyContextWindow = providerPolicy.adapter === "anthropic"
|
|
312
|
+
? anthropicFamilyContextWindow(providerPolicy.modelContextWindows, input.modelId)
|
|
313
|
+
: undefined;
|
|
314
|
+
const modelContextWindow = exactContextWindow ?? familyContextWindow ?? providerPolicy.contextWindow;
|
|
315
|
+
const exactMaxInputTokens = modelValue(providerPolicy.modelMaxInputTokens);
|
|
316
|
+
const modelMaxInputTokens = exactMaxInputTokens !== undefined && modelContextWindow !== undefined
|
|
317
|
+
? Math.min(exactMaxInputTokens, modelContextWindow)
|
|
318
|
+
: exactMaxInputTokens;
|
|
319
|
+
const exactMaxOutputTokens = modelValue(providerPolicy.modelMaxOutputTokens);
|
|
320
|
+
const modelMaxOutputTokens = exactMaxOutputTokens ?? providerPolicy.defaultMaxOutputTokens;
|
|
321
|
+
const modelReasoningEfforts = modelValue(providerPolicy.modelReasoningEfforts) ?? providerPolicy.reasoningEfforts;
|
|
322
|
+
const modelSupportsReasoningSummaries = modelValue(providerPolicy.modelSupportsReasoningSummaries)
|
|
323
|
+
?? (modelValue(providerPolicy.modelReasoningSummaryDelivery) !== undefined ? true : undefined);
|
|
324
|
+
const modelSupportsVerbosity = modelValue(providerPolicy.modelSupportsVerbosity) ?? providerPolicy.supportsVerbosity;
|
|
325
|
+
const modelSupportsServiceTier = modelValue(providerPolicy.modelSupportsServiceTier) ?? providerPolicy.supportsServiceTier;
|
|
326
|
+
const model: ResolvedPerModelStaticPolicy = {
|
|
327
|
+
adapter,
|
|
328
|
+
...(modelContextWindow !== undefined ? { contextWindow: modelContextWindow } : {}),
|
|
329
|
+
...(capabilityModalities ?? modelValue(providerPolicy.modelInputModalities) !== undefined
|
|
330
|
+
? { inputModalities: capabilityModalities ?? modelValue(providerPolicy.modelInputModalities) }
|
|
331
|
+
: {}),
|
|
332
|
+
...(modelMaxInputTokens !== undefined ? { maxInputTokens: modelMaxInputTokens } : {}),
|
|
333
|
+
...(modelMaxOutputTokens !== undefined ? { maxOutputTokens: modelMaxOutputTokens } : {}),
|
|
334
|
+
...(modelReasoningEfforts !== undefined ? { reasoningEfforts: modelReasoningEfforts } : {}),
|
|
335
|
+
...(modelValue(providerPolicy.modelDefaultReasoningEfforts) !== undefined ? { defaultReasoningEffort: modelValue(providerPolicy.modelDefaultReasoningEfforts) } : {}),
|
|
336
|
+
...(modelSupportsReasoningSummaries !== undefined ? { supportsReasoningSummaries: modelSupportsReasoningSummaries } : {}),
|
|
337
|
+
...(modelSupportsVerbosity !== undefined ? { supportsVerbosity: modelSupportsVerbosity } : {}),
|
|
338
|
+
...(modelSupportsServiceTier !== undefined ? { supportsServiceTier: modelSupportsServiceTier } : {}),
|
|
339
|
+
...(modelSupportsServiceTier === true && entry?.fastTierDescription !== undefined
|
|
340
|
+
? { fastTierDescription: entry.fastTierDescription }
|
|
341
|
+
: {}),
|
|
342
|
+
...(entry?.directReasoningEffortModels?.includes(input.modelId) ? { directReasoningEffort: true } : {}),
|
|
343
|
+
...(entry?.modelResponsesUpstreamStreaming?.[normalizedModelId] !== undefined
|
|
344
|
+
? { responsesUpstreamStreaming: entry.modelResponsesUpstreamStreaming[normalizedModelId] }
|
|
345
|
+
: {}),
|
|
346
|
+
...(entry?.modelResponsesTerminalRepair?.[normalizedModelId] !== undefined
|
|
347
|
+
? { responsesTerminalRepair: entry.modelResponsesTerminalRepair[normalizedModelId] }
|
|
348
|
+
: {}),
|
|
349
|
+
};
|
|
350
|
+
const modelProvenance: Partial<Record<keyof ResolvedPerModelStaticPolicy, StaticPolicySource>> = {
|
|
351
|
+
adapter: adapterSource,
|
|
352
|
+
};
|
|
353
|
+
modelProvenance.contextWindow = modelOrProviderSource(provider.modelContextWindows, entry?.modelContextWindows, provider.contextWindow, entry?.contextWindow);
|
|
354
|
+
if (modelProvenance.contextWindow === "unknown" && familyContextWindow !== undefined) {
|
|
355
|
+
modelProvenance.contextWindow = anthropicFamilyContextWindow(provider.modelContextWindows, input.modelId) !== undefined
|
|
356
|
+
? "operator"
|
|
357
|
+
: "registry";
|
|
358
|
+
}
|
|
359
|
+
modelProvenance.inputModalities = capabilityModalities
|
|
360
|
+
? "operator-capability"
|
|
361
|
+
: modelSource(provider.modelInputModalities, entry?.modelInputModalities);
|
|
362
|
+
modelProvenance.maxInputTokens = modelSource(provider.modelMaxInputTokens, entry?.modelMaxInputTokens);
|
|
363
|
+
modelProvenance.maxOutputTokens = modelOrProviderSource(
|
|
364
|
+
provider.modelMaxOutputTokens,
|
|
365
|
+
entry?.modelMaxOutputTokens,
|
|
366
|
+
provider.defaultMaxOutputTokens,
|
|
367
|
+
entry?.defaultMaxOutputTokens,
|
|
368
|
+
);
|
|
369
|
+
modelProvenance.reasoningEfforts = modelOrProviderSource(provider.modelReasoningEfforts, entry?.modelReasoningEfforts, provider.reasoningEfforts, entry?.reasoningEfforts);
|
|
370
|
+
modelProvenance.defaultReasoningEffort = modelSource(
|
|
371
|
+
provider.modelDefaultReasoningEfforts,
|
|
372
|
+
entry?.modelDefaultReasoningEfforts,
|
|
373
|
+
);
|
|
374
|
+
modelProvenance.supportsReasoningSummaries = modelSource(provider.modelSupportsReasoningSummaries, entry?.modelSupportsReasoningSummaries);
|
|
375
|
+
if (modelProvenance.supportsReasoningSummaries === "unknown" && modelValue(provider.modelReasoningSummaryDelivery) !== undefined) {
|
|
376
|
+
modelProvenance.supportsReasoningSummaries = "operator";
|
|
377
|
+
}
|
|
378
|
+
modelProvenance.supportsVerbosity = modelOrProviderSource(provider.modelSupportsVerbosity, entry?.modelSupportsVerbosity, provider.supportsVerbosity, entry?.supportsVerbosity);
|
|
379
|
+
const exactServiceTierSource = modelSource(provider.modelSupportsServiceTier, registryServiceTierDefaults);
|
|
380
|
+
modelProvenance.supportsServiceTier = exactServiceTierSource !== "unknown" ? exactServiceTierSource
|
|
381
|
+
: providerProvenance.supportsServiceTier ?? "unknown";
|
|
382
|
+
modelProvenance.responsesUpstreamStreaming = model.responsesUpstreamStreaming === undefined ? "unknown" : "registry";
|
|
383
|
+
modelProvenance.responsesTerminalRepair = model.responsesTerminalRepair === undefined ? "unknown" : "registry";
|
|
384
|
+
modelProvenance.fastTierDescription = model.fastTierDescription === undefined ? "unknown" : "registry";
|
|
385
|
+
modelProvenance.directReasoningEffort = model.directReasoningEffort === undefined ? "unknown" : "registry";
|
|
386
|
+
const alias = input.effectiveAlias !== undefined
|
|
387
|
+
? input.effectiveAlias
|
|
388
|
+
: provider.alias !== undefined
|
|
389
|
+
? provider.alias
|
|
390
|
+
: entry?.alias;
|
|
391
|
+
const aliasSource: StaticPolicySource = input.effectiveAlias !== undefined
|
|
392
|
+
? input.effectiveAliasSource ?? "operator"
|
|
393
|
+
: provider.alias !== undefined
|
|
394
|
+
? "operator"
|
|
395
|
+
: entry?.alias !== undefined ? "registry" : "unknown";
|
|
396
|
+
return recursivelyFreeze(detachedClone({
|
|
397
|
+
version: 1 as const,
|
|
398
|
+
providerName: input.providerName,
|
|
399
|
+
modelId: input.modelId,
|
|
400
|
+
transportMatchedRegistry: entry !== undefined,
|
|
401
|
+
...(alias !== undefined ? { effectiveAlias: alias } : {}),
|
|
402
|
+
provider: providerPolicy as ResolvedProviderStaticPolicy,
|
|
403
|
+
model,
|
|
404
|
+
provenance: { alias: aliasSource, provider: providerProvenance, model: modelProvenance },
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair vision classifications a saved config inherited from a stale registry seed.
|
|
3
|
+
*
|
|
4
|
+
* `enrichProviderFromRegistry` is fill-only and asymmetric, which is right for a hand-tuned
|
|
5
|
+
* value but freezes a wrong one:
|
|
6
|
+
*
|
|
7
|
+
* - `noVisionModels` is filled ALL-OR-NOTHING (`if (!prov.noVisionModels && seed.noVisionModels)`),
|
|
8
|
+
* so a config saved while the list was current keeps that list forever.
|
|
9
|
+
* - `modelInputModalities` is filled per-key BENEATH the saved value, so a saved `["text"]` for
|
|
10
|
+
* one id survives every later registry correction.
|
|
11
|
+
*
|
|
12
|
+
* Correcting the registry therefore fixes new installs only. This projection is the other half.
|
|
13
|
+
* It repairs the two saved values a stale seed left behind, in both states that reach a running
|
|
14
|
+
* process:
|
|
15
|
+
*
|
|
16
|
+
* - the full pair: modalities still exactly the stale declaration, and the id in the list. Both
|
|
17
|
+
* are rewritten.
|
|
18
|
+
* - the half-repaired row: modalities already corrected but the id still in the list. The
|
|
19
|
+
* sidecar predicate reads `noVisionModels` BEFORE the modality list, so that row keeps
|
|
20
|
+
* stripping images until the name goes as well; this projection removes it and leaves the
|
|
21
|
+
* modalities untouched.
|
|
22
|
+
*
|
|
23
|
+
* The paired modalities value is the guard in both states, which is also why it has to be
|
|
24
|
+
* readable: a name in the list with no modality declaration beside it is ambiguous — either a
|
|
25
|
+
* half-finished repair or an entry the operator added on purpose — and this projection does not
|
|
26
|
+
* guess which. It leaves that row alone.
|
|
27
|
+
*
|
|
28
|
+
* Identity is the same rule enrichment uses, read from the same helper: the registry must still
|
|
29
|
+
* own the row (`providerMatchesRegistryTransport`, the check `enrichProviderFromRegistry` makes
|
|
30
|
+
* before it writes registry metadata) and the row must still be on the entry's adapter.
|
|
31
|
+
* `opencode-go` is an existing key preset without `preserveCustomDestination`, so the id alone
|
|
32
|
+
* claims a row — exactly as it does for enrichment — while an entry that opts into destination
|
|
33
|
+
* preservation narrows this projection for free.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here writes `modelCapabilities`. That is the dedicated per-model axis, it outranks
|
|
36
|
+
* every source this file touches, and it is where a deliberate text-only override belongs
|
|
37
|
+
* (`ocx provider edit <provider> --model <id> --text-only` writes it).
|
|
38
|
+
*
|
|
39
|
+
* Scope is deliberately one entry. The Go gateway's `deepseek-v4.1-flash` was declared text-only
|
|
40
|
+
* from jawcode metadata and was measured natively multimodal on 2026-09-19 (see the note at that
|
|
41
|
+
* entry in `registry/entries-core.ts`). Its sibling `deepseek-v4-flash` still rejects images
|
|
42
|
+
* upstream, and the sibling Zen tiers (`opencode-zen`, `opencode-free`) could not be probed at
|
|
43
|
+
* all — an unverified tier is not evidence, so neither is touched here.
|
|
44
|
+
*/
|
|
45
|
+
import { PROVIDER_REGISTRY, providerMatchesRegistryTransport } from "./registry";
|
|
46
|
+
import type { OcxConfig } from "../types";
|
|
47
|
+
|
|
48
|
+
export interface StaleVisionClassification {
|
|
49
|
+
/** Registry provider id whose saved rows may carry the wrong classification. */
|
|
50
|
+
provider: string;
|
|
51
|
+
model: string;
|
|
52
|
+
/** The stale saved modalities this migration is allowed to replace, and nothing else. */
|
|
53
|
+
fromModalities: string[];
|
|
54
|
+
toModalities: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface StaleVisionClassificationProjection {
|
|
58
|
+
config: OcxConfig;
|
|
59
|
+
changed: boolean;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const STALE_VISION_CLASSIFICATIONS: readonly StaleVisionClassification[] = [
|
|
64
|
+
{
|
|
65
|
+
provider: "opencode-go",
|
|
66
|
+
model: "deepseek-v4.1-flash",
|
|
67
|
+
fromModalities: ["text"],
|
|
68
|
+
toModalities: ["text", "image"],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether the registry still owns the row whose saved values would be rewritten.
|
|
74
|
+
*
|
|
75
|
+
* `providerMatchesRegistryTransport` is the rule enrichment applies, so this projection and the
|
|
76
|
+
* fill that put the stale values there answer identity the same way. Adapter equality stays as
|
|
77
|
+
* an additional tightening: a row moved onto another wire is not the row the seed described,
|
|
78
|
+
* however the registry entry is pinned.
|
|
79
|
+
*/
|
|
80
|
+
function providerStillMatchesRegistry(id: string, prov: OcxConfig["providers"][string]): boolean {
|
|
81
|
+
const entry = PROVIDER_REGISTRY.find(row => row.id === id);
|
|
82
|
+
if (entry === undefined || entry.adapter !== prov.adapter) return false;
|
|
83
|
+
return providerMatchesRegistryTransport(id, prov);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sameModalities(current: unknown, expected: readonly string[]): boolean {
|
|
87
|
+
return Array.isArray(current)
|
|
88
|
+
&& current.length === expected.length
|
|
89
|
+
&& current.every((value, index) => value === expected[index]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Pure projection. The caller decides whether to persist. */
|
|
93
|
+
export function projectStaleVisionClassifications(
|
|
94
|
+
config: OcxConfig,
|
|
95
|
+
entries: readonly StaleVisionClassification[] = STALE_VISION_CLASSIFICATIONS,
|
|
96
|
+
): StaleVisionClassificationProjection {
|
|
97
|
+
const warnings: string[] = [];
|
|
98
|
+
const repaired = new Map<string, string[]>();
|
|
99
|
+
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
const prov = config.providers?.[entry.provider];
|
|
102
|
+
if (!prov) continue;
|
|
103
|
+
if (!providerStillMatchesRegistry(entry.provider, prov)) continue;
|
|
104
|
+
const modalities = prov.modelInputModalities;
|
|
105
|
+
if (!modalities) continue;
|
|
106
|
+
// The paired modalities value is the guard, and it decides which of the two states above this
|
|
107
|
+
// row is in. Anything else is a declaration this file does not own.
|
|
108
|
+
const saved = modalities[entry.model];
|
|
109
|
+
const stale = sameModalities(saved, entry.fromModalities);
|
|
110
|
+
const alreadyMigrated = sameModalities(saved, entry.toModalities);
|
|
111
|
+
if (!stale && !alreadyMigrated) continue;
|
|
112
|
+
const visionList = Array.isArray(prov.noVisionModels) ? prov.noVisionModels : undefined;
|
|
113
|
+
const listed = visionList !== undefined && visionList.includes(entry.model);
|
|
114
|
+
// Half-repaired rows have nothing left to do once the name is gone; the full pair is rewritten
|
|
115
|
+
// whether or not the name was ever listed, because `derive.ts` fills the two fields
|
|
116
|
+
// independently and a per-key modality fill can land without the all-or-nothing list.
|
|
117
|
+
if (!stale && !listed) continue;
|
|
118
|
+
if (stale) modalities[entry.model] = [...entry.toModalities];
|
|
119
|
+
if (visionList !== undefined && listed) {
|
|
120
|
+
prov.noVisionModels = visionList.filter(id => id !== entry.model);
|
|
121
|
+
}
|
|
122
|
+
const repairedList = repaired.get(entry.provider) ?? [];
|
|
123
|
+
repairedList.push(stale
|
|
124
|
+
? `${entry.model} ${entry.fromModalities.join("+")} -> ${entry.toModalities.join("+")}`
|
|
125
|
+
: `${entry.model} dropped from noVisionModels (modalities already ${entry.toModalities.join("+")})`);
|
|
126
|
+
repaired.set(entry.provider, repairedList);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const [provider, list] of repaired) {
|
|
130
|
+
warnings.push(
|
|
131
|
+
`repaired the stale registry vision seed for ${list.length} model(s) on "${provider}": `
|
|
132
|
+
+ `${list.join(", ")}.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { config, changed: repaired.size > 0, warnings };
|
|
137
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Some routed models decorate the first and last lines as
|
|
4
4
|
// `*** Begin Patch ***` / `*** End Patch ***`. Codex rejects those otherwise
|
|
5
|
-
// valid custom-tool payloads. Repair is deliberately limited to
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// valid custom-tool payloads. Repair of executable bodies is deliberately limited to
|
|
6
|
+
// unambiguous wrapper mistakes: one recognized alternate field or one complete outer
|
|
7
|
+
// Markdown fence. Ordinary `exec` JavaScript remains byte-identical.
|
|
8
8
|
//
|
|
9
9
|
// This is the same intent boundary as `src/lib/tool-argument-integers.ts`:
|
|
10
10
|
// repair the one faithful reading, leave genuine patch content alone.
|
|
@@ -22,20 +22,40 @@ const PATCH_BEGIN = "*** Begin Patch";
|
|
|
22
22
|
const PATCH_END = "*** End Patch";
|
|
23
23
|
const TOP_LEVEL_PATCH_ENVELOPE = /^(\*\*\* Begin Patch(?: \*\*\*)?)(\r?\n)([\s\S]*)(\r?\n)(\*\*\* End Patch(?: \*\*\*)?)(\r?\n)?$/;
|
|
24
24
|
const PATCH_OPERATION_LINE = /^\*\*\* (?:Add|Update|Delete) File: .+$/m;
|
|
25
|
+
const OUTER_MARKDOWN_CODE_FENCE = /^```[^\r\n]*\r?\n([\s\S]*?)\r?\n```$/;
|
|
26
|
+
const FREEFORM_FALLBACK_KEYS: Readonly<Record<string, readonly string[]>> = {
|
|
27
|
+
exec: ["code", "script", "js", "javascript", "command", "cmd", "content"],
|
|
28
|
+
apply_patch: ["patch", "content"],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function stripMarkdownCodeFence(text: string, toolName: string): string {
|
|
32
|
+
if (toolName !== "exec" && toolName !== "apply_patch") return text;
|
|
33
|
+
const match = OUTER_MARKDOWN_CODE_FENCE.exec(text.trim());
|
|
34
|
+
return match ? match[1] : text;
|
|
35
|
+
}
|
|
25
36
|
|
|
26
37
|
/** Unwrap the `{input:string}` function-call wrapper used for freeform tools. */
|
|
27
|
-
export function unwrapFreeformToolInput(argumentsText: unknown): string {
|
|
38
|
+
export function unwrapFreeformToolInput(argumentsText: unknown, toolName = ""): string {
|
|
28
39
|
if (typeof argumentsText !== "string") return "";
|
|
29
40
|
try {
|
|
30
41
|
const parsed: unknown = JSON.parse(argumentsText);
|
|
31
42
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
43
|
+
const record = parsed as Record<string, unknown>;
|
|
44
|
+
if (Object.prototype.hasOwnProperty.call(record, "input")) {
|
|
45
|
+
return typeof record.input === "string"
|
|
46
|
+
? stripMarkdownCodeFence(record.input, toolName)
|
|
47
|
+
: argumentsText;
|
|
48
|
+
}
|
|
49
|
+
const fallbackKeys = FREEFORM_FALLBACK_KEYS[toolName] ?? [];
|
|
50
|
+
const candidates = fallbackKeys.filter(key => typeof record[key] === "string");
|
|
51
|
+
if (candidates.length === 1) {
|
|
52
|
+
return stripMarkdownCodeFence(record[candidates[0]] as string, toolName);
|
|
53
|
+
}
|
|
34
54
|
}
|
|
35
55
|
} catch {
|
|
36
56
|
// The string is the freeform body, not nested JSON.
|
|
37
57
|
}
|
|
38
|
-
return argumentsText;
|
|
58
|
+
return stripMarkdownCodeFence(argumentsText, toolName);
|
|
39
59
|
}
|
|
40
60
|
|
|
41
61
|
/**
|
|
@@ -92,17 +112,18 @@ export function mayBecomePatchEnvelope(text: string): boolean {
|
|
|
92
112
|
/**
|
|
93
113
|
* Repair freeform input before Codex sees it.
|
|
94
114
|
*
|
|
95
|
-
* Only a bare or reserved-`functions`
|
|
96
|
-
* repair
|
|
97
|
-
* freeform input are unwrapped and left
|
|
115
|
+
* Only a bare or reserved-`functions` tool may receive fallback-field or outer-fence
|
|
116
|
+
* repair, and only `apply_patch` may receive delimiter repair. Remote namespaces own
|
|
117
|
+
* their grammar; those bodies and every other freeform input are unwrapped and left
|
|
118
|
+
* byte-exact.
|
|
98
119
|
*/
|
|
99
120
|
export function repairFreeformToolInput(
|
|
100
121
|
argumentsText: unknown,
|
|
101
122
|
toolName = "",
|
|
102
123
|
namespace?: string,
|
|
103
124
|
): string {
|
|
104
|
-
const unwrapped = unwrapFreeformToolInput(argumentsText);
|
|
105
125
|
const ownsApplyPatchGrammar = namespace === undefined || namespace === "functions";
|
|
126
|
+
const unwrapped = unwrapFreeformToolInput(argumentsText, ownsApplyPatchGrammar ? toolName : "");
|
|
106
127
|
return ownsApplyPatchGrammar && toolName === "apply_patch"
|
|
107
128
|
? normalizeApplyPatchDelimiters(unwrapped)
|
|
108
129
|
: unwrapped;
|