@bitkyc08/opencodex 2.86.0 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +46 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- 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 +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -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/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- 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/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Server } from "bun";
|
|
2
|
+
import { supportsDesktopPicker } from "../desktop-picker-platform";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
4
|
import { getConfigDir } from "../../config/paths";
|
|
4
5
|
import type { DesktopPickerController } from "../desktop-picker";
|
|
@@ -12,6 +13,7 @@ import { discardPickerCaKey } from "./picker-ca";
|
|
|
12
13
|
import { preparePersistentPickerAuthority } from "./picker-ca-startup";
|
|
13
14
|
import type { PickerCaStore } from "./picker-ca-store";
|
|
14
15
|
import type { PickerRouteInput } from "./picker-models";
|
|
16
|
+
import { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
|
|
15
17
|
import { createPickerRuntime, pickerDesired, type CreatePickerRuntimeOptions, type PickerRuntime } from "./picker-runtime";
|
|
16
18
|
import type { SecurityRunner } from "./picker-trust";
|
|
17
19
|
import { ensureClaudeInterceptProxyToken, readClaudeInterceptProxyToken } from "./proxy-auth";
|
|
@@ -28,7 +30,7 @@ import { buildClaudeInterceptEnv, migrateClaudeInterceptSettings } from "./setti
|
|
|
28
30
|
* proxy. Desktop also hands that proxy to the Claude Code processes it spawns, so the choice is per
|
|
29
31
|
* client: Claude Code (no User-Agent on CONNECT) trusts only the intercept CA and gets the
|
|
30
32
|
* api.anthropic.com intercept and nothing else; the app itself (a browser User-Agent) trusts only
|
|
31
|
-
* the
|
|
33
|
+
* the OS root store and never meets the api.anthropic.com intercept, and only its claude.ai
|
|
32
34
|
* tunnels may be terminated by the picker runtime (src/claude/intercept/picker-runtime.ts).
|
|
33
35
|
*/
|
|
34
36
|
|
|
@@ -41,25 +43,7 @@ const PICKER_AUTHORITY_ERROR_CODES = new Set([
|
|
|
41
43
|
"picker_ca_unsupported_platform",
|
|
42
44
|
]);
|
|
43
45
|
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
export function claudeInterceptEnabled(config: Pick<OcxConfig, "claudeCode" | "runtimeRole">): boolean {
|
|
47
|
-
if (config.runtimeRole === "client") return false;
|
|
48
|
-
if (config.claudeCode?.enabled === false) return false;
|
|
49
|
-
return config.claudeCode?.intercept?.enabled !== false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function claudeInterceptProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
53
|
-
const configured = config.claudeCode?.intercept?.port;
|
|
54
|
-
if (typeof configured === "number" && Number.isInteger(configured) && configured >= 1 && configured <= 65535) return configured;
|
|
55
|
-
return publicPort + CLAUDE_INTERCEPT_PORT_OFFSET;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Desktop's egress proxy for picker mode: the port after the intercept proxy (before it at 65535). */
|
|
59
|
-
export function claudePickerProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
60
|
-
const interceptPort = claudeInterceptProxyPort(config, publicPort);
|
|
61
|
-
return interceptPort < 65535 ? interceptPort + 1 : interceptPort - 1;
|
|
62
|
-
}
|
|
46
|
+
export { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
|
|
63
47
|
|
|
64
48
|
export type ClaudeInterceptOutcome = { ok: true; state: ClaudeInterceptState } | { ok: false; reason: "disabled" | "client_role" | "ephemeral_port" | "port_in_use" | "stopped" | "failed"; port?: number; message?: string };
|
|
65
49
|
|
|
@@ -144,7 +128,7 @@ export interface StartClaudeInterceptOptions<T> {
|
|
|
144
128
|
createPicker?: (options: CreatePickerRuntimeOptions) => PickerRuntime;
|
|
145
129
|
/** Test seam: bind real CONNECT handlers on kernel-assigned ports without probe-and-release races. */
|
|
146
130
|
startProxy?: typeof startConnectProxy;
|
|
147
|
-
/** Test seams: the
|
|
131
|
+
/** Test seams: the platform trust runner and platform for the picker runtime and controller. */
|
|
148
132
|
pickerCaStore?: PickerCaStore;
|
|
149
133
|
pickerSecurity?: SecurityRunner;
|
|
150
134
|
pickerPlatform?: NodeJS.Platform;
|
|
@@ -167,18 +151,6 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
167
151
|
const startProxy = options.startProxy ?? startConnectProxy;
|
|
168
152
|
const authToken = ensureClaudeInterceptProxyToken(configDir);
|
|
169
153
|
const leaf = issueLocalInterceptLeaf(ca, CLAUDE_INTERCEPT_HOSTS);
|
|
170
|
-
// Refresh an env we already own (e.g. a pre-auth proxy URL left by an upgrade) before the
|
|
171
|
-
// authenticated proxy takes over the port — a plain `ocx start` after an update would
|
|
172
|
-
// otherwise 407 every CONNECT until the next `ocx ensure` or apply. Only `stale` state is
|
|
173
|
-
// rewritten, so installs that never applied first-party are untouched.
|
|
174
|
-
try {
|
|
175
|
-
const proxyPort = claudeInterceptProxyPort(options.config, options.publicPort);
|
|
176
|
-
migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxyPort, claudeInterceptCaCertPath(configDir), authToken));
|
|
177
|
-
} catch (error) {
|
|
178
|
-
// A skipped rewrite degrades to the pre-migration behaviour and ensure/apply retries it,
|
|
179
|
-
// but silence here leaves upgraded clients hitting 407 with no recorded cause.
|
|
180
|
-
console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
-
}
|
|
182
154
|
const listener = startClaudeInterceptListener<T>({
|
|
183
155
|
leaf,
|
|
184
156
|
dispatch: options.dispatch,
|
|
@@ -203,15 +175,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
203
175
|
await listener.stop(true);
|
|
204
176
|
throw new ClaudeInterceptProxyBindError(error, claudeInterceptProxyPort(options.config, options.publicPort));
|
|
205
177
|
}
|
|
178
|
+
// Publish only a successfully bound endpoint, using the handle's actual port. A failed
|
|
179
|
+
// fixed-port bind leaves settings untouched; an ephemeral test binder never publishes :0
|
|
180
|
+
// or a probed port another process can steal. Only stale, owned env blocks are rewritten.
|
|
181
|
+
try {
|
|
182
|
+
migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxy.port, claudeInterceptCaCertPath(configDir), authToken));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// Ensure/apply retries a skipped migration; upgraded clients otherwise keep hitting 407.
|
|
185
|
+
console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
}
|
|
206
187
|
// Widened on purpose: assignments happen in nested awaits the catch below must still see.
|
|
207
188
|
let picker = null as PickerRuntime | null;
|
|
208
189
|
let pickerProxy = null as ConnectProxyHandle | null;
|
|
209
190
|
let controller = null as DesktopPickerController | null;
|
|
210
191
|
let pickerProxyLive = false;
|
|
192
|
+
let stopped = false;
|
|
211
193
|
let pickerReason: ClaudeInterceptState["pickerReason"] = null;
|
|
212
194
|
let pickerFailurePort: number | undefined;
|
|
213
195
|
try {
|
|
214
|
-
if (options.loadPickerRoutes && (options.pickerPlatform ?? process.platform)
|
|
196
|
+
if (options.loadPickerRoutes && supportsDesktopPicker(options.pickerPlatform ?? process.platform)) {
|
|
215
197
|
// Remove any legacy plaintext key before inspecting or activating picker state.
|
|
216
198
|
discardPickerCaKey(configDir);
|
|
217
199
|
const { inspectDesktopPickerProfile } = await import("../desktop-picker-profile");
|
|
@@ -225,11 +207,15 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
225
207
|
});
|
|
226
208
|
const pickerProfileApplied = pickerProfile.kind === "applied";
|
|
227
209
|
let pickerBlocked = false;
|
|
210
|
+
let pickerWired = false;
|
|
228
211
|
try {
|
|
229
212
|
const { observeClaudeDesktopMode, resolveClaudeDesktopMode } = await import("../desktop-first-party");
|
|
230
213
|
const fresh = options.config;
|
|
231
214
|
const platform = options.pickerPlatform ?? process.platform;
|
|
232
|
-
|
|
215
|
+
const pickerRequired = pickerProfileApplied || pickerDesired(fresh, resolveClaudeDesktopMode(fresh, observeClaudeDesktopMode(fresh)), platform);
|
|
216
|
+
// Preserve macOS's dormant relay; Windows defers binding until enable or profile recovery.
|
|
217
|
+
pickerWired = platform !== "win32" || pickerRequired;
|
|
218
|
+
if (pickerRequired) {
|
|
233
219
|
await preparePersistentPickerAuthority({ configDir, store: options.pickerCaStore, security: options.pickerSecurity, platform });
|
|
234
220
|
}
|
|
235
221
|
} catch (error) {
|
|
@@ -276,63 +262,76 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
276
262
|
const runtime = picker;
|
|
277
263
|
const interceptPort = listener.port!;
|
|
278
264
|
const pickerPort = claudePickerProxyPort(options.config, options.publicPort);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
265
|
+
const bindPickerProxy = async (): Promise<void> => {
|
|
266
|
+
if (stopped || pickerProxyLive) return;
|
|
267
|
+
try {
|
|
268
|
+
pickerProxy = await startProxy(pickerPort, {
|
|
269
|
+
interceptPort,
|
|
270
|
+
// No authToken: Desktop's egressProxyUrl cannot present proxy credentials, so this
|
|
271
|
+
// listener stays an unauthenticated loopback relay until the profile format can carry
|
|
272
|
+
// one. The intercept proxy above is the credential-bearing hop.
|
|
273
|
+
// No host list here: the choice below depends on which client opened the tunnel.
|
|
274
|
+
interceptHosts: [],
|
|
275
|
+
selectTunnel: (host, port, request) => {
|
|
276
|
+
// Desktop hands its pinned egress proxy to the Claude Code processes it spawns, so their
|
|
277
|
+
// api.anthropic.com traffic arrives here too and gets the same intercept as on the Claude
|
|
278
|
+
// Code proxy. Those processes trust only the intercept CA, so the picker never terminates
|
|
279
|
+
// their claude.ai tunnels; only the app's own (browser) CONNECTs reach the picker.
|
|
280
|
+
if (!isBrowserConnect(request)) {
|
|
281
|
+
return port === 443 && (CLAUDE_INTERCEPT_HOSTS as readonly string[]).includes(host.toLowerCase())
|
|
282
|
+
? { kind: "intercept", port: interceptPort }
|
|
283
|
+
: { kind: "blind" };
|
|
284
|
+
}
|
|
285
|
+
return runtime.selectTunnel(host, port);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
if (stopped) { await pickerProxy.close(); return; }
|
|
289
|
+
pickerProxyLive = true;
|
|
290
|
+
pickerReason = null;
|
|
291
|
+
pickerFailurePort = undefined;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// Picker mode is optional: a busy port leaves the intercept pair running without it.
|
|
294
|
+
// The selected profile row survives; a dormant enable can retry after the port is free.
|
|
295
|
+
pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
|
|
296
|
+
if (pickerReason === "port_in_use") pickerFailurePort = pickerPort;
|
|
297
|
+
console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
if (pickerWired) {
|
|
302
|
+
try { await bindPickerProxy(); }
|
|
303
|
+
catch { await runtime.stop(); picker = null; }
|
|
304
|
+
}
|
|
305
|
+
if (picker) {
|
|
306
|
+
// Dynamic: the controller reaches desktop-first-party, which imports this module.
|
|
307
|
+
const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
|
|
308
|
+
import("../desktop-picker"),
|
|
309
|
+
import("../../config"),
|
|
310
|
+
createPickerPreferenceWriter(options.config),
|
|
311
|
+
]);
|
|
312
|
+
controller = createDesktopPickerController({
|
|
313
|
+
runtime: picker,
|
|
314
|
+
readConfig: loadConfig,
|
|
315
|
+
persistPreference,
|
|
316
|
+
proxyPort: () => (pickerProxyLive ? pickerProxy?.port ?? null : null),
|
|
317
|
+
ensureProxy: async () => {
|
|
318
|
+
await bindPickerProxy();
|
|
319
|
+
if (!pickerProxyLive) return;
|
|
320
|
+
await runtime.start();
|
|
321
|
+
if (!stopped && activeController === controller) activePicker = runtime;
|
|
298
322
|
},
|
|
323
|
+
configDir,
|
|
324
|
+
persistentAuthority: { store: options.pickerCaStore },
|
|
325
|
+
...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
|
|
326
|
+
...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
|
|
299
327
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
308
|
-
await runtime.stop();
|
|
309
|
-
picker = null;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (picker) {
|
|
313
|
-
// Dynamic: the controller reaches desktop-first-party, which imports this module.
|
|
314
|
-
const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
|
|
315
|
-
import("../desktop-picker"),
|
|
316
|
-
import("../../config"),
|
|
317
|
-
createPickerPreferenceWriter(options.config),
|
|
318
|
-
]);
|
|
319
|
-
const boundProxy = pickerProxy;
|
|
320
|
-
controller = createDesktopPickerController({
|
|
321
|
-
runtime: picker,
|
|
322
|
-
readConfig: loadConfig,
|
|
323
|
-
persistPreference,
|
|
324
|
-
proxyPort: () => (pickerProxyLive && boundProxy ? boundProxy.port : null),
|
|
325
|
-
configDir,
|
|
326
|
-
persistentAuthority: { store: options.pickerCaStore },
|
|
327
|
-
...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
|
|
328
|
-
...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
|
|
329
|
-
});
|
|
330
|
-
await picker.start();
|
|
331
|
-
// Restore inspects the retained identity without installing trust. Explicit enable/trust
|
|
332
|
-
// owns consent when trust was revoked or legacy migration introduced a new authority.
|
|
333
|
-
if (pickerProfileApplied && controller) {
|
|
334
|
-
void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
|
|
335
|
-
.catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
328
|
+
if (pickerWired) await picker.start();
|
|
329
|
+
// Restore inspects the retained identity without installing trust. Explicit enable/trust
|
|
330
|
+
// owns consent when trust was revoked or legacy migration introduced a new authority.
|
|
331
|
+
if (pickerProfileApplied && controller) {
|
|
332
|
+
void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
|
|
333
|
+
.catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
334
|
+
}
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
337
|
}
|
|
@@ -348,21 +347,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
348
347
|
throw error;
|
|
349
348
|
}
|
|
350
349
|
const state: ClaudeInterceptState = {
|
|
351
|
-
pickerReason,
|
|
352
|
-
pickerFailurePort,
|
|
350
|
+
get pickerReason() { return pickerReason; },
|
|
351
|
+
get pickerFailurePort() { return pickerFailurePort; },
|
|
353
352
|
proxyPort: proxy.port,
|
|
354
353
|
caCertPath: claudeInterceptCaCertPath(configDir),
|
|
355
|
-
pickerProxyPort
|
|
354
|
+
get pickerProxyPort() { return pickerProxyLive ? pickerProxy?.port ?? null : null; },
|
|
356
355
|
};
|
|
357
356
|
activeState = state;
|
|
358
|
-
activePicker = picker;
|
|
357
|
+
activePicker = pickerProxyLive ? picker : null;
|
|
359
358
|
activeController = controller;
|
|
360
359
|
const ownPicker = picker;
|
|
361
360
|
const ownController = controller;
|
|
362
361
|
return {
|
|
363
362
|
...state,
|
|
363
|
+
get pickerProxyPort() { return state.pickerProxyPort; },
|
|
364
|
+
get pickerReason() { return state.pickerReason; },
|
|
365
|
+
get pickerFailurePort() { return state.pickerFailurePort; },
|
|
364
366
|
listener,
|
|
365
367
|
stop: async () => {
|
|
368
|
+
stopped = true;
|
|
366
369
|
if (activeState === state) activeState = null;
|
|
367
370
|
if (activePicker === ownPicker) activePicker = null;
|
|
368
371
|
if (activeController === ownController) activeController = null;
|
package/src/claude/outbound.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anthropicProgressEnvelope, isAnthropicProgressMirror } from "../responses/anthropic-progress";
|
|
1
2
|
/**
|
|
2
3
|
* Claude Code outbound: internal /v1/responses output -> Anthropic Messages API shapes.
|
|
3
4
|
*
|
|
@@ -222,10 +223,28 @@ export function sanitizeWebSearchInput(input: unknown): Rec {
|
|
|
222
223
|
/**
|
|
223
224
|
* Map a Responses `web_search_call` item to its Anthropic pair: the server_tool_use
|
|
224
225
|
* input (query/queries) and the web_search_tool_result content (hits, or the error
|
|
225
|
-
* object when the search failed).
|
|
226
|
+
* object when the search failed). An item carrying `anthropic_blocks` came from Anthropic's
|
|
227
|
+
* own server-side call and maps back to those blocks verbatim (`srvtoolu_` id,
|
|
228
|
+
* `encrypted_content`, web_fetch), so the client can replay them on its next turn.
|
|
229
|
+
* Shared by the SSE, JSON and direct translation paths.
|
|
226
230
|
*/
|
|
227
|
-
export function webSearchPairFromItem(item: Rec): {
|
|
231
|
+
export function webSearchPairFromItem(item: Rec): {
|
|
232
|
+
id: string; name: string; resultType: string; input: Rec; resultContent: unknown; completed: boolean;
|
|
233
|
+
} {
|
|
228
234
|
const action = isRec(item.action) ? item.action : {};
|
|
235
|
+
const completed = item.status !== "failed";
|
|
236
|
+
const { use, result } = isRec(item.anthropic_blocks) ? item.anthropic_blocks : {};
|
|
237
|
+
if (isRec(use) && isRec(result) && typeof use.id === "string" && isRec(use.input)
|
|
238
|
+
&& (use.name === "web_search" || use.name === "web_fetch")
|
|
239
|
+
&& result.tool_use_id === use.id && result.type === `${use.name}_tool_result`) {
|
|
240
|
+
return { id: use.id, name: use.name, resultType: result.type, input: use.input, resultContent: result.content, completed };
|
|
241
|
+
}
|
|
242
|
+
const id = typeof item.id === "string" && item.id.length > 0 ? item.id : `srvtoolu_${uuid()}`;
|
|
243
|
+
// An item emitted before `anthropic_blocks` carried a server web_fetch as `fetch_result`.
|
|
244
|
+
if (item.fetch_result !== undefined) {
|
|
245
|
+
const input = { url: typeof action.url === "string" ? action.url : "" };
|
|
246
|
+
return { id, name: "web_fetch", resultType: "web_fetch_tool_result", input, resultContent: item.fetch_result, completed };
|
|
247
|
+
}
|
|
229
248
|
const queries = Array.isArray(action.queries)
|
|
230
249
|
? action.queries.filter((q): q is string => typeof q === "string" && q.length > 0)
|
|
231
250
|
: [];
|
|
@@ -233,7 +252,6 @@ export function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resu
|
|
|
233
252
|
const input = sanitizeWebSearchInput(
|
|
234
253
|
queries.length > 1 ? { queries } : { query: queries[0] ?? query },
|
|
235
254
|
);
|
|
236
|
-
const completed = item.status !== "failed";
|
|
237
255
|
let resultContent: unknown;
|
|
238
256
|
if (completed) {
|
|
239
257
|
const hits: Rec[] = [];
|
|
@@ -248,8 +266,7 @@ export function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resu
|
|
|
248
266
|
} else {
|
|
249
267
|
resultContent = { type: "web_search_tool_result_error", error_code: "unavailable" };
|
|
250
268
|
}
|
|
251
|
-
|
|
252
|
-
return { id, input, resultContent, completed };
|
|
269
|
+
return { id, name: "web_search", resultType: "web_search_tool_result", input, resultContent, completed };
|
|
253
270
|
}
|
|
254
271
|
|
|
255
272
|
/**
|
|
@@ -553,7 +570,13 @@ export function responsesSseToAnthropicSse(
|
|
|
553
570
|
terminalDelivered = true;
|
|
554
571
|
};
|
|
555
572
|
|
|
573
|
+
let progressMirrorId: string | undefined;
|
|
556
574
|
const handleFrame = (eventName: string, data: Rec) => {
|
|
575
|
+
const frameItem = isRec(data.item) ? data.item : null;
|
|
576
|
+
if (progressMirrorId && (data.item_id === progressMirrorId || frameItem?.id === progressMirrorId)) {
|
|
577
|
+
if (eventName === "response.output_item.done") progressMirrorId = undefined;
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
557
580
|
switch (eventName) {
|
|
558
581
|
case "response.created":
|
|
559
582
|
case "response.in_progress": {
|
|
@@ -705,7 +728,7 @@ export function responsesSseToAnthropicSse(
|
|
|
705
728
|
const toolIndex = blockIndex++;
|
|
706
729
|
emit("content_block_start", {
|
|
707
730
|
type: "content_block_start", index: toolIndex,
|
|
708
|
-
content_block: { type: "server_tool_use", id: pair.id, name:
|
|
731
|
+
content_block: { type: "server_tool_use", id: pair.id, name: pair.name },
|
|
709
732
|
});
|
|
710
733
|
emit("content_block_delta", {
|
|
711
734
|
type: "content_block_delta", index: toolIndex,
|
|
@@ -715,10 +738,10 @@ export function responsesSseToAnthropicSse(
|
|
|
715
738
|
const resultIndex = blockIndex++;
|
|
716
739
|
emit("content_block_start", {
|
|
717
740
|
type: "content_block_start", index: resultIndex,
|
|
718
|
-
content_block: { type:
|
|
741
|
+
content_block: { type: pair.resultType, tool_use_id: pair.id, content: pair.resultContent },
|
|
719
742
|
});
|
|
720
743
|
emit("content_block_stop", { type: "content_block_stop", index: resultIndex });
|
|
721
|
-
if (pair.completed) webSearchRequests++;
|
|
744
|
+
if (pair.completed && pair.name === "web_search") webSearchRequests++;
|
|
722
745
|
break;
|
|
723
746
|
}
|
|
724
747
|
// Close the matching open block (message/reasoning items close implicitly on
|
|
@@ -772,6 +795,15 @@ export function responsesSseToAnthropicSse(
|
|
|
772
795
|
ensureBlock("thinking");
|
|
773
796
|
}
|
|
774
797
|
if (open?.kind === "thinking") {
|
|
798
|
+
if (env?.upd && typeof env.txt === "string") {
|
|
799
|
+
// This plaintext was deliberately surfaced as commentary, unlike hidden reasoning.
|
|
800
|
+
progressMirrorId = env.upd;
|
|
801
|
+
const bytes = Buffer.byteLength(env.txt);
|
|
802
|
+
translatorBudget.chargeRetained(bytes, { kind: "reasoning" });
|
|
803
|
+
translatorBudget.releaseRetained(open.thinkingBufBytes ?? 0, { kind: "reasoning" });
|
|
804
|
+
open.thinkingBuf = env.txt;
|
|
805
|
+
open.thinkingBufBytes = bytes;
|
|
806
|
+
}
|
|
775
807
|
if (env?.sig) open.reasoningSig = env.sig;
|
|
776
808
|
if (native) open.reasoningNative = typeof item.id === "string" && item.id.length > 0 ? { enc: native, id: item.id } : { enc: native };
|
|
777
809
|
closeOpenBlock();
|
|
@@ -976,7 +1008,8 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
|
|
|
976
1008
|
let sawToolUse = false;
|
|
977
1009
|
let webSearchRequests = 0;
|
|
978
1010
|
|
|
979
|
-
for (const raw of output) {
|
|
1011
|
+
for (const [index, raw] of output.entries()) {
|
|
1012
|
+
if (index > 0 && isAnthropicProgressMirror(raw, anthropicProgressEnvelope(output[index - 1], translatorBudget))) continue;
|
|
980
1013
|
if (!isRec(raw)) continue;
|
|
981
1014
|
switch (raw.type) {
|
|
982
1015
|
case "message": {
|
|
@@ -1010,7 +1043,7 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
|
|
|
1010
1043
|
const native = nativeEncryptedReasoning(encrypted);
|
|
1011
1044
|
const tag = native && nativeReasoningTagFor?.(native);
|
|
1012
1045
|
if (parts.length > 0 || env?.sig || tag) {
|
|
1013
|
-
const text = parts.join("\n\n");
|
|
1046
|
+
const text = env?.upd ? env.txt! : parts.join("\n\n");
|
|
1014
1047
|
const nat = tag ? { enc: native, model, tag, ...(typeof raw.id === "string" && raw.id.length > 0 ? { id: raw.id } : {}) } : undefined;
|
|
1015
1048
|
content.push({ type: "thinking", thinking: text, signature: env?.sig ?? encodeReasoningEnvelope({ txt: text, ...(nat ? { nat } : {}) }, translatorBudget) });
|
|
1016
1049
|
}
|
|
@@ -1034,9 +1067,9 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
|
|
|
1034
1067
|
case "web_search_call": {
|
|
1035
1068
|
// Server-side search: emit the Anthropic pair. Does NOT set sawToolUse.
|
|
1036
1069
|
const pair = webSearchPairFromItem(raw);
|
|
1037
|
-
content.push({ type: "server_tool_use", id: pair.id, name:
|
|
1038
|
-
content.push({ type:
|
|
1039
|
-
if (pair.completed) webSearchRequests++;
|
|
1070
|
+
content.push({ type: "server_tool_use", id: pair.id, name: pair.name, input: pair.input });
|
|
1071
|
+
content.push({ type: pair.resultType, tool_use_id: pair.id, content: pair.resultContent });
|
|
1072
|
+
if (pair.completed && pair.name === "web_search") webSearchRequests++;
|
|
1040
1073
|
break;
|
|
1041
1074
|
}
|
|
1042
1075
|
default:
|
package/src/cli/account-api.ts
CHANGED
|
@@ -8,6 +8,7 @@ export const threshold = z.number().int().min(0).max(100);
|
|
|
8
8
|
const nullableThreshold = threshold.nullable();
|
|
9
9
|
const row = z.object({
|
|
10
10
|
id: accountId, alias: text.optional(), autoSwitchThresholdOverride: nullableThreshold.optional(),
|
|
11
|
+
useRemainingQuota: z.boolean().optional(),
|
|
11
12
|
quota: z.object({ shortWindowSeconds: z.number().finite().optional(), shortResetAt: z.number().finite().optional(),
|
|
12
13
|
weeklyResetAt: z.number().finite().optional() }).nullable().optional(),
|
|
13
14
|
quotaAutoRefresh: z.object({ fiveHourAvailable: z.boolean(), weeklyAvailable: z.boolean(),
|
|
@@ -39,6 +40,11 @@ export const poolSchema = z.object({
|
|
|
39
40
|
export const thresholdReceipt = z.object({ ok: z.literal(true), id: accountId,
|
|
40
41
|
autoSwitchThresholdOverride: nullableThreshold, autoSwitchThreshold: threshold });
|
|
41
42
|
export const creditReceipt = z.object({ ok: z.literal(true), id: accountId, creditsAfterLimit: z.boolean() });
|
|
43
|
+
export const remainingQuotaReceipt = z.object({ ok: z.literal(true), id: accountId,
|
|
44
|
+
useRemainingQuota: z.boolean(), autoSwitchThreshold: threshold,
|
|
45
|
+
mainAccountHardLock: z.object({ enabled: z.boolean(), state: z.enum(["off", "unknown", "ready", "blocked"]),
|
|
46
|
+
thresholds: z.object({ short: threshold, long: threshold }) }).optional(),
|
|
47
|
+
});
|
|
42
48
|
export const creditAllReceipt = z.object({ ok: z.literal(true), all: z.boolean(),
|
|
43
49
|
ids: z.array(accountId).refine(ids => new Set(ids).size === ids.length) });
|
|
44
50
|
const window = z.enum(["five_hour", "seven_day", "seven_day_overage_included"]);
|
|
@@ -5,14 +5,15 @@ import { resolveCodexAccountTargetFromRows } from "./account-target";
|
|
|
5
5
|
import { runCatalogAction } from "./catalog-command-result";
|
|
6
6
|
import { serializeManagementJson } from "./json-input";
|
|
7
7
|
import { creditAllReceipt, creditReceipt, grantsSchema, parseDto, poolSchema, record,
|
|
8
|
-
rosterSchema, threshold, thresholdReceipt } from "./account-policy-dto";
|
|
8
|
+
rosterSchema, threshold, thresholdReceipt, remainingQuotaReceipt } from "./account-policy-dto";
|
|
9
9
|
import { CliUsageError, RuntimeApiError, printData, runtimeBaseUrl, runtimeRequest, summaryLines,
|
|
10
10
|
takeFlag, takeOptionWithSyntax, type RuntimeApiDeps } from "./runtime-api";
|
|
11
11
|
|
|
12
|
-
type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants";
|
|
12
|
+
type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining";
|
|
13
13
|
const USAGE = "Usage: ocx account pool <provider> [--enabled on|off] [--threshold 0-100] [--strategy NAME] [--sticky 1-100] [--quota-window NAME] [--json]\n"
|
|
14
14
|
+ " ocx account auto-switch openai <status|on|off|inherit|threshold N> --account ID [--json]\n"
|
|
15
15
|
+ " ocx account credits openai <ID on|off|--all on|off> [--json]\n"
|
|
16
|
+
+ " ocx account use-remaining openai ID <on|off|status> [--json]\n"
|
|
16
17
|
+ " ocx account quota-activation openai ID --window <fiveHour|weekly> <on|off> [--json]\n"
|
|
17
18
|
+ " ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]";
|
|
18
19
|
const READ_ERRORS = { no_account: "No matching Anthropic OAuth account.", auth_failed: "Sign in to this Anthropic account again.",
|
|
@@ -53,6 +54,32 @@ function output(data: Record<string, unknown>, json: boolean, note?: string): nu
|
|
|
53
54
|
}
|
|
54
55
|
function requireEqual(actual: unknown, expected: unknown): void { if (actual !== expected) throw new Error("Unverified account policy result"); }
|
|
55
56
|
|
|
57
|
+
async function useRemaining(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
|
|
58
|
+
if (args.shift() !== "openai") usage();
|
|
59
|
+
const requested = selector(args.shift()), action = args.shift();
|
|
60
|
+
if (!["on", "off", "status"].includes(action ?? "")) usage();
|
|
61
|
+
done(args);
|
|
62
|
+
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
63
|
+
const { id } = await target(pinned, requested);
|
|
64
|
+
const path = "/api/codex-auth/accounts/use-remaining";
|
|
65
|
+
const result = parseDto(remainingQuotaReceipt, await request(
|
|
66
|
+
action === "status" ? path + "?id=" + encodeURIComponent(id) : path,
|
|
67
|
+
pinned, action === "status" ? undefined : { id, useRemainingQuota: action === "on" },
|
|
68
|
+
));
|
|
69
|
+
requireEqual(result.id, id);
|
|
70
|
+
if (action !== "status") requireEqual(result.useRemainingQuota, action === "on");
|
|
71
|
+
if (result.useRemainingQuota) {
|
|
72
|
+
requireEqual(result.autoSwitchThreshold, 0);
|
|
73
|
+
if (id === MAIN_CODEX_ACCOUNT_ID) {
|
|
74
|
+
requireEqual(result.mainAccountHardLock?.thresholds.short, 100);
|
|
75
|
+
requireEqual(result.mainAccountHardLock?.thresholds.long, 100);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return output(result, json, result.useRemainingQuota
|
|
79
|
+
? "Use remaining included quota: on. Paid-credit permission is unchanged."
|
|
80
|
+
: "Use remaining included quota: off. Configured usage thresholds apply.");
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
|
|
57
84
|
const provider = selector(args.shift()).toLowerCase();
|
|
58
85
|
if (!isValidProviderName(provider)) usage();
|
|
@@ -63,7 +90,8 @@ async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promis
|
|
|
63
90
|
if (limit !== undefined) patch.autoSwitchThreshold = integer(limit, 0);
|
|
64
91
|
if (sticky !== undefined) patch.stickyLimit = integer(sticky, 1);
|
|
65
92
|
if (strategy !== undefined) {
|
|
66
|
-
|
|
93
|
+
// reset-first is a Codex and generic-pool strategy; the Anthropic pool does not run it.
|
|
94
|
+
if (!["quota", "round-robin", "fill-first", ...(isAnthropicInstanceId(provider) ? [] : ["reset-first"]),
|
|
67
95
|
...(provider === "kiro" ? ["least-loaded"] : [])].includes(strategy)) usage();
|
|
68
96
|
patch.strategy = strategy;
|
|
69
97
|
}
|
|
@@ -122,12 +150,12 @@ async function codex(sub: "auto-switch" | "credits" | "quota-activation", args:
|
|
|
122
150
|
const override = selected?.autoSwitchThresholdOverride;
|
|
123
151
|
if (override === undefined) throw new Error("Missing account threshold evidence");
|
|
124
152
|
const active = record(await request("/api/codex-auth/active", pinned));
|
|
125
|
-
const effective = override ?? parseDto(threshold, active.autoSwitchThreshold);
|
|
153
|
+
const effective = selected?.useRemainingQuota ? 0 : override ?? parseDto(threshold, active.autoSwitchThreshold);
|
|
126
154
|
return output({ ok: true, id, autoSwitchThresholdOverride: override, autoSwitchThreshold: effective }, json);
|
|
127
155
|
}
|
|
128
156
|
const result = parseDto(thresholdReceipt, await request("/api/codex-auth/auto-switch", pinned, { id, threshold: value }));
|
|
129
157
|
requireEqual(result.id, id); requireEqual(result.autoSwitchThresholdOverride, value);
|
|
130
|
-
if (value !== null) requireEqual(result.autoSwitchThreshold, value);
|
|
158
|
+
if (value !== null && !rows.find(row => row.id === id)?.useRemainingQuota) requireEqual(result.autoSwitchThreshold, value);
|
|
131
159
|
return output(result, json, "Account threshold saved.");
|
|
132
160
|
}
|
|
133
161
|
if (sub === "credits") {
|
|
@@ -157,6 +185,7 @@ export async function handleAccountPolicyCommand(sub: Subcommand, args: string[]
|
|
|
157
185
|
const rest = [...args], json = takeFlag(rest, "--json");
|
|
158
186
|
if (rest.includes("--json")) usage();
|
|
159
187
|
if (sub === "pool") return pool(rest, json, deps);
|
|
188
|
+
if (sub === "use-remaining") return useRemaining(rest, json, deps);
|
|
160
189
|
if (sub !== "anthropic-reset-grants") return codex(sub, rest, json, deps);
|
|
161
190
|
const selectedProvider = option(rest, "--provider");
|
|
162
191
|
const provider = selectedProvider ?? "anthropic";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { apiError, apiJson, proxyUnreachable, resolveBaseUrl, type AccountDeps } from "./account-api";
|
|
2
|
+
|
|
3
|
+
/** This operation follows the selected runtime; never read a local account/config roster. */
|
|
4
|
+
export async function cmdPreserveSignedIn(args: string[], deps: AccountDeps): Promise<number> {
|
|
5
|
+
const provider = args.shift();
|
|
6
|
+
let mode = "status";
|
|
7
|
+
let hasMode = false;
|
|
8
|
+
let json = false;
|
|
9
|
+
let applyToBound = false;
|
|
10
|
+
for (const arg of args) {
|
|
11
|
+
if (arg === "--json" && !json) json = true;
|
|
12
|
+
else if (arg === "--apply-to-bound" && !applyToBound) applyToBound = true;
|
|
13
|
+
else if (["on", "off", "status"].includes(arg) && !hasMode) { mode = arg; hasMode = true; }
|
|
14
|
+
else return usage();
|
|
15
|
+
}
|
|
16
|
+
if (provider !== "openai" || (applyToBound && mode !== "on")) return usage();
|
|
17
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
18
|
+
if (!baseUrl) return proxyUnreachable();
|
|
19
|
+
const result = await apiJson(deps, baseUrl, mode === "status" ? "GET" : "PUT", "/api/codex-auth/preserve-signed-in",
|
|
20
|
+
mode === "status" ? undefined : { enabled: mode === "on", applyToBound });
|
|
21
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
22
|
+
if (result.status !== 200) return apiError(result.json, "Signed-in quota policy unavailable", result.status);
|
|
23
|
+
if (typeof result.json.enabled !== "boolean" || !Array.isArray(result.json.affectedAccountIds)
|
|
24
|
+
|| !result.json.affectedAccountIds.every(id => typeof id === "string")
|
|
25
|
+
|| (result.json.nextAccountId !== null && typeof result.json.nextAccountId !== "string")) {
|
|
26
|
+
return apiError({}, "Invalid signed-in quota policy response", 502);
|
|
27
|
+
}
|
|
28
|
+
if (json) console.log(JSON.stringify(result.json, null, 2));
|
|
29
|
+
else {
|
|
30
|
+
console.log(`Preserve signed-in quota: ${result.json.enabled ? "on" : "off"}`);
|
|
31
|
+
console.log(`Affected pool entries: ${result.json.affectedAccountIds.join(", ")}`);
|
|
32
|
+
console.log(`Next unbound choice: ${result.json.nextAccountId ?? "none"}`);
|
|
33
|
+
console.log(`Bindings: ${result.json.bindings}; manual pin: ${result.json.pinnedAccountId ?? "none"}`);
|
|
34
|
+
if (result.json.priorityFailbackEnabled === true) console.log("Existing priority failback can move bound work when its quota and freshness conditions hold.");
|
|
35
|
+
console.log("Fallback can consume signed-in quota. Exact-account models override this preference.");
|
|
36
|
+
console.log("Use on --apply-to-bound to release affected bindings and pins for the next request.");
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function usage(): number {
|
|
42
|
+
console.error("Usage: ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]");
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
package/src/cli/account.ts
CHANGED
|
@@ -54,6 +54,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
54
54
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
55
55
|
ocx account auto-switch <anthropic|anthropic2> <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
56
56
|
ocx account alias <provider> <account-or-key-id|alias> <display-name|-> [--json]
|
|
57
|
+
ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]
|
|
57
58
|
ocx account priority <provider> <account-id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
58
59
|
ocx account pause <provider> <account-id|alias|main> [--json]
|
|
59
60
|
ocx account resume <provider> <account-id|alias|main> [--json]
|
|
@@ -69,6 +70,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
69
70
|
ocx account login <provider> [--id <account-id>] [--reauth] [--open-browser on|off] [--add-account on|off] [--code -] [--no-wait] [--json]
|
|
70
71
|
ocx account pool <provider> [--enabled on|off] [--threshold N] [--strategy NAME] [--sticky N] [--quota-window W] [--json]
|
|
71
72
|
ocx account credits openai <ID on|off|--all on|off> [--json]
|
|
73
|
+
ocx account use-remaining openai <ID|main> <on|off|status> [--json]
|
|
72
74
|
ocx account quota-activation openai ID --window fiveHour|weekly <on|off> [--json]
|
|
73
75
|
ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]
|
|
74
76
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
@@ -459,11 +461,11 @@ async function cmdClear(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
459
461
|
export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promise<number> {
|
|
460
462
|
const [sub, ...rest] = args;
|
|
461
463
|
try {
|
|
462
|
-
if (["pool", "credits", "quota-activation", "anthropic-reset-grants"].includes(sub ?? "")
|
|
464
|
+
if (["pool", "credits", "quota-activation", "anthropic-reset-grants", "use-remaining"].includes(sub ?? "")
|
|
463
465
|
|| (sub === "auto-switch" && rest[0]?.trim().toLowerCase() === "openai"
|
|
464
466
|
&& rest.some(arg => arg === "--account" || arg.startsWith("--account=")))) {
|
|
465
467
|
const { handleAccountPolicyCommand } = await import("./account-policy");
|
|
466
|
-
return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants", rest, deps);
|
|
468
|
+
return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining", rest, deps);
|
|
467
469
|
}
|
|
468
470
|
if (sub === "list") return await cmdList(rest, deps);
|
|
469
471
|
if (sub === "history") {
|
|
@@ -476,6 +478,10 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
476
478
|
if (sub === "refresh") return await cmdRefresh(rest, deps);
|
|
477
479
|
if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
|
|
478
480
|
if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
|
|
481
|
+
if (sub === "preserve-signed-in") {
|
|
482
|
+
const { cmdPreserveSignedIn } = await import("./account-preservation");
|
|
483
|
+
return cmdPreserveSignedIn(rest, deps);
|
|
484
|
+
}
|
|
479
485
|
if (sub === "priority") return await cmdPriority(rest, deps);
|
|
480
486
|
// #2702: the server routes existed and only the CLI caller was missing, so these were
|
|
481
487
|
// dashboard-only capabilities.
|