@iislee/opencodex 2.11.0 → 2.35.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/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process reconciliation for user cost overlays.
|
|
3
|
+
*
|
|
4
|
+
* `refreshUserCostOverlays` is process-local by design: `ocx config set`
|
|
5
|
+
* executes `saveConfig()` in the CLI process, and a direct `config.json` edit
|
|
6
|
+
* runs no in-process code at all. A running proxy therefore keeps its previous
|
|
7
|
+
* `activeUserCostOverlays()`/version until restart or another in-process
|
|
8
|
+
* refresh.
|
|
9
|
+
*
|
|
10
|
+
* This module gives the long-lived server a lightweight stat-based poller:
|
|
11
|
+
* when `config.json` changes on disk, it re-reads the persisted config, mirrors
|
|
12
|
+
* the disk `modelCosts` rows into the live provider rows (so a later in-process
|
|
13
|
+
* save cannot erase the external edit), and refreshes the overlay registry so
|
|
14
|
+
* Logs/Usage estimates follow the edit without a restart.
|
|
15
|
+
*
|
|
16
|
+
* The reconciler is owner-scoped: every `startServer` acquires its own lease
|
|
17
|
+
* (with its own live config), and the shared timer is torn down only when the
|
|
18
|
+
* LAST owner stops. This mirrors `acquireServerBackgroundLifecycle` so stopping
|
|
19
|
+
* one of several servers cannot kill reconciliation for the others.
|
|
20
|
+
*/
|
|
21
|
+
import { statSync } from "node:fs";
|
|
22
|
+
|
|
23
|
+
import { getConfigPath, readConfigDiagnostics } from "../config";
|
|
24
|
+
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
25
|
+
import {
|
|
26
|
+
refreshPreservedProviderOwner,
|
|
27
|
+
refreshUserCostOverlays,
|
|
28
|
+
registerPreservedProviderOwner,
|
|
29
|
+
setPreservedDiskOnlyProviders,
|
|
30
|
+
unregisterPreservedProviderOwner,
|
|
31
|
+
} from "./user-cost-overlays";
|
|
32
|
+
|
|
33
|
+
/** Default poll cadence for external config edits. */
|
|
34
|
+
export const USER_COST_OVERLAY_RECONCILE_INTERVAL_MS = 5_000;
|
|
35
|
+
|
|
36
|
+
let reconcileTimer: ReturnType<typeof setInterval> | null = null;
|
|
37
|
+
let reconcileTimerMs = 0;
|
|
38
|
+
const owners = new Map<symbol, OcxConfig | null>();
|
|
39
|
+
const ownerIntervals = new Map<symbol, number>();
|
|
40
|
+
let lastStamp: { mtimeMs: number; size: number; ctimeMs: number; ino: number } | null = null;
|
|
41
|
+
let invalidReconcileCount = 0;
|
|
42
|
+
|
|
43
|
+
function configStamp(): { mtimeMs: number; size: number; ctimeMs: number; ino: number } | null {
|
|
44
|
+
try {
|
|
45
|
+
const stat = statSync(getConfigPath());
|
|
46
|
+
return {
|
|
47
|
+
mtimeMs: stat.mtimeMs,
|
|
48
|
+
size: stat.size,
|
|
49
|
+
ctimeMs: stat.ctimeMs,
|
|
50
|
+
ino: Number(stat.ino),
|
|
51
|
+
};
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Registered live configs plus an optional one-shot config, deduped by object identity. */
|
|
58
|
+
function liveConfigsForPreservation(extra?: OcxConfig | null): OcxConfig[] {
|
|
59
|
+
const liveConfigs: OcxConfig[] = [];
|
|
60
|
+
const seen = new Set<OcxConfig>();
|
|
61
|
+
if (extra) {
|
|
62
|
+
seen.add(extra);
|
|
63
|
+
liveConfigs.push(extra);
|
|
64
|
+
}
|
|
65
|
+
for (const config of owners.values()) {
|
|
66
|
+
if (!config || seen.has(config)) continue;
|
|
67
|
+
seen.add(config);
|
|
68
|
+
liveConfigs.push(config);
|
|
69
|
+
}
|
|
70
|
+
return liveConfigs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Mirror disk `modelCosts` rows into provider rows the live config already
|
|
75
|
+
* knows. Providers added by the external edit are left out of the live config
|
|
76
|
+
* (they would change the routing surface); their overlays still become active
|
|
77
|
+
* because the registry below is refreshed from the disk config.
|
|
78
|
+
*/
|
|
79
|
+
function adoptDiskModelCosts(live: OcxConfig, disk: OcxConfig): void {
|
|
80
|
+
if (!live.providers || !disk.providers) return;
|
|
81
|
+
for (const [name, diskProvider] of Object.entries(disk.providers)) {
|
|
82
|
+
const liveProvider = live.providers[name];
|
|
83
|
+
if (!liveProvider) continue;
|
|
84
|
+
if (diskProvider?.modelCosts === undefined) {
|
|
85
|
+
delete liveProvider.modelCosts;
|
|
86
|
+
} else {
|
|
87
|
+
liveProvider.modelCosts = structuredClone(diskProvider.modelCosts);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Remember provider rows present on disk that at least one live routing
|
|
94
|
+
* config does NOT own. They stay out of the live configs that lack them
|
|
95
|
+
* (adding them would change the routing surface), but
|
|
96
|
+
* `persistConfigUnlocked` merges them back at serialization so ANY owner's
|
|
97
|
+
* unrelated save preserves a provider row that another active owner owns.
|
|
98
|
+
*
|
|
99
|
+
* A row is preserved when it is missing from at least one live config: the
|
|
100
|
+
* writer that lacks it would otherwise erase it. Rows every live config owns
|
|
101
|
+
* need no protection because every writer carries them from its own config.
|
|
102
|
+
*/
|
|
103
|
+
function rememberDiskOnlyProviders(liveConfigs: readonly OcxConfig[], disk: OcxConfig): void {
|
|
104
|
+
const preserved: Record<string, OcxProviderConfig> = {};
|
|
105
|
+
if (disk.providers && liveConfigs.length > 0) {
|
|
106
|
+
for (const [name, provider] of Object.entries(disk.providers)) {
|
|
107
|
+
if (!provider) continue;
|
|
108
|
+
const ownedByAll = liveConfigs.every(live => Object.hasOwn(live.providers ?? {}, name));
|
|
109
|
+
if (!ownedByAll) preserved[name] = structuredClone(provider);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
setPreservedDiskOnlyProviders(Object.keys(preserved).length > 0 ? preserved : null);
|
|
113
|
+
// Update only registered server owners. One-shot callers without a lease do
|
|
114
|
+
// not become deletion authorities merely by asking for a reconciliation.
|
|
115
|
+
for (const live of liveConfigs) refreshPreservedProviderOwner(live, disk);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Re-read the persisted config and recompute the disk-only provider
|
|
120
|
+
* preservation registry against the CURRENT owner set. Used when an owner
|
|
121
|
+
* stops but others remain: a newer owner may have owned a provider that an
|
|
122
|
+
* older owner still treats as disk-only, and the registry must reflect the
|
|
123
|
+
* remaining owners rather than the stale pre-stop snapshot.
|
|
124
|
+
*
|
|
125
|
+
* A missing or transiently invalid file leaves the registry untouched so a
|
|
126
|
+
* bad write cannot erase preservation state.
|
|
127
|
+
*/
|
|
128
|
+
function recomputePreservedDiskOnlyProviders(): void {
|
|
129
|
+
const diagnostics = readConfigDiagnostics();
|
|
130
|
+
if (diagnostics.source !== "file") return;
|
|
131
|
+
const liveConfigs = liveConfigsForPreservation();
|
|
132
|
+
if (liveConfigs.length > 0) {
|
|
133
|
+
rememberDiskOnlyProviders(liveConfigs, diagnostics.config);
|
|
134
|
+
} else {
|
|
135
|
+
// No live routing configuration remains to protect disk-only providers;
|
|
136
|
+
// clear the cache so a later save cannot resurrect externally deleted rows.
|
|
137
|
+
setPreservedDiskOnlyProviders(null);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Re-read the persisted config and make external overlay edits live.
|
|
143
|
+
*
|
|
144
|
+
* Returns `false` (and leaves the registry untouched) when the file is missing
|
|
145
|
+
* or invalid, so a transient bad write cannot wipe display-only prices.
|
|
146
|
+
*/
|
|
147
|
+
export function reconcileUserCostOverlaysFromDisk(liveConfig?: OcxConfig | null): boolean {
|
|
148
|
+
const diagnostics = readConfigDiagnostics();
|
|
149
|
+
if (diagnostics.source !== "file") return false;
|
|
150
|
+
const disk = diagnostics.config;
|
|
151
|
+
if (liveConfig) {
|
|
152
|
+
adoptDiskModelCosts(liveConfig, disk);
|
|
153
|
+
// A one-shot caller is not the whole process. Existing server owners still
|
|
154
|
+
// participate in global preservation or this call can drop protection for
|
|
155
|
+
// a provider absent from an older live projection.
|
|
156
|
+
rememberDiskOnlyProviders(liveConfigsForPreservation(liveConfig), disk);
|
|
157
|
+
} else {
|
|
158
|
+
// No live routing config was supplied: mirror the owners path so a stale
|
|
159
|
+
// preservation registry cannot resurrect externally deleted providers on
|
|
160
|
+
// the next saveConfig. Registered owners still protect disk-only rows;
|
|
161
|
+
// without any, preservation is cleared.
|
|
162
|
+
const liveConfigs = liveConfigsForPreservation();
|
|
163
|
+
if (liveConfigs.length > 0) {
|
|
164
|
+
for (const live of liveConfigs) adoptDiskModelCosts(live, disk);
|
|
165
|
+
rememberDiskOnlyProviders(liveConfigs, disk);
|
|
166
|
+
} else {
|
|
167
|
+
setPreservedDiskOnlyProviders(null);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Refresh from the DISK config: overlays for providers only added by the
|
|
171
|
+
// external edit are display-only and must still resolve for historical rows.
|
|
172
|
+
refreshUserCostOverlays(disk);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function reconcileForOwners(): void {
|
|
177
|
+
const diagnostics = readConfigDiagnostics();
|
|
178
|
+
if (diagnostics.source !== "file") {
|
|
179
|
+
if (diagnostics.source === "fallback") invalidReconcileCount += 1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const disk = diagnostics.config;
|
|
183
|
+
const liveConfigs = liveConfigsForPreservation();
|
|
184
|
+
if (liveConfigs.length > 0) {
|
|
185
|
+
for (const live of liveConfigs) adoptDiskModelCosts(live, disk);
|
|
186
|
+
rememberDiskOnlyProviders(liveConfigs, disk);
|
|
187
|
+
} else {
|
|
188
|
+
// Overlay-only refresh with no live routing configuration: do not treat
|
|
189
|
+
// every disk provider as protected, and do not let a stale preservation
|
|
190
|
+
// cache resurrect providers that were intentionally removed.
|
|
191
|
+
setPreservedDiskOnlyProviders(null);
|
|
192
|
+
}
|
|
193
|
+
refreshUserCostOverlays(disk);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Test-only observation that proves the timer actually read an invalid config fallback. */
|
|
197
|
+
export function userCostOverlayInvalidReconcileCountForTests(): number {
|
|
198
|
+
return invalidReconcileCount;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Smallest poll interval across all active owners (the effective cadence). */
|
|
202
|
+
function effectiveIntervalMs(): number {
|
|
203
|
+
let min = Number.POSITIVE_INFINITY;
|
|
204
|
+
for (const interval of ownerIntervals.values()) {
|
|
205
|
+
if (interval < min) min = interval;
|
|
206
|
+
}
|
|
207
|
+
return min === Number.POSITIVE_INFINITY ? USER_COST_OVERLAY_RECONCILE_INTERVAL_MS : min;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Keep the shared timer on the effective minimum cadence across owners. A
|
|
212
|
+
* later owner may request a smaller interval, which restarts the timer; when
|
|
213
|
+
* an owner stops, the cadence relaxes back up to the smallest still-active
|
|
214
|
+
* owner's request. The timer therefore never outlives or ignores an owner's
|
|
215
|
+
* faster polling need, and never polls faster than the active set requires.
|
|
216
|
+
*/
|
|
217
|
+
function syncReconcileTimer(): void {
|
|
218
|
+
const intervalMs = effectiveIntervalMs();
|
|
219
|
+
if (reconcileTimer && reconcileTimerMs === intervalMs) return;
|
|
220
|
+
if (reconcileTimer) clearInterval(reconcileTimer);
|
|
221
|
+
reconcileTimer = setInterval(() => {
|
|
222
|
+
const stamp = configStamp();
|
|
223
|
+
if (!stamp) return;
|
|
224
|
+
if (
|
|
225
|
+
lastStamp
|
|
226
|
+
&& lastStamp.mtimeMs === stamp.mtimeMs
|
|
227
|
+
&& lastStamp.size === stamp.size
|
|
228
|
+
&& lastStamp.ctimeMs === stamp.ctimeMs
|
|
229
|
+
&& lastStamp.ino === stamp.ino
|
|
230
|
+
) return;
|
|
231
|
+
lastStamp = stamp;
|
|
232
|
+
try {
|
|
233
|
+
reconcileForOwners();
|
|
234
|
+
} catch {
|
|
235
|
+
// Display-only reconciliation must never take the proxy down.
|
|
236
|
+
}
|
|
237
|
+
}, intervalMs);
|
|
238
|
+
reconcileTimer.unref?.();
|
|
239
|
+
reconcileTimerMs = intervalMs;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Start the stat-based reconciler. Each call registers an owner lease; the
|
|
244
|
+
* shared timer starts with the first owner and stops when the last owner's
|
|
245
|
+
* `stop()` releases it.
|
|
246
|
+
*/
|
|
247
|
+
export function startUserCostOverlayReconciler(
|
|
248
|
+
options: { intervalMs?: number; liveConfig?: OcxConfig | null } = {},
|
|
249
|
+
): { stop(): void } {
|
|
250
|
+
const token = Symbol("user-cost-overlay-reconciler");
|
|
251
|
+
const liveConfig = options.liveConfig ?? null;
|
|
252
|
+
owners.set(token, liveConfig);
|
|
253
|
+
ownerIntervals.set(token, options.intervalMs ?? USER_COST_OVERLAY_RECONCILE_INTERVAL_MS);
|
|
254
|
+
if (liveConfig) registerPreservedProviderOwner(liveConfig);
|
|
255
|
+
syncReconcileTimer();
|
|
256
|
+
return {
|
|
257
|
+
stop() {
|
|
258
|
+
const ownedConfig = owners.get(token) ?? null;
|
|
259
|
+
owners.delete(token);
|
|
260
|
+
ownerIntervals.delete(token);
|
|
261
|
+
if (ownedConfig) unregisterPreservedProviderOwner(ownedConfig);
|
|
262
|
+
if (owners.size === 0) {
|
|
263
|
+
if (reconcileTimer) clearInterval(reconcileTimer);
|
|
264
|
+
reconcileTimer = null;
|
|
265
|
+
reconcileTimerMs = 0;
|
|
266
|
+
lastStamp = null;
|
|
267
|
+
// No live routing config remains, so nothing can keep disk-only
|
|
268
|
+
// providers alive: clear the cache to prevent a later save from
|
|
269
|
+
// resurrecting an externally deleted provider.
|
|
270
|
+
setPreservedDiskOnlyProviders(null);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
syncReconcileTimer();
|
|
274
|
+
// Other owners remain: recompute preservation against their live
|
|
275
|
+
// configs. A newer owner may have owned providers that an older owner
|
|
276
|
+
// still sees as disk-only; without this recompute the older owner's next
|
|
277
|
+
// unrelated save could erase them.
|
|
278
|
+
try {
|
|
279
|
+
recomputePreservedDiskOnlyProviders();
|
|
280
|
+
} catch {
|
|
281
|
+
// Preservation is display-only; a failed recompute must not break stop.
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Process-wide stop: releases EVERY owner's lease and the shared timer.
|
|
289
|
+
* Intended for tests and full process teardown. A per-server shutdown must
|
|
290
|
+
* call its own owner-scoped `stop()` handle instead — this function would
|
|
291
|
+
* otherwise tear down reconciliation (and disk-only preservation) for every
|
|
292
|
+
* other server still running in the same process.
|
|
293
|
+
*/
|
|
294
|
+
export function stopUserCostOverlayReconciler(): void {
|
|
295
|
+
for (const config of owners.values()) {
|
|
296
|
+
if (config) unregisterPreservedProviderOwner(config);
|
|
297
|
+
}
|
|
298
|
+
owners.clear();
|
|
299
|
+
ownerIntervals.clear();
|
|
300
|
+
if (reconcileTimer) clearInterval(reconcileTimer);
|
|
301
|
+
reconcileTimer = null;
|
|
302
|
+
reconcileTimerMs = 0;
|
|
303
|
+
lastStamp = null;
|
|
304
|
+
// No live routing config remains; drop preservation so a later save cannot
|
|
305
|
+
// resurrect externally deleted providers.
|
|
306
|
+
setPreservedDiskOnlyProviders(null);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Test-only reset for module-global reconciler state. */
|
|
310
|
+
export function resetUserCostOverlayReconcilerForTests(): void {
|
|
311
|
+
stopUserCostOverlayReconciler();
|
|
312
|
+
invalidReconcileCount = 0;
|
|
313
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime registry for user-configured provider cost overlays
|
|
3
|
+
* (`providers.<name>.modelCosts` in config.json — per-model prices in ocx's
|
|
4
|
+
* flat `modelXxx` convention, mirroring opencode's per-model pricing).
|
|
5
|
+
*
|
|
6
|
+
* The usage cost estimator stays pure: it receives overlays as parameters and
|
|
7
|
+
* defaults to this registry, which is refreshed at the config chokepoints
|
|
8
|
+
* (loadConfig and every persist path). A refresh that actually changes the
|
|
9
|
+
* rows replaces the active array with a NEW identity and bumps a version
|
|
10
|
+
* counter, so the estimator's memo and the /api/usage summary cache skip stale
|
|
11
|
+
* rows without cross-module invalidation. Refreshes with byte-identical rows
|
|
12
|
+
* are no-ops: config is reloaded at many chokepoints and an unchanged reload
|
|
13
|
+
* must not churn the version (see refreshUserCostOverlays). The configured
|
|
14
|
+
* provider-name set is part of the change identity: adding or removing a
|
|
15
|
+
* provider changes which names may collapse to a label base in the resolver,
|
|
16
|
+
* so it bumps the version even when no overlay row changed.
|
|
17
|
+
*
|
|
18
|
+
* Display-time estimation only — these rows never affect billing.
|
|
19
|
+
*/
|
|
20
|
+
import type { OcxConfig, OcxProviderConfig, ProviderCostOverlay } from "../types";
|
|
21
|
+
import { MAX_COST4_RATE, type ExpectedPriceOverlay } from "./expected-prices";
|
|
22
|
+
import { redactSecretString } from "../lib/redact";
|
|
23
|
+
|
|
24
|
+
const EMPTY: readonly ExpectedPriceOverlay[] = [];
|
|
25
|
+
|
|
26
|
+
let active: readonly ExpectedPriceOverlay[] = EMPTY;
|
|
27
|
+
let activeSignature = "";
|
|
28
|
+
let activeConfigured = new Set<string>();
|
|
29
|
+
let version = 0;
|
|
30
|
+
let preservedDiskOnlyProviders: Record<string, OcxProviderConfig> | null = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Preservation owner metadata rides through the shallow config projections in
|
|
34
|
+
* config.ts via an enumerable SYMBOL key. JSON.stringify ignores symbol keys,
|
|
35
|
+
* so the tag is process-local only and can never reach config.json or a DTO.
|
|
36
|
+
*/
|
|
37
|
+
const PRESERVATION_OWNER_STATE = Symbol("opencodex.user-cost-overlay-preservation-owner");
|
|
38
|
+
const PERSISTED_PROVIDER_DELETIONS = Symbol("opencodex.persisted-provider-deletions");
|
|
39
|
+
|
|
40
|
+
type PreservationOwnerState = {
|
|
41
|
+
refs: number;
|
|
42
|
+
config: OcxConfig;
|
|
43
|
+
ownedProviders: Set<string>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type PreservationTaggedConfig = OcxConfig & {
|
|
47
|
+
[PRESERVATION_OWNER_STATE]?: PreservationOwnerState;
|
|
48
|
+
[PERSISTED_PROVIDER_DELETIONS]?: readonly string[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const preservationOwnerStates = new Set<PreservationOwnerState>();
|
|
52
|
+
|
|
53
|
+
function providerNames(config: OcxConfig): Set<string> {
|
|
54
|
+
return new Set(Object.keys(config.providers ?? {}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Register one active live-config owner. Multiple server leases may share one config object. */
|
|
58
|
+
export function registerPreservedProviderOwner(config: OcxConfig): void {
|
|
59
|
+
const tagged = config as PreservationTaggedConfig;
|
|
60
|
+
const existing = tagged[PRESERVATION_OWNER_STATE];
|
|
61
|
+
if (existing && preservationOwnerStates.has(existing)) {
|
|
62
|
+
existing.refs += 1;
|
|
63
|
+
existing.ownedProviders = providerNames(config);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const state: PreservationOwnerState = {
|
|
67
|
+
refs: 1,
|
|
68
|
+
config,
|
|
69
|
+
ownedProviders: providerNames(config),
|
|
70
|
+
};
|
|
71
|
+
// Enumerable is deliberate: projectCustomModelCatalogMigration and the live
|
|
72
|
+
// binding guard use object spread, which must carry this process-local tag to
|
|
73
|
+
// the serialization view. Symbol keys are still omitted by JSON.stringify.
|
|
74
|
+
Object.defineProperty(tagged, PRESERVATION_OWNER_STATE, {
|
|
75
|
+
value: state,
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
});
|
|
79
|
+
preservationOwnerStates.add(state);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Refresh a registered owner's provider snapshot from a successful disk read.
|
|
84
|
+
*
|
|
85
|
+
* A provider still present on disk remains owned even if the live object
|
|
86
|
+
* temporarily omits it: management DELETE has an async-import gap between
|
|
87
|
+
* mutating the live config and committing the write, and a reconciler tick in
|
|
88
|
+
* that gap must not erase the deletion authority. Conversely, providers that
|
|
89
|
+
* disappeared from disk are no longer considered owned, and newly present
|
|
90
|
+
* providers are adopted only when this live config actually has the row.
|
|
91
|
+
*/
|
|
92
|
+
export function refreshPreservedProviderOwner(config: OcxConfig, disk: OcxConfig): void {
|
|
93
|
+
const state = (config as PreservationTaggedConfig)[PRESERVATION_OWNER_STATE];
|
|
94
|
+
if (!state || !preservationOwnerStates.has(state)) return;
|
|
95
|
+
const diskNames = providerNames(disk);
|
|
96
|
+
for (const name of [...state.ownedProviders]) {
|
|
97
|
+
if (!diskNames.has(name)) state.ownedProviders.delete(name);
|
|
98
|
+
}
|
|
99
|
+
for (const name of Object.keys(config.providers ?? {})) {
|
|
100
|
+
if (diskNames.has(name)) state.ownedProviders.add(name);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Release one active live-config owner lease. */
|
|
105
|
+
export function unregisterPreservedProviderOwner(config: OcxConfig): void {
|
|
106
|
+
const tagged = config as PreservationTaggedConfig;
|
|
107
|
+
const state = tagged[PRESERVATION_OWNER_STATE];
|
|
108
|
+
if (!state || !preservationOwnerStates.has(state)) return;
|
|
109
|
+
state.refs -= 1;
|
|
110
|
+
if (state.refs > 0) return;
|
|
111
|
+
preservationOwnerStates.delete(state);
|
|
112
|
+
if (tagged[PRESERVATION_OWNER_STATE] === state) {
|
|
113
|
+
delete tagged[PRESERVATION_OWNER_STATE];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Remember provider rows that exist on disk but are intentionally absent from
|
|
119
|
+
* the live routing config (added by an external editor after the proxy booted).
|
|
120
|
+
* They are merged back at the config serialization boundary so an unrelated
|
|
121
|
+
* in-process save cannot erase the external provider and its overlay.
|
|
122
|
+
*/
|
|
123
|
+
export function setPreservedDiskOnlyProviders(
|
|
124
|
+
providers: Record<string, OcxProviderConfig> | null,
|
|
125
|
+
): void {
|
|
126
|
+
preservedDiskOnlyProviders = providers;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Commit an explicit provider deletion only after the persisted serialization
|
|
131
|
+
* view has been accepted by the config write path. This clears the stale
|
|
132
|
+
* preservation row and removes the provider from every active live projection,
|
|
133
|
+
* so a second owner cannot resurrect it on a later unrelated save.
|
|
134
|
+
*/
|
|
135
|
+
function commitPersistedProviderDeletions(config: OcxConfig): void {
|
|
136
|
+
const tagged = config as PreservationTaggedConfig;
|
|
137
|
+
const deletions = tagged[PERSISTED_PROVIDER_DELETIONS];
|
|
138
|
+
if (!deletions || deletions.length === 0) return;
|
|
139
|
+
|
|
140
|
+
const deleted = new Set(deletions);
|
|
141
|
+
if (preservedDiskOnlyProviders) {
|
|
142
|
+
const next = { ...preservedDiskOnlyProviders };
|
|
143
|
+
for (const name of deleted) delete next[name];
|
|
144
|
+
preservedDiskOnlyProviders = Object.keys(next).length > 0 ? next : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const state of preservationOwnerStates) {
|
|
148
|
+
for (const name of deleted) {
|
|
149
|
+
state.ownedProviders.delete(name);
|
|
150
|
+
if (state.config.providers) delete state.config.providers[name];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
delete tagged[PERSISTED_PROVIDER_DELETIONS];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A serialization view of `config` that keeps externally added providers on
|
|
158
|
+
* disk without adding them to live routing state. Live providers win when a
|
|
159
|
+
* name exists in both maps.
|
|
160
|
+
*
|
|
161
|
+
* A registered owner also carries the provider names it previously owned. If
|
|
162
|
+
* that owner now omits one of those providers, the omission is an intentional
|
|
163
|
+
* in-process deletion rather than an old projection that never knew the row.
|
|
164
|
+
* Suppress only those names from preservation for this write. The suppression
|
|
165
|
+
* is committed globally only after refreshUserCostOverlays receives the
|
|
166
|
+
* successfully persisted view, so a failed atomic write cannot destroy the
|
|
167
|
+
* preservation safety net.
|
|
168
|
+
*/
|
|
169
|
+
export function withPreservedDiskOnlyProviders(config: OcxConfig): OcxConfig {
|
|
170
|
+
const tagged = config as PreservationTaggedConfig;
|
|
171
|
+
const owner = tagged[PRESERVATION_OWNER_STATE];
|
|
172
|
+
const deletedProviders = owner && preservationOwnerStates.has(owner)
|
|
173
|
+
? [...owner.ownedProviders].filter(name => !Object.hasOwn(config.providers ?? {}, name))
|
|
174
|
+
: [];
|
|
175
|
+
const deletedSet = new Set(deletedProviders);
|
|
176
|
+
|
|
177
|
+
let preserved: Record<string, OcxProviderConfig> | null = null;
|
|
178
|
+
if (preservedDiskOnlyProviders) {
|
|
179
|
+
const filtered = Object.entries(preservedDiskOnlyProviders)
|
|
180
|
+
.filter(([name]) => !deletedSet.has(name));
|
|
181
|
+
if (filtered.length > 0) preserved = Object.fromEntries(filtered);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let persisted: OcxConfig;
|
|
185
|
+
if (preserved && Object.keys(preserved).length > 0) {
|
|
186
|
+
persisted = {
|
|
187
|
+
...config,
|
|
188
|
+
providers: {
|
|
189
|
+
...preserved,
|
|
190
|
+
...config.providers,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
} else if (deletedProviders.length > 0) {
|
|
194
|
+
// Return a distinct object so the persisted-deletion marker remains scoped
|
|
195
|
+
// to this serialization attempt rather than the long-lived live config.
|
|
196
|
+
persisted = { ...config };
|
|
197
|
+
} else {
|
|
198
|
+
return config;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (deletedProviders.length > 0) {
|
|
202
|
+
Object.defineProperty(persisted as PreservationTaggedConfig, PERSISTED_PROVIDER_DELETIONS, {
|
|
203
|
+
value: deletedProviders,
|
|
204
|
+
enumerable: false,
|
|
205
|
+
configurable: true,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return persisted;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Test-only reset for the preserved disk-only provider registry. */
|
|
212
|
+
export function resetPreservedDiskOnlyProvidersForTests(): void {
|
|
213
|
+
preservedDiskOnlyProviders = null;
|
|
214
|
+
preservationOwnerStates.clear();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Exact four fields accepted for a user-configured cost tuple. */
|
|
218
|
+
export const COST4_RATE_KEYS = ["input", "output", "cacheRead", "cacheWrite"] as const;
|
|
219
|
+
|
|
220
|
+
/** Shared per-rate predicate used by config validation, display sanitization, and runtime lifting. */
|
|
221
|
+
export function isValidCost4Rate(rate: unknown): rate is number {
|
|
222
|
+
return typeof rate === "number"
|
|
223
|
+
&& Number.isFinite(rate)
|
|
224
|
+
&& rate >= 0
|
|
225
|
+
&& rate <= MAX_COST4_RATE;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** True when `value` is a complete cost entry: all four rates are non-negative finite numbers. */
|
|
229
|
+
function validCost4(value: unknown): value is ProviderCostOverlay {
|
|
230
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
231
|
+
const entry = value as Record<string, unknown>;
|
|
232
|
+
return COST4_RATE_KEYS.every(key => isValidCost4Rate(entry[key]));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Rebuild the active user-overlay rows from the current config. Malformed rows
|
|
237
|
+
* are skipped (config validation reports them separately); a provider with no
|
|
238
|
+
* overlay contributes nothing.
|
|
239
|
+
*/
|
|
240
|
+
export function refreshUserCostOverlays(config: OcxConfig): void {
|
|
241
|
+
// Only persisted serialization views carry PERSISTED_PROVIDER_DELETIONS.
|
|
242
|
+
// Committing here means changed writes update owner state only after the
|
|
243
|
+
// atomic write succeeds, while byte-identical successful saves also converge.
|
|
244
|
+
commitPersistedProviderDeletions(config);
|
|
245
|
+
|
|
246
|
+
const rows: ExpectedPriceOverlay[] = [];
|
|
247
|
+
const providers = config.providers;
|
|
248
|
+
if (providers) {
|
|
249
|
+
for (const [providerName, provider] of Object.entries(providers)) {
|
|
250
|
+
const costs = provider?.modelCosts;
|
|
251
|
+
if (!costs || typeof costs !== "object" || Array.isArray(costs)) continue;
|
|
252
|
+
for (const [modelId, cost4] of Object.entries(costs)) {
|
|
253
|
+
if (!modelId.trim() || !validCost4(cost4)) continue;
|
|
254
|
+
rows.push({
|
|
255
|
+
provider: providerName,
|
|
256
|
+
modelId,
|
|
257
|
+
// Copy ONLY the four validated rate fields: a hand-edited row may
|
|
258
|
+
// carry extra properties (e.g. a misplaced apiKey) that must never
|
|
259
|
+
// reach display estimates or /api/logs through the registry.
|
|
260
|
+
cost4: {
|
|
261
|
+
input: cost4.input,
|
|
262
|
+
output: cost4.output,
|
|
263
|
+
cacheRead: cost4.cacheRead,
|
|
264
|
+
cacheWrite: cost4.cacheWrite,
|
|
265
|
+
},
|
|
266
|
+
// Display provenance only: redact token-shaped provider/model ids so
|
|
267
|
+
// the source string can never echo a pasted key. Matching still uses
|
|
268
|
+
// the raw fields, and the change-detection signature below MUST keep
|
|
269
|
+
// them raw — distinct ids would otherwise collapse to "[REDACTED]"
|
|
270
|
+
// and skip the version bump.
|
|
271
|
+
source: `config:providers.${redactSecretString(providerName)}.modelCosts[${redactSecretString(modelId)}]`,
|
|
272
|
+
verifiedAt: "user-configured",
|
|
273
|
+
status: "verified",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Config is re-loaded at many chokepoints (server start, migrations, persist
|
|
279
|
+
// paths). Bumping the version on every load — even when nothing changed —
|
|
280
|
+
// would invalidate the /api/usage summary cache on unrelated reloads and
|
|
281
|
+
// churn the cost memo. Only a real overlay change bumps the version, so the
|
|
282
|
+
// cache survives reloads of an unchanged config.
|
|
283
|
+
// The signature MUST compare the raw matching fields: two different
|
|
284
|
+
// secret-shaped ids would both redact to "[REDACTED]" and falsely look
|
|
285
|
+
// unchanged, skipping the version bump and serving stale estimates. The
|
|
286
|
+
// signature is process-local state and is never serialized to a response;
|
|
287
|
+
// only the display `source` above is redacted.
|
|
288
|
+
// The configured provider-name set is part of the identity too: adding or
|
|
289
|
+
// removing a provider (even one without an overlay) changes which names are
|
|
290
|
+
// allowed to collapse to a label base, so the resolver memo and the
|
|
291
|
+
// /api/usage summary cache must be invalidated on that change as well.
|
|
292
|
+
const configuredNames = Object.keys(providers ?? {}).sort();
|
|
293
|
+
const signature = `${JSON.stringify(configuredNames)}\u0000${JSON.stringify(rows)}`;
|
|
294
|
+
if (signature === activeSignature) return;
|
|
295
|
+
activeSignature = signature;
|
|
296
|
+
active = rows;
|
|
297
|
+
activeConfigured = new Set(configuredNames);
|
|
298
|
+
version++;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Active user-configured overlay rows (stable identity until the next refresh). */
|
|
302
|
+
export function activeUserCostOverlays(): readonly ExpectedPriceOverlay[] {
|
|
303
|
+
return active;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Monotonic version bumped on every refresh; used by the estimator memo key. */
|
|
307
|
+
export function userCostOverlayVersion(): number {
|
|
308
|
+
return version;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Configured provider names from the last refresh (pricing-namespace identity). */
|
|
312
|
+
export function activeConfiguredProviders(): ReadonlySet<string> {
|
|
313
|
+
return activeConfigured;
|
|
314
|
+
}
|
|
@@ -3,7 +3,7 @@ import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fin
|
|
|
3
3
|
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
4
4
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
5
5
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
6
|
-
import { getValidAccessToken } from "../oauth";
|
|
6
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
7
7
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
|
|
8
8
|
import type { DescribeOutcome, VisionSettings } from "./describe";
|
|
9
9
|
|
|
@@ -67,8 +67,8 @@ export async function parseAnthropicVisionSSE(res: Response): Promise<DescribeOu
|
|
|
67
67
|
const delta = isRecord(data.delta) ? data.delta : {};
|
|
68
68
|
if (delta.type === "text_delta" && typeof delta.text === "string") text += delta.text;
|
|
69
69
|
} else if (data.type === "error") {
|
|
70
|
-
|
|
71
|
-
terminalError =
|
|
70
|
+
// Provider-authored stream errors can contain credentials, paths, or response bodies.
|
|
71
|
+
terminalError = "anthropic vision sidecar stream error";
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -116,7 +116,7 @@ export async function describeImageAnthropic(
|
|
|
116
116
|
try {
|
|
117
117
|
token = await getValidAccessToken(providerName);
|
|
118
118
|
} catch (error) {
|
|
119
|
-
return { text: "", error: `anthropic vision sidecar auth failed: ${
|
|
119
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const headers: Record<string, string> = {
|
|
@@ -166,7 +166,11 @@ export async function describeImageAnthropic(
|
|
|
166
166
|
if (!res.ok) {
|
|
167
167
|
const responseText = await res.text().catch(() => "");
|
|
168
168
|
console.warn(`[vision] anthropic sidecar HTTP ${res.status} (${Date.now() - startedAt}ms)`);
|
|
169
|
-
|
|
169
|
+
if (res.status === 401) {
|
|
170
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(responseText))}` };
|
|
171
|
+
}
|
|
172
|
+
// Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
|
|
173
|
+
return { text: "", error: `anthropic vision sidecar HTTP ${res.status}` };
|
|
170
174
|
}
|
|
171
175
|
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
172
176
|
try {
|
|
@@ -177,7 +181,7 @@ export async function describeImageAnthropic(
|
|
|
177
181
|
} catch (error) {
|
|
178
182
|
const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
179
183
|
console.warn(`[vision] anthropic sidecar ${kind} (${Date.now() - startedAt}ms)`);
|
|
180
|
-
return { text: "", error:
|
|
184
|
+
return { text: "", error: `anthropic vision sidecar ${kind}` };
|
|
181
185
|
} finally {
|
|
182
186
|
sidecarExit();
|
|
183
187
|
linkedSignal.cleanup();
|