@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
|
@@ -47,7 +47,7 @@ const DATA: Record<string, readonly Row[]> = {
|
|
|
47
47
|
"azure-openai": [["gpt-4.1",1047576,32768,"text,image",0,null,2,8,0.5,0],["gpt-4o",128000,16384,"text,image",0,null,2.5,10,1.25,0],["gpt-4o-mini",128000,16384,"text,image",0,null,0.15,0.6,0.075,0],["o3",200000,100000,"text,image",1,null,2,8,0.5,0],["o3-mini",200000,100000,"text",1,null,1.1,4.4,0.55,0]],
|
|
48
48
|
"cerebras": [["gemma-4-31b",131072,40960,"text,image",1,null,0.99,1.49,0,0],["gpt-oss-120b",131072,40960,"text",1,null,0.35,0.75,0,0],["llama3.1-8b",32000,8000,"text",0,null,0.1,0.1,0,0],["qwen-3-235b-a22b-instruct-2507",131000,32000,"text",0,null,0.6,1.2,0,0],["qwen-3-coder-480b",131072,32768,"text",0,null,0,0,0,0],["zai-glm-4.6",131072,32768,"text",0,null,0,0,0,0],["zai-glm-4.7",131072,40960,"text",1,null,2.25,2.75,2.25,0]],
|
|
49
49
|
"deepseek": [["deepseek-v4-flash",1048576,384000,"text",1,null,0.14,0.28,0.0028,0],["deepseek-v4-pro",1048576,384000,"text",1,null,0.435,0.87,0.003625,0]],
|
|
50
|
-
"google": [["deep-research-max-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["deep-research-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["gemini-1.5-flash",1000000,8192,"text,image",0,null,0.075,0.3,0.01875,0],["gemini-1.5-flash-8b",1000000,8192,"text,image",0,null,0.0375,0.15,0.01,0],["gemini-1.5-pro",1000000,8192,"text,image",0,null,1.25,5,0.3125,0],["gemini-2.0-flash",1048576,8192,"text,image",0,null,0.1,0.4,0.025,0],["gemini-2.0-flash-lite",1048576,8192,"text,image",0,null,0.075,0.3,0,0],["gemini-2.5-computer-use-preview-10-2025",131072,65536,"text,image",1,null,1.25,10,0,0],["gemini-2.5-flash",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-2.5-flash-lite",1048576,65536,"text,image",1,null,0.1,0.4,0.01,0],["gemini-2.5-flash-lite-preview-06-17",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-lite-preview-09-2025",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-preview-04-17",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-05-20",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1,null,0.3,2.5,0.075,0],["gemini-2.5-pro",1048576,65536,"text,image",1,null,1.25,10,0.125,0],["gemini-2.5-pro-preview-05-06",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-2.5-pro-preview-06-05",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-3-flash-preview",1048576,65536,"text,image",1,null,0.5,3,0.05,0],["gemini-3-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-flash-lite",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-lite-image",65536,65536,"text,image",1,null,0.25,30,0,0],["gemini-3.1-flash-lite-preview",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-live-preview",131072,65536,"text,image",1,null,0.75,4.5,0,0],["gemini-3.1-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.5-flash",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-3.5-flash-lite",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-3.6-flash",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0],["gemini-3.7-flash",1048576,65536,"text,image",1],["gemini-3.8-flash",1048576,65536,"text,image",1],["gemini-flash-latest",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-flash-lite-latest",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-live-2.5-flash",128000,8000,"text,image",1,null,0.5,2,0,0],["gemini-live-2.5-flash-preview-native-audio",131072,65536,"text",1,null,0.5,2,0,0],["gemini-robotics-er-1.6-preview",131072,65536,"text,image",1,null,1,5,0,0],["gemma-3-27b-it",131072,8192,"text,image",0,null,0,0,0,0],["gemma-4-26b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-26b-a4b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-26b-it",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-E2B-it",131072,8192,"text,image",1,null,0,0,0,0],["gemma-4-E4B-it",131072,8192,"text,image",1,null,0,0,0,0]],
|
|
50
|
+
"google": [["deep-research-max-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["deep-research-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["gemini-1.5-flash",1000000,8192,"text,image",0,null,0.075,0.3,0.01875,0],["gemini-1.5-flash-8b",1000000,8192,"text,image",0,null,0.0375,0.15,0.01,0],["gemini-1.5-pro",1000000,8192,"text,image",0,null,1.25,5,0.3125,0],["gemini-2.0-flash",1048576,8192,"text,image",0,null,0.1,0.4,0.025,0],["gemini-2.0-flash-lite",1048576,8192,"text,image",0,null,0.075,0.3,0,0],["gemini-2.5-computer-use-preview-10-2025",131072,65536,"text,image",1,null,1.25,10,0,0],["gemini-2.5-flash",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-2.5-flash-lite",1048576,65536,"text,image",1,null,0.1,0.4,0.01,0],["gemini-2.5-flash-lite-preview-06-17",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-lite-preview-09-2025",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-preview-04-17",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-05-20",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1,null,0.3,2.5,0.075,0],["gemini-2.5-pro",1048576,65536,"text,image",1,null,1.25,10,0.125,0],["gemini-2.5-pro-preview-05-06",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-2.5-pro-preview-06-05",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-3-flash-preview",1048576,65536,"text,image",1,null,0.5,3,0.05,0],["gemini-3-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-flash-lite",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-lite-image",65536,65536,"text,image",1,null,0.25,30,0,0],["gemini-3.1-flash-lite-preview",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-live-preview",131072,65536,"text,image",1,null,0.75,4.5,0,0],["gemini-3.1-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.5-flash",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-3.5-flash-lite",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-3.6-flash",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0],["gemini-3.7-flash",1048576,65536,"text,image",1],["gemini-3.8-flash",1048576,65536,"text,image",1],["gemini-4-argon",1048576,65536,"text,image",1],["gemini-flash-latest",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-flash-lite-latest",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-live-2.5-flash",128000,8000,"text,image",1,null,0.5,2,0,0],["gemini-live-2.5-flash-preview-native-audio",131072,65536,"text",1,null,0.5,2,0,0],["gemini-robotics-er-1.6-preview",131072,65536,"text,image",1,null,1,5,0,0],["gemma-3-27b-it",131072,8192,"text,image",0,null,0,0,0,0],["gemma-4-26b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-26b-a4b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-26b-it",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-E2B-it",131072,8192,"text,image",1,null,0,0,0,0],["gemma-4-E4B-it",131072,8192,"text,image",1,null,0,0,0,0]],
|
|
51
51
|
"minimax": [["MiniMax-M2",196608,128000,"text",1,null,0.3,1.2,0,0],["MiniMax-M2.1",204800,131072,"text",1,null,0.3,1.2,0,0],["MiniMax-M2.5",204800,131072,"text",1,null,0.3,1.2,0.03,0.375],["MiniMax-M2.5-highspeed",204800,131072,"text",1,null,0.6,2.4,0.06,0.375],["MiniMax-M2.5-lightning",204800,32000,"text",1,null,0.3,2.4,0,0],["MiniMax-M2.7",204800,131072,"text",1,null,0.3,1.2,0.06,0.375],["MiniMax-M2.7-highspeed",204800,131072,"text",1,null,0.6,2.4,0.06,0.375],["minimax-m3",512000,128000,"text,image",1,null,0.6,2.4,0.12,0],["MiniMax-M3",1000000,128000,"text,image,video",1,null,0.3,1.2,0.06,0],["MiniMax-M3.1-Flash-Preview",1000000,null,"text,image,video",1]],
|
|
52
52
|
"mistral": [["codestral-latest",256000,4096,"text",0,null,0.3,0.9,0,0],["devstral-2512",262144,262144,"text",0,null,0.4,2,0,0],["devstral-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-medium-2507",128000,128000,"text",0,null,0.4,2,0,0],["devstral-medium-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-small-2505",128000,128000,"text",0,null,0.1,0.3,0,0],["devstral-small-2507",128000,128000,"text",0,null,0.1,0.3,0,0],["labs-devstral-small-2512",256000,256000,"text,image",0,null,0,0,0,0],["magistral-medium-latest",128000,16384,"text",1,null,2,5,0,0],["magistral-small",128000,128000,"text",1,null,0.5,1.5,0,0],["ministral-3b-latest",128000,128000,"text",0,null,0.04,0.04,0,0],["ministral-8b-latest",128000,128000,"text",0,null,0.1,0.1,0,0],["mistral-large-2411",131072,16384,"text",0,null,2,6,0,0],["mistral-large-2512",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-large-latest",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-medium-2505",131072,131072,"text,image",0,null,0.4,2,0,0],["mistral-medium-2508",262144,262144,"text,image",0,null,0.4,2,0,0],["mistral-medium-2604",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-medium-latest",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["mistral-small-2506",128000,16384,"text,image",0,null,0.1,0.3,0,0],["mistral-small-2603",256000,256000,"text,image",1,null,0.15,0.6,0,0],["mistral-small-latest",256000,256000,"text,image",1,null,0.15,0.6,0,0],["open-mistral-7b",8000,8000,"text",0,null,0.25,0.25,0,0],["open-mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["open-mixtral-8x22b",64000,64000,"text",0,null,2,6,0,0],["open-mixtral-8x7b",32000,32000,"text",0,null,0.7,0.7,0,0],["pixtral-12b",128000,128000,"text,image",0,null,0.15,0.15,0,0],["pixtral-large-latest",128000,128000,"text,image",0,null,2,6,0,0]],
|
|
53
53
|
"moonshot": [["kimi-k2.5",262144,65536,"text,image",1,null,0,0,0,0]],
|
package/src/grok/sync.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface GrokSyncDeps {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async function defaultFetchAllModels(config: OcxConfig): Promise<CatalogModel[]> {
|
|
21
|
-
const { fetchAllModels } = await import("../server/management
|
|
21
|
+
const { fetchAllModels } = await import("../server/management/shared");
|
|
22
22
|
return fetchAllModels(config);
|
|
23
23
|
}
|
|
24
24
|
|
package/src/images/loop.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
|
20
20
|
import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
|
|
21
21
|
import type { AttemptRecoveryKind } from "../usage/log";
|
|
22
22
|
import { bridgeToResponsesSSE } from "../bridge";
|
|
23
|
+
import { isTerminalRefusalIncomplete } from "../bridge/terminal-refusal";
|
|
23
24
|
import { clearableDeadline, idleDeadline } from "../lib/abort";
|
|
24
25
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
25
26
|
import { applyUpstreamRecoveryInit, fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
|
|
@@ -39,6 +40,7 @@ import { submitVideoJob } from "./xai-video-client";
|
|
|
39
40
|
import { downloadVideoToArtifact, createImageBudget, pruneArtifacts } from "./artifacts";
|
|
40
41
|
import { IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "./synthetic-tool";
|
|
41
42
|
import type { ImageBridgePlan, VideoBridgePlan } from "./types";
|
|
43
|
+
import { guardEmptyToolCatalogStream, type ClientToolCatalogRestriction } from "../lib/empty-tool-catalog";
|
|
42
44
|
|
|
43
45
|
const SSE_HEADERS = {
|
|
44
46
|
"Content-Type": "text/event-stream",
|
|
@@ -241,7 +243,11 @@ function extractIterationThinking(events: AdapterEvent[]): OcxThinkingContent[]
|
|
|
241
243
|
};
|
|
242
244
|
|
|
243
245
|
for (const e of events) {
|
|
244
|
-
if (e.type === "
|
|
246
|
+
if (e.type === "text_delta" && e.anthropicThinkingSignature) {
|
|
247
|
+
flushVisible();
|
|
248
|
+
flushRaw();
|
|
249
|
+
parts.push({ type: "thinking", thinking: e.text, signature: e.anthropicThinkingSignature });
|
|
250
|
+
} else if (e.type === "thinking_delta") {
|
|
245
251
|
flushRaw();
|
|
246
252
|
thinking += e.thinking;
|
|
247
253
|
} else if (e.type === "reasoning_raw_delta") {
|
|
@@ -286,6 +292,7 @@ class LoopError extends Error {
|
|
|
286
292
|
*/
|
|
287
293
|
export interface ImageBridgeDeps {
|
|
288
294
|
parsed: OcxParsedRequest;
|
|
295
|
+
clientToolCatalogRestriction?: ClientToolCatalogRestriction;
|
|
289
296
|
adapter: ProviderAdapter;
|
|
290
297
|
incomingMeta: IncomingMeta;
|
|
291
298
|
plan?: ImageBridgePlan;
|
|
@@ -319,6 +326,12 @@ export interface ImageBridgeDeps {
|
|
|
319
326
|
waitForRequestSlot?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | void>;
|
|
320
327
|
/** Raw adapter usage at the terminal event, pre wire-normalization (see bridgeToResponsesSSE onUsage). */
|
|
321
328
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
329
|
+
/**
|
|
330
|
+
* After an `on429` rotation, offered the usage of the hidden iterations the left credential
|
|
331
|
+
* served. Returning true means it was billed to that credential's attempt, so `onUsage` will not
|
|
332
|
+
* report it again; the client terminal still includes it.
|
|
333
|
+
*/
|
|
334
|
+
onRotatedUsage?: (usage: OcxUsage) => boolean;
|
|
322
335
|
/**
|
|
323
336
|
* Optional 429 failover for the routed (non-xAI) model. Return a rebuilt adapter for the
|
|
324
337
|
* rotated credential, or null when the pool is exhausted. Async hooks support OAuth refresh;
|
|
@@ -353,6 +366,8 @@ export interface ImageBridgeDeps {
|
|
|
353
366
|
onCompletedResponse?: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => void;
|
|
354
367
|
/** WebSocket Responses path only — leave response id empty for protocol compatibility. */
|
|
355
368
|
forceEmptyResponseId?: boolean;
|
|
369
|
+
/** Responses inbound only: deliver a non-retryable upstream refusal as `response.failed`. */
|
|
370
|
+
terminalRefusalAsFailed?: boolean;
|
|
356
371
|
}
|
|
357
372
|
|
|
358
373
|
/**
|
|
@@ -377,6 +392,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
377
392
|
let paidImageCalls = 0;
|
|
378
393
|
let paidVideoCalls = 0;
|
|
379
394
|
let hiddenUsage: OcxUsage | undefined;
|
|
395
|
+
// Hidden usage already billed through onRotatedUsage: still on the client terminal, not in
|
|
396
|
+
// ledgerUsage, which is what onUsage reports once a rotation has moved some.
|
|
397
|
+
let rotatedUsage: OcxUsage | undefined;
|
|
398
|
+
let ledgerUsage: OcxUsage | undefined;
|
|
380
399
|
let activeProducers = 0;
|
|
381
400
|
|
|
382
401
|
const addUsage = (a: OcxUsage | undefined, b: OcxUsage | undefined): OcxUsage | undefined => {
|
|
@@ -569,6 +588,7 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
569
588
|
// runTurn adapters signal errors via {type:"error"} events, not HTTP status codes.
|
|
570
589
|
const errorEvent = events.find(e => e.type === "error");
|
|
571
590
|
if (errorEvent && errorEvent.type === "error") {
|
|
591
|
+
hiddenUsage = addUsage(hiddenUsage, errorEvent.usage);
|
|
572
592
|
if (errorEvent.code === "translation_buffer_limit") {
|
|
573
593
|
throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
|
|
574
594
|
}
|
|
@@ -709,6 +729,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
709
729
|
const rotated = await deps.on429(prepared.response.headers.get("retry-after"), prepared.response.headers,
|
|
710
730
|
iterParsed, prepared.response);
|
|
711
731
|
if (!rotated) break;
|
|
732
|
+
if (hiddenUsage && deps.onRotatedUsage?.(hiddenUsage)) {
|
|
733
|
+
rotatedUsage = addUsage(rotatedUsage, hiddenUsage);
|
|
734
|
+
hiddenUsage = undefined;
|
|
735
|
+
}
|
|
712
736
|
try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
713
737
|
adapter = rotated.adapter;
|
|
714
738
|
yield { type: "heartbeat" };
|
|
@@ -772,6 +796,8 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
772
796
|
signal,
|
|
773
797
|
inactivityTimeoutMs: stallTimeoutMs,
|
|
774
798
|
translatorBudget,
|
|
799
|
+
// A pass that fails was still billed; the error terminal below reports it with the hidden passes.
|
|
800
|
+
onErrorUsage: usage => { hiddenUsage = addUsage(hiddenUsage, usage); },
|
|
775
801
|
})) {
|
|
776
802
|
if (event.type === "heartbeat") yield event;
|
|
777
803
|
else {
|
|
@@ -863,13 +889,17 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
863
889
|
// Loop (fulfill + re-ask) ONLY when the model's actionable output is purely image_gen. A
|
|
864
890
|
// real tool call means this turn is terminal for Codex — finalize so those calls reach
|
|
865
891
|
// Codex. forceFinal also finalizes.
|
|
866
|
-
|
|
892
|
+
// A refused iteration also finalizes: fulfilling its media call would re-ask the model
|
|
893
|
+
// with the refused context (#7148).
|
|
894
|
+
const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceFinal
|
|
895
|
+
&& !split.passthrough.some(isTerminalRefusalIncomplete);
|
|
867
896
|
if (!shouldLoop) {
|
|
868
|
-
if (hiddenUsage) {
|
|
897
|
+
if (hiddenUsage || rotatedUsage) {
|
|
869
898
|
for (let i = split.passthrough.length - 1; i >= 0; i--) {
|
|
870
899
|
const e = split.passthrough[i];
|
|
871
900
|
if (e?.type === "done" || e?.type === "incomplete") {
|
|
872
|
-
|
|
901
|
+
ledgerUsage = addUsage(hiddenUsage, e.usage);
|
|
902
|
+
split.passthrough[i] = { ...e, usage: addUsage(rotatedUsage, ledgerUsage) };
|
|
873
903
|
break;
|
|
874
904
|
}
|
|
875
905
|
}
|
|
@@ -881,6 +911,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
881
911
|
// Discarded iteration still contributed tokens — accumulate for the final onUsage.
|
|
882
912
|
takeUsageFrom(split.passthrough);
|
|
883
913
|
|
|
914
|
+
for (const event of split.passthrough) {
|
|
915
|
+
if (event.type === "text_delta" && event.anthropicThinkingSignature) yield event;
|
|
916
|
+
}
|
|
917
|
+
|
|
884
918
|
// Fulfill each image/video call, then inject ONE assistant turn (thinking once + all tool
|
|
885
919
|
// calls) so Anthropic extended-thinking continuations stay valid across parallel calls.
|
|
886
920
|
const iterationThinking = extractIterationThinking(split.passthrough);
|
|
@@ -1052,6 +1086,8 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
1052
1086
|
});
|
|
1053
1087
|
}
|
|
1054
1088
|
} catch (e) {
|
|
1089
|
+
ledgerUsage = hiddenUsage;
|
|
1090
|
+
const usage = addUsage(rotatedUsage, hiddenUsage);
|
|
1055
1091
|
if (isTranslatorBudgetExceededError(e)) {
|
|
1056
1092
|
yield {
|
|
1057
1093
|
type: "error",
|
|
@@ -1059,14 +1095,14 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
1059
1095
|
errorType: "upstream_error",
|
|
1060
1096
|
code: e.code,
|
|
1061
1097
|
message: "upstream translation buffer exceeded the safe limit",
|
|
1062
|
-
...(
|
|
1098
|
+
...(usage ? { usage } : {}),
|
|
1063
1099
|
};
|
|
1064
1100
|
} else {
|
|
1065
1101
|
yield {
|
|
1066
1102
|
type: "error",
|
|
1067
1103
|
message: e instanceof LoopError ? e.message : (e instanceof Error ? e.message : String(e)),
|
|
1068
1104
|
...(e instanceof LoopError ? { status: e.status } : {}),
|
|
1069
|
-
...(
|
|
1105
|
+
...(usage ? { usage } : {}),
|
|
1070
1106
|
};
|
|
1071
1107
|
}
|
|
1072
1108
|
return;
|
|
@@ -1079,13 +1115,15 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
1079
1115
|
}
|
|
1080
1116
|
|
|
1081
1117
|
const sse = bridgeToResponsesSSE(
|
|
1082
|
-
produce(), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
|
|
1118
|
+
guardEmptyToolCatalogStream(produce(), deps.clientToolCatalogRestriction, translatorBudget), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
|
|
1083
1119
|
internalAbort.abort("client closed responses stream");
|
|
1084
1120
|
}, 2_000,
|
|
1085
1121
|
{
|
|
1086
1122
|
translatorBudget,
|
|
1087
1123
|
replayCacheScope: parsed._reasoningReplayScope,
|
|
1124
|
+
...(deps.clientToolCatalogRestriction ? { ...deps.clientToolCatalogRestriction, enforceDeclaredToolNames: false } : {}),
|
|
1088
1125
|
...(deps.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
1126
|
+
...(deps.terminalRefusalAsFailed ? { terminalRefusalAsFailed: true } : {}),
|
|
1089
1127
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
1090
1128
|
hideRawReasoning: parsed.options.hideRawReasoning,
|
|
1091
1129
|
stallTimeoutSec: deps.stallTimeoutSec,
|
|
@@ -1093,7 +1131,7 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
|
|
|
1093
1131
|
...(deps.onUsage ? {
|
|
1094
1132
|
// Terminal done/incomplete already includes hiddenUsage (merged above). Do not
|
|
1095
1133
|
// add it again here or request logs double-count multi-iteration image turns.
|
|
1096
|
-
onUsage: (usage: OcxUsage | undefined) => deps.onUsage?.(usage),
|
|
1134
|
+
onUsage: (usage: OcxUsage | undefined) => deps.onUsage?.(rotatedUsage ? ledgerUsage : usage),
|
|
1097
1135
|
} : {}),
|
|
1098
1136
|
...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
|
|
1099
1137
|
},
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
/** Refresh only previously connected clients; a refused file never blocks its peers. */
|
|
13
13
|
export async function refreshOwnedCatalogIntegrations(
|
|
14
14
|
input: Omit<OwnedIntegrationRefreshInput, "clientId">,
|
|
15
|
-
clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo"],
|
|
15
|
+
clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo", "qoder", "gentle-shell"],
|
|
16
16
|
options: { refreshOnly?: boolean; admit?: () => boolean } = {},
|
|
17
17
|
): Promise<OwnedIntegrationRefreshOutcome[]> {
|
|
18
18
|
// Client files are shared with the live proxy a sibling instance runs beside; their entries
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Design of record: devlog/_fin/260802_client_toggle_api/021 §5-6.
|
|
9
9
|
*/
|
|
10
|
-
import { lstatSync, mkdirSync,
|
|
10
|
+
import { lstatSync, mkdirSync, rmSync, statSync } from "node:fs";
|
|
11
11
|
import type { ConfigFormat } from "../clients/config-export";
|
|
12
|
+
import { readBoundedRegularFileSync } from "../lib/bounded-file-read";
|
|
12
13
|
import { canonicalizeJsonc } from "../lib/jsonc";
|
|
13
14
|
import { MAX_JSON_NESTING } from "./serialize";
|
|
14
15
|
import { atomicWriteFileNoFollow, isMissingPathError } from "../config/atomic-write";
|
|
@@ -24,6 +25,10 @@ import type { IntegrationClientId } from "./registry";
|
|
|
24
25
|
* then overwritten.
|
|
25
26
|
*/
|
|
26
27
|
export const PARSE_FAILED = Symbol("parse-failed");
|
|
28
|
+
const MAX_INTEGRATION_CONFIG_FILE_BYTES = 4 * 1024 * 1024;
|
|
29
|
+
|
|
30
|
+
/** Raised before any target mutation, including pending-marker creation. */
|
|
31
|
+
export class IntegrationWriteSizeError extends Error {}
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
34
|
* What `JSON.parse` has already discarded by the time we hold the parsed value,
|
|
@@ -285,7 +290,7 @@ export function fileIO(): Omit<IntegrationIO, "appendJournal" | "putRecord" | "d
|
|
|
285
290
|
lstatKind,
|
|
286
291
|
readText: path => {
|
|
287
292
|
try {
|
|
288
|
-
return { kind: "text", text:
|
|
293
|
+
return { kind: "text", text: readBoundedRegularFileSync(path, MAX_INTEGRATION_CONFIG_FILE_BYTES).toString("utf8") };
|
|
289
294
|
} catch (error) {
|
|
290
295
|
const code = (error as NodeJS.ErrnoException).code;
|
|
291
296
|
return code === "ENOENT" ? { kind: "missing" } : { kind: "failed", ...(code ? { code } : {}) };
|
|
@@ -300,6 +305,19 @@ export function fileIO(): Omit<IntegrationIO, "appendJournal" | "putRecord" | "d
|
|
|
300
305
|
}
|
|
301
306
|
},
|
|
302
307
|
writeText: (path, text) => {
|
|
308
|
+
/*
|
|
309
|
+
* One bound on both halves of the seam: a file past this size is one
|
|
310
|
+
* readText refuses, so writing it would strand the integration against
|
|
311
|
+
* its own configuration — or, for Cline's pending marker, leave a
|
|
312
|
+
* transaction recovery could never read back.
|
|
313
|
+
*/
|
|
314
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
315
|
+
if (bytes > MAX_INTEGRATION_CONFIG_FILE_BYTES) {
|
|
316
|
+
throw new IntegrationWriteSizeError(
|
|
317
|
+
`refusing oversized integration write to ${path}: `
|
|
318
|
+
+ `${bytes} bytes is past the ${MAX_INTEGRATION_CONFIG_FILE_BYTES}-byte read bound`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
303
321
|
const kind = lstatKind(path);
|
|
304
322
|
if (kind !== "file" && kind !== "missing") {
|
|
305
323
|
throw new Error(`refusing unsafe integration write target: ${path}`);
|
|
@@ -16,7 +16,7 @@ import { randomUUID } from "node:crypto";
|
|
|
16
16
|
import { appendFileSync, existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
17
17
|
import { join, resolve, sep } from "node:path";
|
|
18
18
|
import { atomicWriteFile } from "../config";
|
|
19
|
-
import { ensureDir, fingerprint, integrationsDir, isOwnershipRecord, type OwnershipRecord } from "./ownership";
|
|
19
|
+
import { ensureDir, fingerprint, integrationsDir, isOwnershipRecord, readRecordsStrict, type OwnershipRecord } from "./ownership";
|
|
20
20
|
import { isIntegrationClientId, type IntegrationClientId } from "./registry";
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -171,6 +171,20 @@ export function captureSnapshot(
|
|
|
171
171
|
return { kind: "stored", relPath: join("snapshots", clientId, opId) };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/** Remove only an unused pre-write snapshot; uncertain or committed evidence survives. */
|
|
175
|
+
export function discardUncommittedSnapshot(
|
|
176
|
+
clientId: IntegrationClientId,
|
|
177
|
+
opId: string,
|
|
178
|
+
dir: string = integrationsDir(),
|
|
179
|
+
): void {
|
|
180
|
+
const target = containedPath(dir, "snapshots", assertSafeComponent(clientId, "clientId"), assertSafeComponent(opId, "opId"));
|
|
181
|
+
if (findCommittedOperation(opId, dir)
|
|
182
|
+
|| Object.values(readRecordsStrict(dir)).some(record => record?.opId === opId)) {
|
|
183
|
+
throw new Error("integration snapshot still belongs to a recorded operation");
|
|
184
|
+
}
|
|
185
|
+
rmSync(target, { force: true });
|
|
186
|
+
}
|
|
187
|
+
|
|
174
188
|
/** Commit the row. Pruning is post-commit and can never fail the append. */
|
|
175
189
|
export function appendOperation(entry: JournalEntry, dir: string = integrationsDir()): void {
|
|
176
190
|
ensureDir(journalPath(dir));
|
|
@@ -131,8 +131,10 @@ export const DYNAMIC_SEGMENT = "*";
|
|
|
131
131
|
* paths silently have no declaration.
|
|
132
132
|
*/
|
|
133
133
|
const CLIENT_MANAGED_PATHS = {
|
|
134
|
+
qoder: [["providers", OPENCODE_PROVIDER_ID], ["modelConfigs", "customModels", DYNAMIC_SEGMENT]],
|
|
134
135
|
opencode: [["provider", OPENCODE_PROVIDER_ID], ["providers", OPENCODE_PROVIDER_ID]],
|
|
135
136
|
pi: [["providers", OPENCODE_PROVIDER_ID]],
|
|
137
|
+
"gentle-shell": [["providers", OPENCODE_PROVIDER_ID]],
|
|
136
138
|
omp: [["providers", OPENCODE_PROVIDER_ID]],
|
|
137
139
|
hermes: [["providers", OPENCODE_PROVIDER_ID]],
|
|
138
140
|
openclaw: [["models", "providers", OPENCODE_PROVIDER_ID]],
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Design of record: devlog/_fin/260802_client_toggle_api/021 §1.
|
|
10
10
|
*/
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { gentleShellPaths } from "../clients/gentle-shell-path";
|
|
12
13
|
import { assertDroidSettingsUnambiguous } from "./droid-settings";
|
|
13
14
|
import { readPath } from "./merge";
|
|
14
15
|
import type { OwnershipRecord } from "./ownership";
|
|
@@ -17,6 +18,8 @@ import { dirname, join } from "node:path";
|
|
|
17
18
|
import {
|
|
18
19
|
ClientPathError,
|
|
19
20
|
buildDroidContribution,
|
|
21
|
+
qoderConfigPath,
|
|
22
|
+
qoderHomeDir,
|
|
20
23
|
clineConfigPath,
|
|
21
24
|
clineSettingsDir,
|
|
22
25
|
droidConfigPath,
|
|
@@ -280,6 +283,11 @@ function xdgConfigHome(env: NodeJS.ProcessEnv, home: string): string {
|
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientSpec> = {
|
|
286
|
+
qoder: {
|
|
287
|
+
id: "qoder",
|
|
288
|
+
configPath: qoderConfigPath,
|
|
289
|
+
detectDir: (env = process.env, home = homedir()) => qoderHomeDir(env, home),
|
|
290
|
+
},
|
|
283
291
|
opencode: {
|
|
284
292
|
id: "opencode",
|
|
285
293
|
// These take `home` explicitly. The export registry's `destination` reads
|
|
@@ -498,6 +506,13 @@ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientS
|
|
|
498
506
|
return { configPath: droidConfigPath(env, home), detectDir };
|
|
499
507
|
},
|
|
500
508
|
},
|
|
509
|
+
"gentle-shell": {
|
|
510
|
+
id: "gentle-shell",
|
|
511
|
+
configPath: (env, home, io) => gentleShellPaths(env, home, io).configPath,
|
|
512
|
+
detectDir: (env, home, io) => gentleShellPaths(env, home, io).detectDir,
|
|
513
|
+
resolvePaths: (env, home, _context, io) => gentleShellPaths(env, home, io),
|
|
514
|
+
unresolvedPathHint: (_env, home = homedir()) => join(home, ".gentle-shell", "agent", "models.json"),
|
|
515
|
+
},
|
|
501
516
|
};
|
|
502
517
|
|
|
503
518
|
export const INTEGRATION_CLIENT_IDS: readonly IntegrationClientId[] =
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
captureSnapshot,
|
|
17
17
|
clearPruneFailure,
|
|
18
18
|
countSnapshots,
|
|
19
|
+
discardUncommittedSnapshot,
|
|
19
20
|
findOperation,
|
|
20
21
|
findCommittedOperation,
|
|
21
22
|
listOperations,
|
|
@@ -53,6 +54,7 @@ export interface IntegrationStateStore {
|
|
|
53
54
|
findOperation(opId: string): JournalEntry | null;
|
|
54
55
|
findCommittedOperation(opId: string): JournalEntry | null;
|
|
55
56
|
captureSnapshot(clientId: IntegrationClientId, opId: string, text: string | null): SnapshotRef;
|
|
57
|
+
discardUncommittedSnapshot(clientId: IntegrationClientId, opId: string): void;
|
|
56
58
|
readSnapshot(entry: JournalEntry):
|
|
57
59
|
| { kind: "none" }
|
|
58
60
|
| { kind: "stored"; text: string; path: string }
|
|
@@ -92,6 +94,7 @@ export function createIntegrationStateStore(root: string = integrationsDir()): I
|
|
|
92
94
|
findOperation: opId => findOperation(opId, dir),
|
|
93
95
|
findCommittedOperation: opId => findCommittedOperation(opId, dir),
|
|
94
96
|
captureSnapshot: (clientId, opId, text) => captureSnapshot(clientId, opId, text, dir),
|
|
97
|
+
discardUncommittedSnapshot: (clientId, opId) => discardUncommittedSnapshot(clientId, opId, dir),
|
|
95
98
|
readSnapshot: entry => readSnapshot(entry, dir),
|
|
96
99
|
countSnapshots: clientId => countSnapshots(clientId, dir),
|
|
97
100
|
pruneSnapshots: clientId => pruneSnapshots(clientId, dir),
|
|
@@ -18,7 +18,7 @@ import { shouldInjectApiAuthHeader } from "../codex/inject";
|
|
|
18
18
|
import { detachedConfigSnapshot } from "../config/admitted-identity";
|
|
19
19
|
import { copyPlainData } from "../lib/plain-data";
|
|
20
20
|
import type { OcxConfig } from "../types";
|
|
21
|
-
import { PARSE_FAILED, defaultIntegrationIO, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
|
|
21
|
+
import { IntegrationWriteSizeError, PARSE_FAILED, defaultIntegrationIO, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
|
|
22
22
|
import { inspectKiloCandidates } from "./kilo-candidates";
|
|
23
23
|
import {
|
|
24
24
|
fingerprint,
|
|
@@ -162,6 +162,15 @@ function commit(args: CommitArgs): WriteOutcome {
|
|
|
162
162
|
}
|
|
163
163
|
} catch (error) {
|
|
164
164
|
if (transactionStarted) return compensate(args, error, "could not write both Cline files");
|
|
165
|
+
if (error instanceof IntegrationWriteSizeError && args.snapshotPath) {
|
|
166
|
+
try {
|
|
167
|
+
args.store.discardUncommittedSnapshot(clientId, args.entry.opId);
|
|
168
|
+
} catch (cleanupError) {
|
|
169
|
+
return refuse(clientId, "write_failed", args.state,
|
|
170
|
+
`${messageOf(error)}; could not remove the unused backup: ${messageOf(cleanupError)}`, args.snapshotPath);
|
|
171
|
+
}
|
|
172
|
+
return refuse(clientId, "write_failed", args.state, messageOf(error));
|
|
173
|
+
}
|
|
165
174
|
return refuse(clientId, "write_failed", args.state, messageOf(error), args.snapshotPath);
|
|
166
175
|
}
|
|
167
176
|
try {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Host activation seam. Registration is synchronous; optional code loads only on use. */
|
|
2
|
+
import type { OcxConfig } from "../types";
|
|
3
|
+
import { setAdvisorPlanFactory } from "../server/responses/advisor-plan-slot";
|
|
4
|
+
|
|
5
|
+
export function activateAdvisor(config: OcxConfig): void {
|
|
6
|
+
if (config.advisor?.enabled !== true || typeof config.advisor.model !== "string" || !config.advisor.model.trim()) {
|
|
7
|
+
setAdvisorPlanFactory(config, null);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
setAdvisorPlanFactory(config, async input => {
|
|
11
|
+
// A later settings write may disable the same live config before this request starts.
|
|
12
|
+
if (config.advisor?.enabled !== true || !config.advisor.model?.trim()) return null;
|
|
13
|
+
const { createAdvisorRuntimePlan } = await import("../advisor/runtime");
|
|
14
|
+
return createAdvisorRuntimePlan(input);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fire-and-forget work the process started and nobody awaits.
|
|
3
|
+
*
|
|
4
|
+
* A lazy quota prime or a startup reconcile is deliberately left running behind a
|
|
5
|
+
* request so the request never waits on it. The work still owns files: the pool
|
|
6
|
+
* prime holds the native-main claim database open across ACL hardening and a WHAM
|
|
7
|
+
* read. Whoever later removes the directory those files live in has to wait for the
|
|
8
|
+
* owner. Without a handle on the work there was nothing to wait for: test teardown
|
|
9
|
+
* spun a synchronous retry loop, which blocks the very event loop the owner needs to
|
|
10
|
+
* reach its `finally` and close the file. On Windows every overlap became a 15 s EBUSY.
|
|
11
|
+
*
|
|
12
|
+
* Registering costs one Set entry per in-flight task and changes nothing about how
|
|
13
|
+
* the work runs. Settling is opt-in: test teardown calls it; requests never do.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface BackgroundWork {
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly settled: Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const inFlight = new Set<BackgroundWork>();
|
|
22
|
+
|
|
23
|
+
/** Record `work` until it settles and return it unchanged. Rejections stay the caller's. */
|
|
24
|
+
export function trackBackgroundWork<T>(label: string, work: Promise<T>): Promise<T> {
|
|
25
|
+
const entry: BackgroundWork = {
|
|
26
|
+
label,
|
|
27
|
+
settled: work.then(() => { inFlight.delete(entry); }, () => { inFlight.delete(entry); }),
|
|
28
|
+
};
|
|
29
|
+
inFlight.add(entry);
|
|
30
|
+
return work;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Labels of work still running, for diagnostics. */
|
|
34
|
+
export function pendingBackgroundWork(): string[] {
|
|
35
|
+
return [...inFlight].map(entry => entry.label);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BackgroundWorkSettlement {
|
|
39
|
+
readonly settled: boolean;
|
|
40
|
+
readonly pending: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wait until no tracked work is in flight, including work started by work that was
|
|
45
|
+
* already running. A bounded wait reports what is still running instead of throwing,
|
|
46
|
+
* so the caller decides whether that is fatal.
|
|
47
|
+
*/
|
|
48
|
+
export async function settleBackgroundWork(options: { timeoutMs?: number } = {}): Promise<BackgroundWorkSettlement> {
|
|
49
|
+
const deadline = options.timeoutMs === undefined ? Infinity : Date.now() + options.timeoutMs;
|
|
50
|
+
while (inFlight.size > 0) {
|
|
51
|
+
const remaining = deadline - Date.now();
|
|
52
|
+
if (remaining <= 0) break;
|
|
53
|
+
const batch = Promise.all([...inFlight].map(entry => entry.settled)).then(() => false);
|
|
54
|
+
if (remaining === Infinity) {
|
|
55
|
+
await batch;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
59
|
+
const expired = new Promise<boolean>(resolve => { timer = setTimeout(() => resolve(true), remaining); });
|
|
60
|
+
const timedOut = await Promise.race([batch, expired]);
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (timedOut) break;
|
|
63
|
+
}
|
|
64
|
+
return { settled: inFlight.size === 0, pending: pendingBackgroundWork() };
|
|
65
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const MAX_BOUNDED_READ_BYTES = 64 * 1024 * 1024;
|
|
4
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
5
|
+
|
|
6
|
+
function sameIdentity(
|
|
7
|
+
left: { dev: bigint; ino: bigint },
|
|
8
|
+
right: { dev: bigint; ino: bigint },
|
|
9
|
+
): boolean {
|
|
10
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readBoundedRegularFileSync(path: string, limit: number): Buffer {
|
|
14
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_BOUNDED_READ_BYTES) {
|
|
15
|
+
throw new RangeError("invalid bounded file read limit");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const windows = process.platform === "win32";
|
|
19
|
+
const flags = constants.O_RDONLY | (windows ? 0 : constants.O_NONBLOCK | constants.O_NOFOLLOW);
|
|
20
|
+
const entryBefore = windows ? lstatSync(path, { bigint: true }) : undefined;
|
|
21
|
+
if (entryBefore && (entryBefore.isSymbolicLink() || !entryBefore.isFile())) {
|
|
22
|
+
throw new Error("not a regular file");
|
|
23
|
+
}
|
|
24
|
+
const fd = openSync(path, flags);
|
|
25
|
+
try {
|
|
26
|
+
const opened = fstatSync(fd, { bigint: true });
|
|
27
|
+
if (!opened.isFile() || opened.size > BigInt(limit)) throw new Error("file is not bounded regular data");
|
|
28
|
+
if (entryBefore && (!sameIdentity(entryBefore, opened) || opened.ino === 0n)) {
|
|
29
|
+
throw new Error("file entry changed while opening");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Start near the observed size, but keep probing up to limit + 1 if the file grows.
|
|
33
|
+
let bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
34
|
+
let length = 0;
|
|
35
|
+
while (length < limit + 1) {
|
|
36
|
+
if (length === bytes.length) {
|
|
37
|
+
const capacity = Math.min(limit + 1, Math.max(bytes.length * 2, bytes.length + READ_CHUNK_BYTES));
|
|
38
|
+
const next = Buffer.alloc(capacity);
|
|
39
|
+
bytes.copy(next, 0, 0, length);
|
|
40
|
+
bytes = next;
|
|
41
|
+
}
|
|
42
|
+
const count = readSync(fd, bytes, length, Math.min(READ_CHUNK_BYTES, bytes.length - length), null);
|
|
43
|
+
if (count === 0) break;
|
|
44
|
+
length += count;
|
|
45
|
+
}
|
|
46
|
+
if (length > limit) throw new Error("file exceeds byte limit");
|
|
47
|
+
|
|
48
|
+
if (entryBefore) {
|
|
49
|
+
const entryAfter = lstatSync(path, { bigint: true });
|
|
50
|
+
if (entryAfter.isSymbolicLink() || !entryAfter.isFile() || !sameIdentity(opened, entryAfter)) {
|
|
51
|
+
throw new Error("file entry changed while reading");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return bytes.subarray(0, length);
|
|
55
|
+
} finally {
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -5,7 +5,16 @@ export interface DesktopCliIo {
|
|
|
5
5
|
export interface PathBunIo extends DesktopCliIo {
|
|
6
6
|
now?: () => number;
|
|
7
7
|
realpath?: (path: string) => string;
|
|
8
|
-
stat?: (path: string) => { isFile(): boolean; mode: number };
|
|
8
|
+
stat?: (path: string) => { isFile(): boolean; mode: number; uid?: number };
|
|
9
|
+
lstat?: (path: string) => {
|
|
10
|
+
isFile(): boolean;
|
|
11
|
+
isDirectory(): boolean;
|
|
12
|
+
isSymbolicLink(): boolean;
|
|
13
|
+
mode: number;
|
|
14
|
+
uid?: number;
|
|
15
|
+
};
|
|
16
|
+
readlink?: (path: string) => string;
|
|
17
|
+
geteuid?: () => number | undefined;
|
|
9
18
|
isRealBunBinary?: (path: string) => boolean;
|
|
10
19
|
spawnSync?: (path: string, args: string[], options: {
|
|
11
20
|
env: NodeJS.ProcessEnv;
|