@bitkyc08/opencodex 2.84.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.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 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- 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/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- 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-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- 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 +44 -3
- 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 +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -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/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- 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/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- 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 +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- 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 +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -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 +41 -9
- package/src/cli/restart-scope.ts +3 -1
- 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 +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- 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/pool-mode-gate.ts +5 -2
- 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 +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -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 +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- 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 +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- 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/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- 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 +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- 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 +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- 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/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -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 +962 -350
- package/src/generated/model-metadata.ts +1 -1
- 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/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -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/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- 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 +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- 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/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- 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 +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- 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 +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- 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 +78 -22
- 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/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- 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 +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- 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 +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -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 +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- 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/protocol-settings-patch.ts +1 -0
- 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 +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- 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 +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- 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 +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- 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 +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- 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/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- 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/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -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/gemini-executor.ts +2 -2
- 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/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET / PUT /api/advisor/settings — the advisor sidecar's management surface.
|
|
3
|
+
*
|
|
4
|
+
* GET answers the RESOLVED settings (defaults applied, sources marked) plus availability, so the
|
|
5
|
+
* GUI/CLI show real runtime state rather than stored fields. PUT is a strict partial patch:
|
|
6
|
+
* unknown keys and wrong types are refused, values are validated against the same resolver the
|
|
7
|
+
* runtime reads, and the patch persists through the locked config writer with the snapshot-
|
|
8
|
+
* restore discipline used by PATCH /api/protocols/settings — a refused or failed write never
|
|
9
|
+
* leaves the live config serving a state the file does not hold.
|
|
10
|
+
*/
|
|
11
|
+
import { activateAdvisor } from "../../lib/advisor-activation";
|
|
12
|
+
import { jsonResponse } from "../auth-cors";
|
|
13
|
+
import { readManagementJsonBodyOr } from "./body";
|
|
14
|
+
import type { ManagementContext } from "./context";
|
|
15
|
+
import {
|
|
16
|
+
ADVISOR_CONTEXT_SHARING_CONSENT_VERSION,
|
|
17
|
+
ADVISOR_EFFORTS,
|
|
18
|
+
advisorContextSharingBlocked,
|
|
19
|
+
advisorRunnable,
|
|
20
|
+
isCurrentAdvisorContextSharingConsent,
|
|
21
|
+
isValidAdvisorEffort,
|
|
22
|
+
isValidAdvisorPolicy,
|
|
23
|
+
isValidAdvisorTaskLimit,
|
|
24
|
+
resolveAdvisorSettings,
|
|
25
|
+
type AdvisorEffort,
|
|
26
|
+
type AdvisorPolicy,
|
|
27
|
+
} from "../../advisor/settings";
|
|
28
|
+
import { advisorActivityStore } from "../../advisor/activity";
|
|
29
|
+
|
|
30
|
+
const INVALID_BODY = Symbol("invalid-body");
|
|
31
|
+
|
|
32
|
+
interface AdvisorPatch {
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
model?: string;
|
|
35
|
+
effort?: AdvisorEffort;
|
|
36
|
+
policy?: AdvisorPolicy;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
maxConsultationsPerTask?: number;
|
|
39
|
+
/** "v1" records current consent. null removes it. Enabling does not imply this field. */
|
|
40
|
+
contextSharingConsent?: typeof ADVISOR_CONTEXT_SHARING_CONSENT_VERSION | null;
|
|
41
|
+
reset?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ParsedPatch = { ok: true; patch: AdvisorPatch } | { ok: false; code: string; message: string };
|
|
45
|
+
|
|
46
|
+
const PATCH_KEYS = new Set(["enabled", "model", "effort", "policy", "timeoutMs", "maxConsultationsPerTask", "contextSharingConsent", "reset"]);
|
|
47
|
+
|
|
48
|
+
type Rec = Record<string, unknown>;
|
|
49
|
+
function isRec(value: unknown): value is Rec {
|
|
50
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Strict: unknown keys and wrong types are refused; messages name the field, never the value. */
|
|
54
|
+
export function parseAdvisorSettingsPatch(body: unknown): ParsedPatch {
|
|
55
|
+
if (!isRec(body)) return { ok: false, code: "invalid_body", message: "body must be a JSON object" };
|
|
56
|
+
if (Object.keys(body).length === 0) return { ok: false, code: "empty_body", message: "body must set at least one supported advisor setting or reset" };
|
|
57
|
+
for (const key of Object.keys(body)) {
|
|
58
|
+
if (!PATCH_KEYS.has(key)) return { ok: false, code: "unknown_field", message: "body accepts only enabled, model, effort, policy, timeoutMs, maxConsultationsPerTask, contextSharingConsent and reset" };
|
|
59
|
+
}
|
|
60
|
+
const patch: AdvisorPatch = {};
|
|
61
|
+
if (body.reset !== undefined) {
|
|
62
|
+
if (body.reset !== true) return { ok: false, code: "invalid_reset", message: "reset must be true when present" };
|
|
63
|
+
if (Object.keys(body).length > 1) return { ok: false, code: "reset_with_fields", message: "reset must be the only field when present" };
|
|
64
|
+
patch.reset = true;
|
|
65
|
+
return { ok: true, patch };
|
|
66
|
+
}
|
|
67
|
+
if (body.enabled !== undefined) {
|
|
68
|
+
if (typeof body.enabled !== "boolean") return { ok: false, code: "invalid_enabled", message: "enabled must be a boolean" };
|
|
69
|
+
patch.enabled = body.enabled;
|
|
70
|
+
}
|
|
71
|
+
if (body.model !== undefined) {
|
|
72
|
+
// An empty or whitespace-only value is the supported "clear the model" state, not an error:
|
|
73
|
+
// the resolver defaults to an empty model and `advisorRunnable` requires a non-blank one. The
|
|
74
|
+
// only structural rules are the string type and the trimmed length bound.
|
|
75
|
+
if (typeof body.model !== "string" || body.model.trim().length > 200) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
code: "invalid_model",
|
|
79
|
+
message: "model must be a string whose trimmed value is at most 200 characters; an empty value clears the advisor model",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
patch.model = body.model.trim();
|
|
83
|
+
}
|
|
84
|
+
if (body.effort !== undefined) {
|
|
85
|
+
if (!isValidAdvisorEffort(body.effort)) {
|
|
86
|
+
return { ok: false, code: "invalid_effort", message: `effort must be one of ${ADVISOR_EFFORTS.join(", ")}` };
|
|
87
|
+
}
|
|
88
|
+
patch.effort = body.effort;
|
|
89
|
+
}
|
|
90
|
+
if (body.policy !== undefined) {
|
|
91
|
+
if (!isValidAdvisorPolicy(body.policy)) {
|
|
92
|
+
return { ok: false, code: "invalid_policy", message: 'policy must be "manual", "preflight" or "adaptive"' };
|
|
93
|
+
}
|
|
94
|
+
patch.policy = body.policy;
|
|
95
|
+
}
|
|
96
|
+
if (body.timeoutMs !== undefined) {
|
|
97
|
+
if (typeof body.timeoutMs !== "number" || !Number.isFinite(body.timeoutMs) || body.timeoutMs < 1_000 || body.timeoutMs > 600_000) {
|
|
98
|
+
return { ok: false, code: "invalid_timeout", message: "timeoutMs must be a number between 1000 and 600000" };
|
|
99
|
+
}
|
|
100
|
+
patch.timeoutMs = Math.floor(body.timeoutMs);
|
|
101
|
+
}
|
|
102
|
+
if (body.maxConsultationsPerTask !== undefined) {
|
|
103
|
+
if (!isValidAdvisorTaskLimit(body.maxConsultationsPerTask)) return { ok: false, code: "invalid_task_limit", message: "maxConsultationsPerTask must be an integer between 1 and 3" };
|
|
104
|
+
patch.maxConsultationsPerTask = body.maxConsultationsPerTask;
|
|
105
|
+
}
|
|
106
|
+
if (body.contextSharingConsent !== undefined) {
|
|
107
|
+
if (body.contextSharingConsent === null) {
|
|
108
|
+
patch.contextSharingConsent = null;
|
|
109
|
+
} else if (isCurrentAdvisorContextSharingConsent(body.contextSharingConsent)) {
|
|
110
|
+
patch.contextSharingConsent = body.contextSharingConsent;
|
|
111
|
+
} else {
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
code: "invalid_context_sharing_consent",
|
|
115
|
+
message: `contextSharingConsent must be "${ADVISOR_CONTEXT_SHARING_CONSENT_VERSION}" or null`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { ok: true, patch };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function advisorInfo(config: ManagementContext["config"]): Record<string, unknown> {
|
|
123
|
+
const settings = resolveAdvisorSettings(config);
|
|
124
|
+
const warning = settings.enabled && settings.model.trim() === ""
|
|
125
|
+
? "advisor_enabled_without_model"
|
|
126
|
+
: advisorContextSharingBlocked(settings)
|
|
127
|
+
? "advisor_context_sharing_consent_required"
|
|
128
|
+
: undefined;
|
|
129
|
+
return {
|
|
130
|
+
settings,
|
|
131
|
+
runnable: advisorRunnable(settings),
|
|
132
|
+
// Enabled without a model cannot consult. Enabled with a model but without current
|
|
133
|
+
// consent must not send task context; the warning names that block.
|
|
134
|
+
...(warning ? { warning } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function applyPatchInMemory(config: ManagementContext["config"], patch: AdvisorPatch): void {
|
|
139
|
+
if (patch.reset) {
|
|
140
|
+
delete config.advisor;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const current: Rec = isRec(config.advisor) ? { ...config.advisor } : {};
|
|
144
|
+
if (patch.enabled !== undefined) current.enabled = patch.enabled;
|
|
145
|
+
if (patch.model !== undefined) current.model = patch.model;
|
|
146
|
+
if (patch.effort !== undefined) current.effort = patch.effort;
|
|
147
|
+
if (patch.policy !== undefined) current.policy = patch.policy;
|
|
148
|
+
if (patch.timeoutMs !== undefined) current.timeoutMs = patch.timeoutMs;
|
|
149
|
+
if (patch.maxConsultationsPerTask !== undefined) current.maxConsultationsPerTask = patch.maxConsultationsPerTask;
|
|
150
|
+
if (patch.contextSharingConsent === null) delete current.contextSharingConsent;
|
|
151
|
+
else if (patch.contextSharingConsent !== undefined) current.contextSharingConsent = patch.contextSharingConsent;
|
|
152
|
+
config.advisor = current as ManagementContext["config"]["advisor"];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isConfigLockContention(error: unknown): boolean {
|
|
156
|
+
if (!error || typeof error !== "object") return false;
|
|
157
|
+
if ((error as { code?: unknown }).code !== "CONFIG_MUTATION_LOCK_UNAVAILABLE") return false;
|
|
158
|
+
return (error as { cause?: { code?: unknown } }).cause?.code === "SQLITE_BUSY";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function handleAdvisorRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
162
|
+
const { url, req, config } = ctx;
|
|
163
|
+
if (url.pathname === "/api/advisor/activity" && req.method === "GET") {
|
|
164
|
+
return jsonResponse({ entries: advisorActivityStore(config).snapshot(), retention: "process", limit: 100 }, 200, req, config);
|
|
165
|
+
}
|
|
166
|
+
if (url.pathname !== "/api/advisor/settings") return null;
|
|
167
|
+
|
|
168
|
+
if (req.method === "GET") return jsonResponse(advisorInfo(config), 200, req, config);
|
|
169
|
+
if (req.method === "PUT") return putAdvisorSettings(ctx);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function putAdvisorSettings(ctx: ManagementContext): Promise<Response> {
|
|
174
|
+
const { req, config } = ctx;
|
|
175
|
+
|
|
176
|
+
const body = await readManagementJsonBodyOr(req, INVALID_BODY);
|
|
177
|
+
const parsed = body === INVALID_BODY
|
|
178
|
+
? { ok: false as const, code: "invalid_json", message: "body must be valid JSON" }
|
|
179
|
+
: parseAdvisorSettingsPatch(body);
|
|
180
|
+
if (!parsed.ok) return jsonResponse({ error: { code: parsed.code, message: parsed.message } }, 400, req, config);
|
|
181
|
+
|
|
182
|
+
// Resolve the writer BEFORE touching the live config: a resolution failure must leave the
|
|
183
|
+
// in-memory config exactly as it was, not rely on the snapshot restore below.
|
|
184
|
+
// `deps.` first: route tests with an in-memory fixture must never write the real config.
|
|
185
|
+
const persist = ctx.deps.saveConfigPreservingClaudeCode
|
|
186
|
+
?? (await import("../../config")).saveConfigPreservingClaudeCode;
|
|
187
|
+
const snapshot = isRec(config.advisor) ? { ...config.advisor } : undefined;
|
|
188
|
+
applyPatchInMemory(config, parsed.patch);
|
|
189
|
+
try {
|
|
190
|
+
persist(config);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Undo in memory too: a live config that serves a state the file does not hold would
|
|
193
|
+
// mislead every GET until the next restart.
|
|
194
|
+
if (snapshot === undefined) delete config.advisor;
|
|
195
|
+
else config.advisor = snapshot as ManagementContext["config"]["advisor"];
|
|
196
|
+
return isConfigLockContention(error)
|
|
197
|
+
? jsonResponse({ error: { code: "config_busy", message: "Another process is saving the configuration. Try again in a moment." } }, 409, req, config)
|
|
198
|
+
: jsonResponse({ error: { code: "write_failed", message: "The configuration could not be saved." } }, 500, req, config);
|
|
199
|
+
}
|
|
200
|
+
activateAdvisor(config);
|
|
201
|
+
return jsonResponse(advisorInfo(config), 200, req, config);
|
|
202
|
+
}
|
|
@@ -1397,6 +1397,24 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
1399
|
// Claude Code inbound settings (GUI "Claude ON" toggle + Claude page).
|
|
1400
|
+
if (url.pathname === "/api/claude-code/model-defaults" && (req.method === "GET" || req.method === "POST")) {
|
|
1401
|
+
const { previewClaudeModelDefaults, applyClaudeModelDefaults, ModelDefaultsRecoveryError } = await import("../../claude/model-default-settings");
|
|
1402
|
+
try {
|
|
1403
|
+
if (req.method === "GET") return jsonResponse(previewClaudeModelDefaults(config.claudeCode?.tierModels));
|
|
1404
|
+
let body: unknown;
|
|
1405
|
+
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
1406
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).length !== 1 ||
|
|
1407
|
+
!("fingerprint" in body) || typeof body.fingerprint !== "string" || !/^[a-f0-9]{64}$/.test(body.fingerprint)) {
|
|
1408
|
+
return jsonResponse({ error: "fingerprint is required" }, 400);
|
|
1409
|
+
}
|
|
1410
|
+
return jsonResponse(applyClaudeModelDefaults(config.claudeCode?.tierModels, body.fingerprint));
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
rethrowManagementBodyTooLarge(error);
|
|
1413
|
+
if (error instanceof ModelDefaultsRecoveryError) return jsonResponse({ error: error.message, code: error.message, backupPath: error.backupPath }, 500);
|
|
1414
|
+
const code = error instanceof Error && error.message === "settings_changed" ? "settings_changed" : "settings_unavailable";
|
|
1415
|
+
return jsonResponse({ error: code, code }, code === "settings_changed" ? 409 : 500);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1400
1418
|
if (url.pathname === "/api/claude-code" && req.method === "GET") {
|
|
1401
1419
|
const models = await fetchAllModels(config);
|
|
1402
1420
|
const { listCatalogNativeSlugs } = await import("../../codex/catalog");
|
|
@@ -1513,10 +1531,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1513
1531
|
});
|
|
1514
1532
|
}
|
|
1515
1533
|
if (url.pathname === "/api/claude-code" && req.method === "PUT") {
|
|
1516
|
-
// NOTE: model /
|
|
1534
|
+
// NOTE: model / maxContextTokens / alwaysEnableEffort are
|
|
1517
1535
|
// CONFIG-ONLY back-compat fields — the GUI no longer offers controls for them
|
|
1518
|
-
// (default model is owned by Claude Code's /model picker;
|
|
1519
|
-
//
|
|
1536
|
+
// (default model is owned by Claude Code's /model picker;
|
|
1537
|
+
// auto-context supersedes the max-context pair; effort rides
|
|
1520
1538
|
// regardless on 2.1.207). PUT keeps validating them so hand-written configs
|
|
1521
1539
|
// and older GUIs stay safe; GUI saves omit them and the spread preserves them.
|
|
1522
1540
|
let parsedBody: unknown;
|
|
@@ -1835,7 +1853,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1835
1853
|
}
|
|
1836
1854
|
}
|
|
1837
1855
|
if (body.tierModels !== undefined) {
|
|
1838
|
-
//
|
|
1856
|
+
// Family defaults also serve third-party callers that request native tier aliases.
|
|
1839
1857
|
if (body.tierModels === null) {
|
|
1840
1858
|
delete next.tierModels;
|
|
1841
1859
|
} else if (!isPlainObject(body.tierModels)) {
|
|
@@ -44,7 +44,7 @@ export function pickerPreferenceOn(config: Pick<OcxConfig, "claudeCode">): boole
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const PICKER_BODY_KEYS = new Set(["enabled", "persist", "trustedLocally", "callerAddedTrust"]);
|
|
47
|
-
/** Enable outcomes that are progress, not refusal: on, waiting for a Desktop restart, or for
|
|
47
|
+
/** Enable outcomes that are progress, not refusal: on, waiting for a Desktop restart, or for certificate trust. */
|
|
48
48
|
const ENABLE_ACCEPTED = new Set(["active", "restart_required", "trust_pending"]);
|
|
49
49
|
|
|
50
50
|
interface PickerRequestBody { enabled: boolean; persist: boolean; trustedLocally?: boolean; callerAddedTrust?: boolean }
|
|
@@ -52,6 +52,15 @@ import {
|
|
|
52
52
|
setDebugSettings,
|
|
53
53
|
type DebugFlag,
|
|
54
54
|
} from "../../lib/debug-settings";
|
|
55
|
+
import { mergeModelCapabilities, modelCapabilitiesConfigError } from "../../config/provider-validation";
|
|
56
|
+
import { modelRecordValue } from "../../reasoning-effort";
|
|
57
|
+
import { commitProviderPatch } from "./provider-patch-transaction";
|
|
58
|
+
import { ConfigWritePublishedError } from "../../config/persist-unlocked";
|
|
59
|
+
import {
|
|
60
|
+
customRowInputModalities,
|
|
61
|
+
isVisionSidecarConsumer,
|
|
62
|
+
modelAcceptsImageInput,
|
|
63
|
+
} from "../../vision/eligibility";
|
|
55
64
|
import type { OcxClaudeCodeConfig, OcxComboConfig, OcxConfig, OcxCustomModel, OcxProviderConfig, OcxComboCooldownWaitPolicy } from "../../types";
|
|
56
65
|
import { drainAndShutdown } from "../lifecycle";
|
|
57
66
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
@@ -116,6 +125,43 @@ function sparseComboConfig<T extends {
|
|
|
116
125
|
};
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
interface VisionSidecarTarget {
|
|
129
|
+
provider: string;
|
|
130
|
+
model: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Request-only PUT field: combo members to declare text-only so the Vision Sidecar
|
|
135
|
+
* covers them when the combo accepts images. Every entry must be an exact target of
|
|
136
|
+
* the submitted combo, and the whole field is validated before any config mutation.
|
|
137
|
+
* Nothing from here is persisted under config.combos.
|
|
138
|
+
*/
|
|
139
|
+
function parseVisionSidecarTargets(
|
|
140
|
+
raw: unknown,
|
|
141
|
+
comboTargetKeys: ReadonlySet<string>,
|
|
142
|
+
): { targets?: VisionSidecarTarget[]; error?: string } {
|
|
143
|
+
if (raw === undefined) return {};
|
|
144
|
+
if (!Array.isArray(raw)) return { error: "visionSidecarTargets must be an array" };
|
|
145
|
+
const seen = new Set<string>();
|
|
146
|
+
const targets: VisionSidecarTarget[] = [];
|
|
147
|
+
for (const entry of raw) {
|
|
148
|
+
if (!isPlainRecord(entry)) return { error: "visionSidecarTargets entries must be objects" };
|
|
149
|
+
const provider = typeof entry.provider === "string" ? entry.provider.trim() : "";
|
|
150
|
+
const model = typeof entry.model === "string" ? entry.model.trim() : "";
|
|
151
|
+
if (!provider || !model) {
|
|
152
|
+
return { error: "visionSidecarTargets entries must have nonblank provider and model" };
|
|
153
|
+
}
|
|
154
|
+
const key = `${provider}/${model}`;
|
|
155
|
+
if (!comboTargetKeys.has(key)) {
|
|
156
|
+
return { error: `visionSidecarTargets entry "${key}" is not a target of this combo` };
|
|
157
|
+
}
|
|
158
|
+
if (seen.has(key)) continue;
|
|
159
|
+
seen.add(key);
|
|
160
|
+
targets.push({ provider, model });
|
|
161
|
+
}
|
|
162
|
+
return { targets };
|
|
163
|
+
}
|
|
164
|
+
|
|
119
165
|
export async function handleComboRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
120
166
|
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
121
167
|
|
|
@@ -176,6 +222,9 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
176
222
|
const requestedCombo: Record<string, unknown> = body.combo;
|
|
177
223
|
const effectiveCombo = {
|
|
178
224
|
...requestedCombo,
|
|
225
|
+
...(!Object.hasOwn(requestedCombo, "allowMeteredTargets") && previous?.allowMeteredTargets === true
|
|
226
|
+
? { allowMeteredTargets: true }
|
|
227
|
+
: {}),
|
|
179
228
|
...(!Object.hasOwn(requestedCombo, "cooldownMs") && previous?.cooldownMs !== undefined
|
|
180
229
|
? { cooldownMs: previous.cooldownMs }
|
|
181
230
|
: {}),
|
|
@@ -203,7 +252,7 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
203
252
|
// A non-record entry stays as the client sent it so comboConfigError reports it.
|
|
204
253
|
// Reading `lastResort` off it here would throw in place of that structured 400.
|
|
205
254
|
if (!isPlainRecord(target)) return target;
|
|
206
|
-
if (Object.hasOwn(target,
|
|
255
|
+
if (["lastResort", "metered"].every(key => Object.hasOwn(target, key))) return target;
|
|
207
256
|
const rawProvider = target.provider;
|
|
208
257
|
const rawModel = target.model;
|
|
209
258
|
if (typeof rawProvider !== "string" || typeof rawModel !== "string") return target;
|
|
@@ -214,7 +263,13 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
214
263
|
const before = previous.targets.find(
|
|
215
264
|
candidate => candidate.provider === provider && candidate.model === model,
|
|
216
265
|
);
|
|
217
|
-
|
|
266
|
+
// `metered` is carried the same way: dropping it on a GUI round-trip would open a
|
|
267
|
+
// pay-per-use target to every issued key.
|
|
268
|
+
return {
|
|
269
|
+
...target,
|
|
270
|
+
...(!Object.hasOwn(target, "lastResort") && before?.lastResort ? { lastResort: true } : {}),
|
|
271
|
+
...(!Object.hasOwn(target, "metered") && before?.metered ? { metered: true } : {}),
|
|
272
|
+
};
|
|
218
273
|
}),
|
|
219
274
|
}
|
|
220
275
|
: {}),
|
|
@@ -280,6 +335,54 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
280
335
|
if (routeError) return jsonResponse({ error: issue ? `combo "${otherId}": ${routeError}` : routeError }, 400);
|
|
281
336
|
}
|
|
282
337
|
const normalized = normalizeComboConfig(effectiveCombo as unknown as OcxComboConfig);
|
|
338
|
+
const sidecarParsed = parseVisionSidecarTargets(
|
|
339
|
+
body.visionSidecarTargets,
|
|
340
|
+
new Set(normalized.targets.map((target) => `${target.provider}/${target.model}`)),
|
|
341
|
+
);
|
|
342
|
+
if (sidecarParsed.error) return jsonResponse({ error: sidecarParsed.error }, 400);
|
|
343
|
+
const sidecarPatches = new Map<string, Record<string, { inputModalities: string[] }>>();
|
|
344
|
+
if (sidecarParsed.targets?.length) {
|
|
345
|
+
if (normalized.imageInput === "disabled") {
|
|
346
|
+
return jsonResponse({ error: "visionSidecarTargets requires imageInput not disabled" }, 400);
|
|
347
|
+
}
|
|
348
|
+
for (const { provider, model } of sidecarParsed.targets) {
|
|
349
|
+
const providerRow = config.providers?.[provider];
|
|
350
|
+
if (!providerRow) {
|
|
351
|
+
return jsonResponse({ error: `visionSidecarTargets entry "${provider}/${model}" has no configured provider` }, 400);
|
|
352
|
+
}
|
|
353
|
+
// A text-only declaration would HIDE a real capability; never overwrite one.
|
|
354
|
+
if (modelAcceptsImageInput(config, { provider, id: model }) === true) {
|
|
355
|
+
return jsonResponse({ error: `visionSidecarTargets entry "${provider}/${model}" already accepts image input` }, 400);
|
|
356
|
+
}
|
|
357
|
+
// Already a consumer through any runtime source (exact declaration, custom row,
|
|
358
|
+
// noVisionModels, legacy record, registry enrichment): the catalog already
|
|
359
|
+
// advertises image for it. Writing the declaration again would be a no-op write.
|
|
360
|
+
if (isVisionSidecarConsumer(config, provider, model)) continue;
|
|
361
|
+
// Read the declaration the way the runtime does: exact capability axis, then the
|
|
362
|
+
// operator's custom row, then the provider's per-model modality hint. A row without
|
|
363
|
+
// text (audio-only) cannot be widened by the sidecar, and writing ["text"] on the
|
|
364
|
+
// capability axis would silently override that declaration.
|
|
365
|
+
const capabilityRow = Object.hasOwn(providerRow.modelCapabilities ?? {}, model)
|
|
366
|
+
? providerRow.modelCapabilities?.[model] : undefined;
|
|
367
|
+
const declared = capabilityRow?.inputModalities
|
|
368
|
+
?? customRowInputModalities(config, provider, model)
|
|
369
|
+
?? modelRecordValue(providerRow.modelInputModalities, model);
|
|
370
|
+
if (declared !== undefined && !declared.includes("text")) {
|
|
371
|
+
return jsonResponse({ error: `visionSidecarTargets entry "${provider}/${model}" is declared without text input; the Vision Sidecar cannot cover it` }, 400);
|
|
372
|
+
}
|
|
373
|
+
const patch = sidecarPatches.get(provider) ?? {};
|
|
374
|
+
patch[model] = { inputModalities: ["text"] };
|
|
375
|
+
sidecarPatches.set(provider, patch);
|
|
376
|
+
}
|
|
377
|
+
for (const [providerName, patch] of sidecarPatches) {
|
|
378
|
+
// Validate the map that will be persisted, not just the patch: a provider already at
|
|
379
|
+
// the modelCapabilities cap would otherwise save an oversized map that load-time
|
|
380
|
+
// sanitization truncates, silently dropping the new text-only declaration.
|
|
381
|
+
const merged = mergeModelCapabilities(config.providers?.[providerName]?.modelCapabilities, patch);
|
|
382
|
+
const error = modelCapabilitiesConfigError(patch) ?? (merged ? modelCapabilitiesConfigError(merged) : null);
|
|
383
|
+
if (error) return jsonResponse({ error: `visionSidecarTargets for provider "${providerName}": ${error}` }, 400);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
283
386
|
// Persist only non-default identity/capability fields so config stays sparse.
|
|
284
387
|
// Capability defaults (`imageInput`, `reasoningEffortMode`) go through the same
|
|
285
388
|
// helper the GET/PUT responses use, so the wire shape and the stored shape cannot drift.
|
|
@@ -295,9 +398,10 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
295
398
|
// would add a noise key to every target of every combo, including ones that never use
|
|
296
399
|
// the policy (#5736). Only the opt-in value is stored, matching how the combo-level
|
|
297
400
|
// policy is handled in sparseComboConfig.
|
|
298
|
-
targets: normalizedBase.targets.map(({ lastResort, modelProfile, ...target }) => ({
|
|
401
|
+
targets: normalizedBase.targets.map(({ lastResort, metered, modelProfile, ...target }) => ({
|
|
299
402
|
...target,
|
|
300
403
|
...(lastResort ? { lastResort: true } : {}),
|
|
404
|
+
...(metered ? { metered: true } : {}),
|
|
301
405
|
...(modelProfile ? { modelProfile } : {}),
|
|
302
406
|
})),
|
|
303
407
|
...(normalizedAlias ? { alias: normalizedAlias } : {}),
|
|
@@ -340,58 +444,80 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
340
444
|
const targetError = shadowCallTargetError({ ...config, combos: nextCombos }, migratedShadowTarget);
|
|
341
445
|
if (targetError) return jsonResponse({ error: targetError }, 400);
|
|
342
446
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
447
|
+
// One atomic transaction: a failed save must restore the combo, the capability
|
|
448
|
+
// declarations, and every migrated reference together, never partially.
|
|
449
|
+
try {
|
|
450
|
+
commitProviderPatch(config, () => {
|
|
451
|
+
config.combos = nextCombos;
|
|
452
|
+
if (migratedModels.size > 0) {
|
|
453
|
+
const migrateReference = (model: string): string => migratedModels.get(model) ?? model;
|
|
454
|
+
const migrateAgentReference = (model: string): string => {
|
|
455
|
+
const migrated = migrateReference(model);
|
|
456
|
+
if (migrated !== model) shouldSyncClaudeAgentDefs = true;
|
|
457
|
+
return migrated;
|
|
458
|
+
};
|
|
459
|
+
if (config.subagentModels) {
|
|
460
|
+
config.subagentModels = [...new Set(config.subagentModels.map(migrateAgentReference))];
|
|
461
|
+
}
|
|
462
|
+
if (config.injectionModel && migratedModels.has(config.injectionModel)) {
|
|
463
|
+
config.injectionModel = migrateReference(config.injectionModel);
|
|
464
|
+
}
|
|
465
|
+
if (config.shadowCallIntercept?.model && migratedModels.has(config.shadowCallIntercept.model)) {
|
|
466
|
+
config.shadowCallIntercept = {
|
|
467
|
+
...config.shadowCallIntercept,
|
|
468
|
+
model: migrateReference(config.shadowCallIntercept.model),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (config.claudeCode) {
|
|
472
|
+
const claudeCode = { ...config.claudeCode };
|
|
473
|
+
for (const field of ["model", "smallFastModel"] as const) {
|
|
474
|
+
if (claudeCode[field]) claudeCode[field] = migrateAgentReference(claudeCode[field]);
|
|
475
|
+
}
|
|
476
|
+
if (claudeCode.tierModels) {
|
|
477
|
+
claudeCode.tierModels = Object.fromEntries(
|
|
478
|
+
Object.entries(claudeCode.tierModels).map(([tier, model]) => [tier, migrateAgentReference(model)]),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
if (claudeCode.modelMap) {
|
|
482
|
+
claudeCode.modelMap = Object.fromEntries(
|
|
483
|
+
Object.entries(claudeCode.modelMap).map(([source, model]) => [source, migrateAgentReference(model)]),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (claudeCode.intercept?.modelMap) {
|
|
487
|
+
claudeCode.intercept = {
|
|
488
|
+
...claudeCode.intercept,
|
|
489
|
+
modelMap: Object.fromEntries(
|
|
490
|
+
Object.entries(claudeCode.intercept.modelMap).map(([pickerId, route]) => [pickerId, migrateAgentReference(route)]),
|
|
491
|
+
),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
config.claudeCode = claudeCode;
|
|
495
|
+
}
|
|
372
496
|
}
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
497
|
+
if (oldDisabledSelectors.size > 0 && config.disabledModels) {
|
|
498
|
+
config.disabledModels = [...new Set(config.disabledModels.map(model => (
|
|
499
|
+
oldDisabledSelectors.has(model) ? newDisabledModel : model
|
|
500
|
+
)))];
|
|
377
501
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
502
|
+
for (const [providerName, patch] of sidecarPatches) {
|
|
503
|
+
const provider = config.providers?.[providerName];
|
|
504
|
+
if (!provider) continue;
|
|
505
|
+
const capabilities = mergeModelCapabilities(provider.modelCapabilities, patch);
|
|
506
|
+
// Re-check under the config lock: a concurrent edit may have grown the map since
|
|
507
|
+
// the pre-check, and an oversized map must never be published.
|
|
508
|
+
if (capabilities && modelCapabilitiesConfigError(capabilities)) {
|
|
509
|
+
throw new Error("modelCapabilities would exceed its limit");
|
|
510
|
+
}
|
|
511
|
+
if (capabilities === undefined) delete provider.modelCapabilities;
|
|
512
|
+
else provider.modelCapabilities = capabilities;
|
|
385
513
|
}
|
|
386
|
-
|
|
514
|
+
}, deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (!(error instanceof ConfigWritePublishedError)) {
|
|
517
|
+
return jsonResponse({ error: "combo could not be saved" }, 500);
|
|
387
518
|
}
|
|
519
|
+
throw error;
|
|
388
520
|
}
|
|
389
|
-
if (oldDisabledSelectors.size > 0 && config.disabledModels) {
|
|
390
|
-
config.disabledModels = [...new Set(config.disabledModels.map(model => (
|
|
391
|
-
oldDisabledSelectors.has(model) ? newDisabledModel : model
|
|
392
|
-
)))];
|
|
393
|
-
}
|
|
394
|
-
saveConfigPreservingClaudeCode(config);
|
|
395
521
|
reconcileLiveStateStores();
|
|
396
522
|
clearComboSelectionState(id);
|
|
397
523
|
clearComboTargetCooldowns(id);
|
|
@@ -27,6 +27,10 @@ function response(): Response {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export async function handleCompanionRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
30
|
+
if (ctx.url.pathname === "/api/advisor/settings" || ctx.url.pathname === "/api/advisor/activity") {
|
|
31
|
+
const { handleAdvisorRoutes } = await import("./advisor-routes");
|
|
32
|
+
return handleAdvisorRoutes(ctx);
|
|
33
|
+
}
|
|
30
34
|
if (ctx.url.pathname === "/api/companion/open-in-browser" && ctx.req.method === "POST") {
|
|
31
35
|
let body: unknown;
|
|
32
36
|
try {
|