@bitkyc08/opencodex 2.86.0 → 2.87.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/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +38 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +608 -284
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { OPENCODE_GO_LEGACY_NAME_PINS } from "../types/opencode-go";
|
|
2
|
+
import { captureWireAdapterHardPins } from "../types/wire";
|
|
3
|
+
import { declaredUnsupportedHostedTools, isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
|
|
4
|
+
import { sanitizeOpenCodeZenOverridesForLoad } from "./opencode-zen-upgrade";
|
|
5
|
+
|
|
6
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
8
|
+
? value as Record<string, unknown> : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** File reads alone repair overrides admitted before OpenCode's native-wire upgrade. */
|
|
12
|
+
export function sanitizeOpenCodeOverridesForLoad(parsed: unknown): void {
|
|
13
|
+
sanitizeOpenCodeZenOverridesForLoad(parsed);
|
|
14
|
+
const providers = record(record(parsed)?.providers);
|
|
15
|
+
const provider = providers && Object.hasOwn(providers, "opencode-go")
|
|
16
|
+
? record(providers["opencode-go"]) : undefined;
|
|
17
|
+
if (!provider) return;
|
|
18
|
+
// Use validation's destination/auth rules, including the Go preset's fixed URL.
|
|
19
|
+
const pins = captureWireAdapterHardPins("opencode-go", provider);
|
|
20
|
+
const newlyPinned = (model: string): boolean => Object.hasOwn(pins, model)
|
|
21
|
+
&& !OPENCODE_GO_LEGACY_NAME_PINS.has(model);
|
|
22
|
+
const overrides = record(provider.modelAdapters) ?? {};
|
|
23
|
+
const preferences = record(provider.modelPreferHostedTools);
|
|
24
|
+
let preferencesChanged = false;
|
|
25
|
+
if (preferences) {
|
|
26
|
+
const unsupported = declaredUnsupportedHostedTools(provider as { unsupportedHostedTools?: readonly string[] });
|
|
27
|
+
for (const [model, tools] of Object.entries(preferences)) {
|
|
28
|
+
// Go's registry owns the old provider-wide Chat wire. Only an explicit
|
|
29
|
+
// Responses override could previously make these native models eligible.
|
|
30
|
+
if (!newlyPinned(model.trim()) || overrides[model] !== "openai-responses"
|
|
31
|
+
|| !Array.isArray(tools) || tools.length === 0 || tools.some(tool => tool !== "image_generation")
|
|
32
|
+
|| isHostedToolUnsupportedForModel(model, "image_generation", undefined, unsupported)) continue;
|
|
33
|
+
delete preferences[model];
|
|
34
|
+
preferencesChanged = true;
|
|
35
|
+
}
|
|
36
|
+
if (preferencesChanged && Object.keys(preferences).length === 0) delete provider.modelPreferHostedTools;
|
|
37
|
+
}
|
|
38
|
+
let overridesChanged = false;
|
|
39
|
+
for (const [model, adapter] of Object.entries(overrides)) {
|
|
40
|
+
// Leave malformed or previously incompatible preferences visible on every pass.
|
|
41
|
+
if (preferences && Object.hasOwn(preferences, model)) continue;
|
|
42
|
+
if (newlyPinned(model.trim()) && (adapter === "openai-chat" || adapter === "openai-responses")) {
|
|
43
|
+
delete overrides[model];
|
|
44
|
+
overridesChanged = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (overridesChanged) {
|
|
48
|
+
if (Object.keys(overrides).length === 0) delete provider.modelAdapters;
|
|
49
|
+
console.warn("OpenCode Go native models now use their required APIs; obsolete Chat/Responses overrides were ignored while loading config.json.");
|
|
50
|
+
}
|
|
51
|
+
if (preferencesChanged) {
|
|
52
|
+
console.warn("OpenCode Go native models now use their required APIs; legacy Responses-only hosted-tool preferences were ignored while loading config.json.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { openCodeZenBaseUrl } from "../types/opencode-zen";
|
|
2
|
+
import { declaredUnsupportedHostedTools, isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
|
|
3
|
+
|
|
4
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
5
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
6
|
+
? value as Record<string, unknown> : undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isGeminiModel(model: string): boolean {
|
|
10
|
+
return model.trim().toLowerCase().startsWith("gemini-");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Repair legacy Zen wire overrides and dependent preferences in file-backed reads only. */
|
|
14
|
+
export function sanitizeOpenCodeZenOverridesForLoad(parsed: unknown): void {
|
|
15
|
+
const providers = record(record(parsed)?.providers);
|
|
16
|
+
const provider = providers && Object.hasOwn(providers, "opencode-zen")
|
|
17
|
+
? record(providers["opencode-zen"]) : undefined;
|
|
18
|
+
if (!provider || !openCodeZenBaseUrl(provider)) return;
|
|
19
|
+
const overrides = record(provider.modelAdapters) ?? {};
|
|
20
|
+
const preferences = record(provider.modelPreferHostedTools);
|
|
21
|
+
let preferencesChanged = false;
|
|
22
|
+
if (preferences) {
|
|
23
|
+
const unsupported = declaredUnsupportedHostedTools(provider as { unsupportedHostedTools?: readonly string[] });
|
|
24
|
+
for (const [model, tools] of Object.entries(preferences)) {
|
|
25
|
+
// Capture the old wire before deleting overrides. An explicit non-Responses entry
|
|
26
|
+
// must not inherit a provider-wide Responses default for this migration.
|
|
27
|
+
const oldAdapter = Object.hasOwn(overrides, model) ? overrides[model] : provider.adapter;
|
|
28
|
+
if (!isGeminiModel(model) || oldAdapter !== "openai-responses"
|
|
29
|
+
|| !Array.isArray(tools) || tools.length === 0 || tools.some(tool => tool !== "image_generation")
|
|
30
|
+
|| isHostedToolUnsupportedForModel(model, "image_generation", undefined, unsupported)) continue;
|
|
31
|
+
delete preferences[model];
|
|
32
|
+
preferencesChanged = true;
|
|
33
|
+
}
|
|
34
|
+
if (preferencesChanged && Object.keys(preferences).length === 0) delete provider.modelPreferHostedTools;
|
|
35
|
+
}
|
|
36
|
+
let overridesChanged = false;
|
|
37
|
+
for (const [model, adapter] of Object.entries(overrides)) {
|
|
38
|
+
// Retain invalid preference/wire pairs so repeated normalization cannot admit them.
|
|
39
|
+
if (preferences && Object.hasOwn(preferences, model)) continue;
|
|
40
|
+
if (isGeminiModel(model) && (adapter === "openai-chat" || adapter === "openai-responses")) {
|
|
41
|
+
delete overrides[model];
|
|
42
|
+
overridesChanged = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (overridesChanged) {
|
|
46
|
+
if (Object.keys(overrides).length === 0) delete provider.modelAdapters;
|
|
47
|
+
console.warn("OpenCode Zen Gemini models now use the native Google API; obsolete Chat/Responses overrides were ignored while loading config.json.");
|
|
48
|
+
}
|
|
49
|
+
if (preferencesChanged) {
|
|
50
|
+
console.warn("OpenCode Zen Gemini models now use the native Google API; legacy Responses-only hosted-tool preferences were ignored while loading config.json.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -24,21 +24,19 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { createHash } from "node:crypto";
|
|
27
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
27
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync } from "node:fs";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { dirname, join, resolve } from "node:path";
|
|
30
30
|
import { atomicWriteFileNoFollow } from "./atomic-write";
|
|
31
31
|
import { getConfigDir } from "./paths";
|
|
32
|
-
import { assertNotRealHomeUnderTest } from "../lib/test-home-guard";
|
|
32
|
+
import { assertNotRealHomeUnderTest, isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
33
33
|
import { hardenSecretDir } from "../lib/windows-secret-acl";
|
|
34
34
|
|
|
35
35
|
const REGISTRY_DIR_NAME = "ocx-homes";
|
|
36
36
|
/**
|
|
37
|
-
* Test seam, not a user knob
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* fixture home; production always leaves it unset so the default home stays the
|
|
41
|
-
* one locator every sibling can find.
|
|
37
|
+
* Test seam, not a user knob. The sandbox pins this for inherited child environments;
|
|
38
|
+
* guarded callers without it use their selected config home. Production leaves it
|
|
39
|
+
* unset so the default home stays the one locator every sibling can find.
|
|
42
40
|
*/
|
|
43
41
|
const REGISTRY_DIR_ENV = "OCX_OWNER_REGISTRY_DIR";
|
|
44
42
|
const REGISTRY_ENTRY_SUFFIX = ".json";
|
|
@@ -60,8 +58,23 @@ function registryBaseDir(): string {
|
|
|
60
58
|
/** The shared directory the registry lives under. */
|
|
61
59
|
export function ownerRegistryDir(): string {
|
|
62
60
|
const override = process.env[REGISTRY_DIR_ENV]?.trim();
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const dir = override ? resolve(override)
|
|
62
|
+
: join(isTestHomeGuardArmed() ? getConfigDir() : registryBaseDir(), REGISTRY_DIR_NAME);
|
|
63
|
+
// Bun caches the startup home, so HOME rewrites cannot isolate this anchor.
|
|
64
|
+
// Check reads and removals too, including an inherited override or symlink that
|
|
65
|
+
// points back into the protected home. An unpinned guarded caller fails closed.
|
|
66
|
+
if (isTestHomeGuardArmed()) {
|
|
67
|
+
for (let ancestor = dir; ;) {
|
|
68
|
+
assertNotRealHomeUnderTest(ancestor);
|
|
69
|
+
// Follow an existing alias before walking parents; missing children reach
|
|
70
|
+
// their nearest existing ancestor on the next iterations.
|
|
71
|
+
try { ancestor = realpathSync.native(ancestor); } catch { /* not created yet */ }
|
|
72
|
+
const parent = dirname(ancestor);
|
|
73
|
+
if (parent === ancestor) break;
|
|
74
|
+
ancestor = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return dir;
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
function registryEntryPath(dir: string, home: string): string {
|
|
@@ -76,14 +89,6 @@ function registryEntryPath(dir: string, home: string): string {
|
|
|
76
89
|
export function registerOwnerRegistryHome(home: string): void {
|
|
77
90
|
try {
|
|
78
91
|
const dir = ownerRegistryDir();
|
|
79
|
-
// Guard every ancestor of the write target: an override that still resolves
|
|
80
|
-
// under the protected ~/.opencodex must not slip past the test home guard.
|
|
81
|
-
for (let ancestor = dir; ;) {
|
|
82
|
-
assertNotRealHomeUnderTest(ancestor);
|
|
83
|
-
const parent = dirname(ancestor);
|
|
84
|
-
if (parent === ancestor) break;
|
|
85
|
-
ancestor = parent;
|
|
86
|
-
}
|
|
87
92
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
88
93
|
// This locator contains no secret, but its deterministic entry names must not
|
|
89
94
|
// give another local user a place to plant a destination symlink. Refuse a
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
2
|
+
import { resolveDshAccountMessagesUrl } from "../providers/dsh-account-destination";
|
|
2
3
|
import { redactSecretString } from "../lib/redact";
|
|
3
4
|
import {
|
|
4
5
|
isValidModelDiscoveryModelId,
|
|
@@ -256,10 +257,11 @@ export function autoReviewModelTargetConfigError(
|
|
|
256
257
|
field = "autoReviewModel",
|
|
257
258
|
allowClear = false,
|
|
258
259
|
): string | null {
|
|
259
|
-
if (value === undefined || (allowClear &&
|
|
260
|
+
if (value === undefined || (allowClear && value === null)) return null;
|
|
260
261
|
if (typeof value !== "string") return `${field} must be a string`;
|
|
261
262
|
const trimmed = value.trim();
|
|
262
|
-
|
|
263
|
+
// `allowClear` callers trim then delete, so whitespace-only is a clear, not a blank selector.
|
|
264
|
+
if (!trimmed) return allowClear ? null : `${field} must be nonblank`;
|
|
263
265
|
if (trimmed.length > 1024 || AUTO_REVIEW_MODEL_CONTROL_CHARS.test(trimmed)) {
|
|
264
266
|
return `${field} must be a catalog selector without whitespace or control characters`;
|
|
265
267
|
}
|
|
@@ -271,6 +273,26 @@ export function isValidAutoReviewModel(value: unknown): value is string {
|
|
|
271
273
|
return typeof value === "string" && autoReviewModelTargetConfigError(value) === null;
|
|
272
274
|
}
|
|
273
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Validate the global auto-review override block the Models settings panel writes.
|
|
278
|
+
*
|
|
279
|
+
* `enabled` is optional so a save may carry only the model during an edit; `model` follows the
|
|
280
|
+
* same selector rules as the provider-scoped field, and an empty value clears the override.
|
|
281
|
+
*/
|
|
282
|
+
export function autoReviewOverrideConfigError(value: unknown, field = "autoReviewOverride"): string | null {
|
|
283
|
+
if (value === undefined || value === null) return null;
|
|
284
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return `${field} must be a plain object`;
|
|
285
|
+
const prototype = Object.getPrototypeOf(value);
|
|
286
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
287
|
+
return `${field} must be a plain object with own properties`;
|
|
288
|
+
}
|
|
289
|
+
const block = value as Record<string, unknown>;
|
|
290
|
+
if (block.enabled !== undefined && typeof block.enabled !== "boolean") {
|
|
291
|
+
return `${field}.enabled must be a boolean`;
|
|
292
|
+
}
|
|
293
|
+
return autoReviewModelTargetConfigError(block.model, `${field}.model`, true);
|
|
294
|
+
}
|
|
295
|
+
|
|
274
296
|
/** Canonical model key used for map matching, duplicate detection, and route tombstones. */
|
|
275
297
|
export function canonicalAutoReviewModelKey(modelId: string): string {
|
|
276
298
|
return encodeRoutedModelId(modelId.trim());
|
|
@@ -390,6 +412,27 @@ export function modelAdapterRecordConfigError(
|
|
|
390
412
|
}
|
|
391
413
|
|
|
392
414
|
|
|
415
|
+
/**
|
|
416
|
+
* The `dsh-account` row owns the DeepSeek Harness account token, which every helper resolves by this
|
|
417
|
+
* provider name. Keeping the row on its own adapter and DeepSeek's Anthropic host means no generic
|
|
418
|
+
* helper (Anthropic sidecars, discovery, probes) can be pointed at another destination with it.
|
|
419
|
+
*/
|
|
420
|
+
export function credentialPinnedProviderConfigError(
|
|
421
|
+
providerName: string,
|
|
422
|
+
provider: { adapter?: unknown; baseUrl?: unknown },
|
|
423
|
+
): string | null {
|
|
424
|
+
if (providerName !== "dsh-account") return null;
|
|
425
|
+
if (provider.adapter !== undefined && provider.adapter !== "dsh-account") {
|
|
426
|
+
return 'adapter must stay "dsh-account": its account token is pinned to that adapter';
|
|
427
|
+
}
|
|
428
|
+
if (typeof provider.baseUrl === "string") {
|
|
429
|
+
try { resolveDshAccountMessagesUrl(provider.baseUrl); } catch {
|
|
430
|
+
return "baseUrl must stay on https://api.deepseek.com/anthropic: its account token is pinned to that host";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
393
436
|
function capabilityRecord(value: unknown): value is Record<string, unknown> {
|
|
394
437
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
395
438
|
&& [Object.prototype, null].includes(Object.getPrototypeOf(value));
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "../../codex/quota-types";
|
|
2
|
+
import { validRemainingQuotaAccountIds } from "../../codex/account-use-remaining";
|
|
2
3
|
import * as z from "zod/v4";
|
|
3
4
|
import { anthropicAccountPoolSchema } from "./anthropic-account-pool";
|
|
4
5
|
import { compactionRecoverySchema } from "./compaction-recovery";
|
|
6
|
+
import { nativeReasoningRetentionSchema } from "./native-reasoning-retention";
|
|
5
7
|
import { desktopCompatibilitySchema } from "./desktop-compatibility";
|
|
6
8
|
import { blockedModelRedirectsSchema } from "./blocked-model-redirects";
|
|
9
|
+
import { mainAccountHardLockFallbackSchema } from "./main-account-hard-lock-fallback";
|
|
7
10
|
import {
|
|
8
11
|
agentTaskRecoverySchema,
|
|
9
12
|
catalogAutoRefreshSchema,
|
|
@@ -29,7 +32,9 @@ import {
|
|
|
29
32
|
CODEX_ACCOUNT_NAMESPACE_ACCOUNT_ID_COLLISION_ERROR,
|
|
30
33
|
codexAccountNamespacesSchema,
|
|
31
34
|
modelPinnedEffortsSchema,
|
|
35
|
+
autoReviewOverrideSchema,
|
|
32
36
|
compactionRoutingSchema,
|
|
37
|
+
reasoningRetentionSchema,
|
|
33
38
|
memoryModelSettingSchema,
|
|
34
39
|
memoryModelsSchema,
|
|
35
40
|
modelPreferHostedToolsConfigError,
|
|
@@ -49,6 +54,7 @@ import {
|
|
|
49
54
|
providerHeadersConfigError,
|
|
50
55
|
providerForwardClientHeadersConfigError,
|
|
51
56
|
reasoningSummaryDeliveryRecordConfigError,
|
|
57
|
+
credentialPinnedProviderConfigError,
|
|
52
58
|
} from "../provider-validation";
|
|
53
59
|
import {
|
|
54
60
|
CODEX_ACCOUNT_NAMESPACE_COMBO_ALIAS_COLLISION_ERROR,
|
|
@@ -190,9 +196,15 @@ export const configSchema = z.object({
|
|
|
190
196
|
z.object({ enabled: z.literal(true), port: z.number().int().min(1).max(65535).optional() }),
|
|
191
197
|
]).optional().catch(undefined),
|
|
192
198
|
providers: z.record(z.string(), providerConfigSchema),
|
|
199
|
+
// Models settings panel writes the global reviewer override; an invalid hand edit is
|
|
200
|
+
// stripped by load degradation so it cannot reject the providers that carry it.
|
|
201
|
+
autoReviewOverride: autoReviewOverrideSchema.optional(),
|
|
193
202
|
modelPinnedEfforts: modelPinnedEffortsSchema.optional(),
|
|
194
203
|
compactionRouting: compactionRoutingSchema.optional().catch(undefined),
|
|
195
204
|
compactionRecovery: compactionRecoverySchema.optional().catch(undefined),
|
|
205
|
+
// A bad hand edit disables only this block; writes validate the catch-free leaf first.
|
|
206
|
+
reasoningRetention: reasoningRetentionSchema.optional().catch(undefined),
|
|
207
|
+
nativeReasoningRetention: nativeReasoningRetentionSchema.optional().catch(undefined),
|
|
196
208
|
desktopCompatibility: desktopCompatibilitySchema.optional().catch(undefined),
|
|
197
209
|
// A hand-edited malformed phase disables only that phase instead of rejecting
|
|
198
210
|
// providers/apiKeys, matching the load-time degradation notice; the management write
|
|
@@ -207,6 +219,7 @@ export const configSchema = z.object({
|
|
|
207
219
|
.catch(undefined),
|
|
208
220
|
defaultProvider: z.string().min(1).default("openai"),
|
|
209
221
|
defaultModelAliases: z.boolean().optional(),
|
|
222
|
+
preferModelAliasIds: z.boolean().optional().catch(false),
|
|
210
223
|
// Malformed hand edits disable this opt-in projection without rejecting providers.
|
|
211
224
|
cursorEffortRows: z.boolean().optional().catch(false),
|
|
212
225
|
// Fast selectors default on; malformed hand edits disable them without rejecting providers.
|
|
@@ -221,6 +234,9 @@ export const configSchema = z.object({
|
|
|
221
234
|
short: z.number().int().min(MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT).max(100).optional().catch(undefined),
|
|
222
235
|
long: z.number().int().min(MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT).max(100).optional().catch(undefined),
|
|
223
236
|
}).optional().catch(undefined),
|
|
237
|
+
// A malformed fallback section disables only the reroute, never the lock's refusal
|
|
238
|
+
// behavior; the management write boundary still rejects the bad value.
|
|
239
|
+
codexMainAccountHardLockFallback: mainAccountHardLockFallbackSchema.optional().catch(undefined),
|
|
224
240
|
// #2999: a malformed hand edit falls back to auto detection rather than rejecting the config.
|
|
225
241
|
nativeMainRefresh: z.enum(["codex", "opencodex"]).optional().catch(undefined),
|
|
226
242
|
// Future versions remain opaque through passthrough-compatible whole-config saves.
|
|
@@ -292,6 +308,7 @@ export const configSchema = z.object({
|
|
|
292
308
|
// so a hand-edited typo cannot trip the backup-and-defaults repair path; the write path rejects
|
|
293
309
|
// it (creditCodexAccountIdsError in diagnostics).
|
|
294
310
|
creditCodexAccountIds: z.array(z.string().regex(/^[a-zA-Z0-9._-]{1,64}$/)).optional().catch(undefined),
|
|
311
|
+
codexUseRemainingQuotaAccountIds: z.custom<string[]>(validRemainingQuotaAccountIds).optional().catch(undefined),
|
|
295
312
|
// A malformed policy degrades to "no policy" rather than failing the parse, so a hand-edited
|
|
296
313
|
// typo cannot trip the backup-and-defaults repair path and wipe providers or pool accounts.
|
|
297
314
|
// Silently ignoring it would be its own trap, so the write path rejects it and loadConfig warns.
|
|
@@ -307,6 +324,7 @@ export const configSchema = z.object({
|
|
|
307
324
|
codexAccountAutoSwitchThresholds: z.unknown().optional().transform(salvageCodexAccountAutoSwitchThresholds),
|
|
308
325
|
// An invalid optional preference must not discard providers or credential rows.
|
|
309
326
|
codexAccountPriorityFailback: z.boolean().optional().catch(false),
|
|
327
|
+
codexPreserveSignedInAccount: z.boolean().optional().catch(false),
|
|
310
328
|
// Absent or invalid keeps the sliding window on. Explicit false restores consecutive-only steering.
|
|
311
329
|
codexFailureWindow: z.boolean().optional().catch(undefined),
|
|
312
330
|
codexPinnedTransientPolicy: z.enum(["hold", "detour-new-threads"]).optional().catch("hold"),
|
|
@@ -594,6 +612,10 @@ export const configSchema = z.object({
|
|
|
594
612
|
message: modelAdaptersError,
|
|
595
613
|
});
|
|
596
614
|
}
|
|
615
|
+
const credentialPinError = credentialPinnedProviderConfigError(name, provider);
|
|
616
|
+
if (credentialPinError) {
|
|
617
|
+
ctx.addIssue({ code: "custom", path: ["providers", redactSecretString(name)], message: credentialPinError });
|
|
618
|
+
}
|
|
597
619
|
const preferHostedToolsError = modelPreferHostedToolsConfigError(
|
|
598
620
|
(provider as { modelPreferHostedTools?: unknown }).modelPreferHostedTools,
|
|
599
621
|
"modelPreferHostedTools",
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
modelPinnedEffortsConfigError,
|
|
7
7
|
pinnedReasoningEffortConfigError,
|
|
8
8
|
modelDisplayNamesConfigError,
|
|
9
|
+
autoReviewOverrideConfigError,
|
|
9
10
|
autoReviewModelOverridesConfigError,
|
|
10
11
|
autoReviewModelTargetConfigError,
|
|
11
12
|
normalizeNonBlankStringArray,
|
|
@@ -28,13 +29,14 @@ import {
|
|
|
28
29
|
PROVIDER_WEB_SEARCH_BRIDGE_BACKENDS,
|
|
29
30
|
UPSTREAM_HTTP_VERSION_VALUES,
|
|
30
31
|
type OcxProviderConfig,
|
|
32
|
+
type OcxConfig,
|
|
31
33
|
type FastWire,
|
|
32
34
|
type ProviderCostOverlay,
|
|
33
35
|
} from "../../types";
|
|
34
36
|
import { fastWireDeclarationError } from "../../providers/fastwire";
|
|
35
37
|
import { getProviderRegistryEntry, providerMatchesRegistryTransport, providerModelWireDefault } from "../../providers/registry";
|
|
36
38
|
import { resolveOpenAiVirtualModel } from "../../providers/openai-virtual-models";
|
|
37
|
-
import { COST4_RATE_KEYS, isValidCost4Rate } from "../../usage/user-cost-overlays";
|
|
39
|
+
import { COST4_RATE_KEYS, isValidCost4Rate, isValidPromptLengthPricing, copyPromptLengthPricing } from "../../usage/user-cost-overlays";
|
|
38
40
|
import { MAX_COST4_RATE } from "../../usage/expected-prices";
|
|
39
41
|
import {
|
|
40
42
|
DECLARABLE_HOSTED_TOOL_TYPES,
|
|
@@ -63,6 +65,12 @@ export const compactionRoutingSchema = z.object({
|
|
|
63
65
|
.optional(),
|
|
64
66
|
}).strict();
|
|
65
67
|
|
|
68
|
+
export const reasoningRetentionSchema = z.object({
|
|
69
|
+
enabled: z.boolean().optional(),
|
|
70
|
+
maxContextPercent: z.number().gt(0).max(100).optional(),
|
|
71
|
+
maxTokens: z.number().int().positive().optional(),
|
|
72
|
+
}).strict();
|
|
73
|
+
|
|
66
74
|
/**
|
|
67
75
|
* One phase of Codex's memory pipeline. A present phase must name a model: the GUI's "Off"
|
|
68
76
|
* removes the phase instead of blanking it, so an empty entry would only ever come from a
|
|
@@ -133,12 +141,15 @@ const requestPacingSchema = z.object({
|
|
|
133
141
|
minIntervalMs: z.number().int().min(1).max(3_600_000).optional(),
|
|
134
142
|
maxConcurrentRequests: z.number().int().min(1).optional(),
|
|
135
143
|
models: z.record(z.string().trim().min(1), requestPacingRuleSchema).optional(),
|
|
144
|
+
keySelection: z.enum(["active", "round-robin"]).optional(),
|
|
145
|
+
keys: requestPacingRuleSchema.optional(),
|
|
136
146
|
}).strict().refine(value => value.enabled === false
|
|
137
147
|
|| value.requestsPerMinute !== undefined
|
|
138
148
|
|| value.minIntervalMs !== undefined
|
|
139
149
|
|| value.maxConcurrentRequests !== undefined
|
|
150
|
+
|| value.keys !== undefined || value.keySelection === "round-robin"
|
|
140
151
|
|| (value.models !== undefined && Object.keys(value.models).length > 0), {
|
|
141
|
-
message: "enabled request pacing needs a provider rule or
|
|
152
|
+
message: "enabled request pacing needs a provider, model, or key rule, or round-robin selection",
|
|
142
153
|
});
|
|
143
154
|
|
|
144
155
|
export function requestPacingConfigError(value: unknown): string | null {
|
|
@@ -257,6 +268,12 @@ const autoReviewModelOverridesSchema = z.unknown().superRefine((value, ctx) => {
|
|
|
257
268
|
if (error) ctx.addIssue({ code: "custom", message: error });
|
|
258
269
|
}).transform(value => normalizeAutoReviewModelOverrides(value));
|
|
259
270
|
|
|
271
|
+
/** Global auto-review override block; a malformed hand edit is stripped by load degradation. */
|
|
272
|
+
export const autoReviewOverrideSchema = z.unknown().superRefine((value, ctx) => {
|
|
273
|
+
const error = autoReviewOverrideConfigError(value, "autoReviewOverride");
|
|
274
|
+
if (error) ctx.addIssue({ code: "custom", message: error });
|
|
275
|
+
}).transform(value => value as NonNullable<OcxConfig["autoReviewOverride"]>);
|
|
276
|
+
|
|
260
277
|
const modelCapabilitiesSchema = z.unknown().superRefine((value, ctx) => {
|
|
261
278
|
const error = modelCapabilitiesConfigError(value);
|
|
262
279
|
if (error) ctx.addIssue({ code: "custom", message: error });
|
|
@@ -337,9 +354,12 @@ export const providerConfigSchema = z.object({
|
|
|
337
354
|
preserveResponsesReasoningContent: z.boolean().optional(),
|
|
338
355
|
preserveResponsesInputItemIds: z.boolean().optional(),
|
|
339
356
|
preserveResponsesMessageMetadata: z.boolean().optional(),
|
|
357
|
+
projectResponsesLiteHostedSearchHistory: z.boolean().optional(),
|
|
340
358
|
dropResponsesReasoningItems: z.boolean().optional(),
|
|
341
359
|
modelReasoningEffortsAuthoritative: z.boolean().optional(),
|
|
342
360
|
decodesNativeCompactionBlobs: z.boolean().optional(),
|
|
361
|
+
supportsNativeCompactionTrigger: z.boolean().optional(),
|
|
362
|
+
supportsNativeCompactEndpoint: z.boolean().optional(),
|
|
343
363
|
allowEncryptedV2AgentTasks: z.boolean().optional(),
|
|
344
364
|
allowPrivateNetwork: z.boolean().optional(),
|
|
345
365
|
// Per-provider egress (#2894): absent inherits the global proxy decision, "direct"/null
|
|
@@ -423,7 +443,7 @@ export { providerRelativeSendPathConfigError } from "../provider-relative-send-p
|
|
|
423
443
|
|
|
424
444
|
/**
|
|
425
445
|
* Validate `providers.<name>.modelCosts`: a plain object keyed by exact model
|
|
426
|
-
* id, each value
|
|
446
|
+
* id, each value four USD-per-1M-token rates and an optional prompt-length policy.
|
|
427
447
|
* Returns null when valid/absent, else a human-readable error.
|
|
428
448
|
*/
|
|
429
449
|
export function providerModelCostsConfigError(value: unknown, field = "modelCosts"): string | null {
|
|
@@ -443,25 +463,28 @@ export function providerModelCostsConfigError(value: unknown, field = "modelCost
|
|
|
443
463
|
const rates = entry as Record<string, unknown>;
|
|
444
464
|
for (const key of COST4_RATE_KEYS) {
|
|
445
465
|
const rate = rates[key];
|
|
446
|
-
if (!isValidCost4Rate(rate)) {
|
|
466
|
+
if (!Object.hasOwn(rates, key) || !isValidCost4Rate(rate)) {
|
|
447
467
|
return `${field}.${safeModelId}.${key} must be a non-negative finite number at most ${MAX_COST4_RATE} (USD per 1M tokens)`;
|
|
448
468
|
}
|
|
449
469
|
}
|
|
470
|
+
if (Object.hasOwn(rates, "promptLengthPricing") && !isValidPromptLengthPricing(rates.promptLengthPricing)) {
|
|
471
|
+
return `${field}.${safeModelId}.promptLengthPricing must be automatic, flat, or a complete custom policy with a positive safe-integer thresholdTokens, gt/gte comparison, and four valid rates`;
|
|
472
|
+
}
|
|
450
473
|
// Reject unknown fields: a misplaced apiKey/apiKeyPool under a cost row
|
|
451
474
|
// would otherwise be persisted and echoed verbatim by display paths that
|
|
452
475
|
// mask only top-level provider secrets.
|
|
453
476
|
const extraKeys = Object.keys(rates)
|
|
454
|
-
.filter((key) => !(COST4_RATE_KEYS as readonly string[]).includes(key));
|
|
477
|
+
.filter((key) => key !== "promptLengthPricing" && !(COST4_RATE_KEYS as readonly string[]).includes(key));
|
|
455
478
|
if (extraKeys.length > 0) {
|
|
456
|
-
return `${field}.${safeModelId} has unexpected fields ${JSON.stringify(extraKeys.map(redactSecretString).join(", "))} — only input, output, cacheRead, and
|
|
479
|
+
return `${field}.${safeModelId} has unexpected fields ${JSON.stringify(extraKeys.map(redactSecretString).join(", "))} — only input, output, cacheRead, cacheWrite, and promptLengthPricing are allowed (USD per 1M tokens)`;
|
|
457
480
|
}
|
|
458
481
|
}
|
|
459
482
|
return null;
|
|
460
483
|
}
|
|
461
484
|
|
|
462
485
|
/**
|
|
463
|
-
* Serialize `providers.<name>.modelCosts` for display: copy
|
|
464
|
-
*
|
|
486
|
+
* Serialize `providers.<name>.modelCosts` for display: copy the four validated
|
|
487
|
+
* rates and complete validated policy and DROP secret-shaped model ids, so a pasted
|
|
465
488
|
* API key in a key position cannot be echoed back by CLI/DTO display paths.
|
|
466
489
|
* The result uses a null prototype so "__proto__" remains an own row.
|
|
467
490
|
*/
|
|
@@ -471,6 +494,7 @@ export function sanitizeModelCostsForDisplay(costs: unknown): Record<string, Pro
|
|
|
471
494
|
for (const [modelId, entry] of Object.entries(costs)) {
|
|
472
495
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
473
496
|
const rates = entry as Record<string, unknown>;
|
|
497
|
+
if (Object.hasOwn(rates, "promptLengthPricing") && !isValidPromptLengthPricing(rates.promptLengthPricing)) continue;
|
|
474
498
|
const input = rates.input;
|
|
475
499
|
const output = rates.output;
|
|
476
500
|
const cacheRead = rates.cacheRead;
|
|
@@ -484,7 +508,10 @@ export function sanitizeModelCostsForDisplay(costs: unknown): Record<string, Pro
|
|
|
484
508
|
// Secret-shaped ids are DROPPED rather than mapped to "[REDACTED]" so
|
|
485
509
|
// distinct rows cannot collapse into one placeholder key.
|
|
486
510
|
if (redactSecretString(modelId) !== modelId) continue;
|
|
487
|
-
|
|
511
|
+
const policy = isValidPromptLengthPricing(rates.promptLengthPricing)
|
|
512
|
+
? copyPromptLengthPricing(rates.promptLengthPricing) : undefined;
|
|
513
|
+
out[modelId] = { input, output, cacheRead, cacheWrite,
|
|
514
|
+
...(policy ? { promptLengthPricing: policy } : {}) };
|
|
488
515
|
}
|
|
489
516
|
}
|
|
490
517
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
@@ -794,6 +821,8 @@ export const apiKeyEntrySchema = z.object({
|
|
|
794
821
|
// rather than promoting it.
|
|
795
822
|
allowedProviders: z.array(z.string().trim().min(1).max(256)).optional(),
|
|
796
823
|
allowedModels: z.array(z.string().trim().min(1).max(256)).optional(),
|
|
824
|
+
// Not `.catch`ed for the same reason: a damaged value must not grant metered spend.
|
|
825
|
+
allowMeteredComboTargets: z.boolean().optional(),
|
|
797
826
|
}).passthrough();
|
|
798
827
|
|
|
799
828
|
/**
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { pinnedReasoningEffortConfigError } from "../provider-validation";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extended settings of the main-account hard lock (`codexMainAccountHardLockFallback`):
|
|
6
|
+
* while the lock blocks, a qualifying Codex-native request reroutes to `model` instead of
|
|
7
|
+
* failing. The dashboard row keeps the configured model when the switch is off, so
|
|
8
|
+
* `enabled: false` is a remembered pause, not a cleared setting; an absent section, an
|
|
9
|
+
* absent model, and a malformed section all mean the same thing — the lock refuses,
|
|
10
|
+
* exactly as it does today.
|
|
11
|
+
*/
|
|
12
|
+
export const mainAccountHardLockFallbackSchema = z.object({
|
|
13
|
+
enabled: z.boolean().optional(),
|
|
14
|
+
model: z.string().refine(model => model.length > 0 && model === model.trim() && !/\s/.test(model)),
|
|
15
|
+
reasoningEffort: z.string().refine(value => pinnedReasoningEffortConfigError(value) === null).optional(),
|
|
16
|
+
}).strict();
|
|
17
|
+
|
|
18
|
+
/** Candidate writes must reject sections the read schema would silently discard. */
|
|
19
|
+
export function mainAccountHardLockFallbackError(value: unknown): string | undefined {
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
21
|
+
const raw = (value as Record<string, unknown>).codexMainAccountHardLockFallback;
|
|
22
|
+
if (raw === undefined) return undefined;
|
|
23
|
+
return mainAccountHardLockFallbackSchema.safeParse(raw).success
|
|
24
|
+
? undefined
|
|
25
|
+
: "schema_invalid: codexMainAccountHardLockFallback requires a nonblank model selector and an optional declared reasoningEffort";
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
|
|
4
|
+
/** Opt-in allowances for canonical ChatGPT reasoning replay; absence keeps existing cleanup. */
|
|
5
|
+
export const nativeReasoningRetentionSchema = z.object({
|
|
6
|
+
modelSwitch: z.boolean().optional(),
|
|
7
|
+
accountSwitch: z.boolean().optional(),
|
|
8
|
+
}).strict();
|
|
9
|
+
|
|
10
|
+
export function nativeReasoningRetentionConfigError(value: unknown): string | null {
|
|
11
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
12
|
+
const retention = (value as Record<string, unknown>).nativeReasoningRetention;
|
|
13
|
+
return retention === undefined || nativeReasoningRetentionSchema.safeParse(retention).success
|
|
14
|
+
? null : "schema_invalid: nativeReasoningRetention: accepts only optional boolean modelSwitch and accountSwitch fields";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Resolve malformed synthetic inputs the same way as degraded disk config: both off. */
|
|
18
|
+
export function resolveNativeReasoningRetention(
|
|
19
|
+
config: Pick<OcxConfig, "nativeReasoningRetention">,
|
|
20
|
+
): { modelSwitch: boolean; accountSwitch: boolean } {
|
|
21
|
+
const parsed = nativeReasoningRetentionSchema.safeParse(config.nativeReasoningRetention);
|
|
22
|
+
return {
|
|
23
|
+
modelSwitch: parsed.success && parsed.data.modelSwitch === true,
|
|
24
|
+
accountSwitch: parsed.success && parsed.data.accountSwitch === true,
|
|
25
|
+
};
|
|
26
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -162,6 +162,7 @@ import { persistConfigUnlocked, readRawConfigJson } from "./config/persist-unloc
|
|
|
162
162
|
import { withConfigMutationLockSync, bumpGenerationForCooperatingConfigWrite } from "./config/mutation-lock";
|
|
163
163
|
import { getDefaultConfig } from "./config/proxy-env";
|
|
164
164
|
import { configSchema } from "./config/schema/config-schema";
|
|
165
|
+
import { sanitizeOpenCodeOverridesForLoad } from "./config/opencode-wire-upgrade";
|
|
165
166
|
import {
|
|
166
167
|
hardenExistingSecret,
|
|
167
168
|
normalizeApiKeyIds,
|
|
@@ -222,6 +223,7 @@ export function loadConfig(): OcxConfig {
|
|
|
222
223
|
try {
|
|
223
224
|
const raw = readFileSync(configPath, "utf-8").replace(/^\uFEFF/, "");
|
|
224
225
|
const parsed = JSON.parse(raw);
|
|
226
|
+
sanitizeOpenCodeOverridesForLoad(parsed);
|
|
225
227
|
sanitizeAliasesForLoad(parsed);
|
|
226
228
|
sanitizeReasoningPinsForLoad(parsed);
|
|
227
229
|
sanitizeModelDisplayNamesForLoad(parsed);
|