@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
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Rules, in order:
|
|
12
12
|
* - A running Desktop app is the supervisor. Wait for its proxy; never start a competing one.
|
|
13
|
-
* - A durable Desktop claim with no running app: start Desktop (hidden,
|
|
14
|
-
*
|
|
13
|
+
* - A durable Desktop claim with no running app: start Desktop (hidden, by the path its own
|
|
14
|
+
* login registration recorded — LaunchServices never picks the bundle) where that is
|
|
15
|
+
* verifiable, then wait. Elsewhere, fail with guidance. A missing Desktop process
|
|
15
16
|
* never authorizes a CLI proxy by itself; handing ownership to the CLI stays an explicit
|
|
16
17
|
* `ocx service install` consent step.
|
|
17
18
|
* - Ownership or the process list that cannot be read is not evidence of absence: refuse.
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
*/
|
|
23
24
|
import { execFileSync } from "node:child_process";
|
|
24
25
|
import { randomUUID } from "node:crypto";
|
|
25
|
-
import { existsSync, linkSync, readdirSync, readFileSync, readlinkSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { accessSync, constants, existsSync, linkSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
26
27
|
import { homedir } from "node:os";
|
|
27
|
-
import { basename, dirname, join, win32 } from "node:path";
|
|
28
|
+
import { basename, dirname, join, posix, win32 } from "node:path";
|
|
28
29
|
import { getConfigDir } from "../config/paths";
|
|
29
30
|
import { resolveServiceOwnership } from "../service/state";
|
|
30
31
|
|
|
@@ -220,11 +221,63 @@ export function inspectDesktopProcess(options: DesktopProcessOptions = {}): Desk
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
const DESKTOP_BUNDLE_ID = "com.opencodex.desktop";
|
|
225
|
+
const DESKTOP_PLIST_LABEL = "OpenCodex";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The app bundle the consented installation itself registered as its Start-at-Login program —
|
|
229
|
+
* not whichever bundle LaunchServices would resolve for our identifier. `open -b` answers a
|
|
230
|
+
* mutable per-user registry: any planted bundle claiming {@link DESKTOP_BUNDLE_ID} could be
|
|
231
|
+
* selected and executed in the real app's place. The recorded claim, this app's own install-id
|
|
232
|
+
* store, and the login registration it wrote are the durable evidence of which installation
|
|
233
|
+
* that is; a claim whose registration is absent, malformed, stale or points off a Desktop
|
|
234
|
+
* executable refuses the launch instead of guessing.
|
|
235
|
+
*/
|
|
236
|
+
function consentedDesktopBundle(home: string, installId: string, run: Execute): string | null {
|
|
237
|
+
const localId = readFileSync(
|
|
238
|
+
join(home, "Library", "Application Support", DESKTOP_BUNDLE_ID, "install-id"), "utf8",
|
|
239
|
+
).trim();
|
|
240
|
+
if (!localId || localId !== installId) return null;
|
|
241
|
+
const plistPath = realpathSync(join(home, "Library", "LaunchAgents", `${DESKTOP_PLIST_LABEL}.plist`));
|
|
242
|
+
const registration = JSON.parse(
|
|
243
|
+
run("/usr/bin/plutil", ["-convert", "json", "-o", "-", plistPath], 3_000),
|
|
244
|
+
) as { Label?: unknown; ProgramArguments?: unknown; Program?: unknown };
|
|
245
|
+
const args = registration.ProgramArguments;
|
|
246
|
+
// The shapes auto-launch writes: [program] or [program, "--autostart"]; see the migration
|
|
247
|
+
// rules in desktop/src-tauri/src/login_registration.rs, which classify anything else Leave.
|
|
248
|
+
// This is a macOS plist path, regardless of the host running an injected launch probe.
|
|
249
|
+
if (registration.Label !== DESKTOP_PLIST_LABEL || !Array.isArray(args)
|
|
250
|
+
|| (args.length !== 1 && args.length !== 2) || typeof args[0] !== "string" || !posix.isAbsolute(args[0])
|
|
251
|
+
|| (args.length === 2 && args[1] !== "--autostart")
|
|
252
|
+
|| (registration.Program !== undefined && registration.Program !== args[0])) return null;
|
|
253
|
+
const executable = realpathSync(args[0]);
|
|
254
|
+
if (!isDesktopExecutable(executable, "darwin") || !statSync(executable).isFile()) return null;
|
|
255
|
+
accessSync(executable, constants.X_OK);
|
|
256
|
+
const bundle = dirname(dirname(dirname(executable)));
|
|
257
|
+
if (!bundle.endsWith(".app")) return null;
|
|
258
|
+
const info = JSON.parse(
|
|
259
|
+
run("/usr/bin/plutil", ["-convert", "json", "-o", "-", join(bundle, "Contents", "Info.plist")], 3_000),
|
|
260
|
+
) as { CFBundleIdentifier?: unknown; CFBundleExecutable?: unknown };
|
|
261
|
+
// `open` uses this selector, so checking only the registration and identifier would
|
|
262
|
+
// validate one binary while launching another. The registered path is already canonical.
|
|
263
|
+
if (info.CFBundleIdentifier !== DESKTOP_BUNDLE_ID || info.CFBundleExecutable !== "opencodex-desktop") return null;
|
|
264
|
+
return realpathSync(join(bundle, "Contents", "MacOS", info.CFBundleExecutable)) === executable ? bundle : null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Start the consented macOS app hidden, by the path its own login registration recorded. */
|
|
268
|
+
export function launchDesktopApp(options: {
|
|
269
|
+
platform?: NodeJS.Platform;
|
|
270
|
+
run?: Execute;
|
|
271
|
+
home?: string;
|
|
272
|
+
ownership?: () => ReturnType<typeof resolveServiceOwnership>;
|
|
273
|
+
} = {}): boolean {
|
|
225
274
|
if ((options.platform ?? process.platform) !== "darwin") return false;
|
|
226
275
|
try {
|
|
227
|
-
(options.
|
|
276
|
+
const owner = (options.ownership ?? resolveServiceOwnership)();
|
|
277
|
+
if (owner.kind !== "owned" || owner.ownership.owner !== "desktop") return false;
|
|
278
|
+
const bundle = consentedDesktopBundle(options.home ?? homedir(), owner.ownership.installId, options.run ?? execute);
|
|
279
|
+
if (bundle === null) return false;
|
|
280
|
+
(options.run ?? execute)("/usr/bin/open", ["-g", bundle, "--args", "--autostart"], 10_000);
|
|
228
281
|
return true;
|
|
229
282
|
} catch { return false; }
|
|
230
283
|
}
|
package/src/cli/help.ts
CHANGED
|
@@ -85,14 +85,14 @@ Usage:
|
|
|
85
85
|
ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
|
|
86
86
|
ocx route <sub> Routing features (combo, policy)
|
|
87
87
|
ocx logs [filters] Alias of ocx observe logs
|
|
88
|
-
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
|
|
88
|
+
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>] [--top <1-1000>]
|
|
89
89
|
Token and estimated-cost report (alias of ocx observe usage)
|
|
90
90
|
ocx storage <sub> Storage report, cleanup, trash, and the cleanup policy
|
|
91
91
|
ocx memory [--json] Alias of ocx observe memory
|
|
92
92
|
ocx api-key <sub> Alias of ocx access key
|
|
93
93
|
ocx access <sub> External API keys and endpoint information
|
|
94
94
|
ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
|
|
95
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
95
|
+
ocx export --client <id> Print a client config wired to the running proxy (20 clients)
|
|
96
96
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
97
97
|
ocx grok <sub> Grok Build model selection and apply
|
|
98
98
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
@@ -100,6 +100,7 @@ Usage:
|
|
|
100
100
|
ocx advisor <status|on|off|set|consent> Advisor sidecar settings and context-sharing consent
|
|
101
101
|
ocx companion <show|set|reset> Menu-bar and widget companion usage settings
|
|
102
102
|
ocx lab <sub> Inspect Lab evidence and control local automation
|
|
103
|
+
ocx codex-provider <sub> Confirm custom Codex provider routing: status|allow|revoke
|
|
103
104
|
ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
|
|
104
105
|
ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
|
|
105
106
|
ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
|
package/src/cli/index.ts
CHANGED
|
@@ -660,7 +660,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
660
660
|
}
|
|
661
661
|
removePid(process.pid);
|
|
662
662
|
removeRuntimePort(process.pid);
|
|
663
|
-
if (teardown.restoreNativeCodex
|
|
663
|
+
if (teardown.restoreNativeCodex) {
|
|
664
664
|
try {
|
|
665
665
|
if (!codexHomeIsAbsent(getCodexHome())) {
|
|
666
666
|
const restored = restoreNativeCodex();
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indexed request-history reads: page the derived ledger, or fetch one canonical row.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `ocx logs` (the recent ring) and `ocx logs explain` (route-decision).
|
|
5
|
+
*/
|
|
6
|
+
import { REQUEST_HISTORY_DEFAULT_PAGE_SIZE, REQUEST_HISTORY_MAX_PAGE_SIZE } from "../routing/history/indexer";
|
|
7
|
+
import { parseUsageTimeWindow } from "../usage/time-range";
|
|
8
|
+
import {
|
|
9
|
+
CliUsageError,
|
|
10
|
+
printData,
|
|
11
|
+
rejectArgs,
|
|
12
|
+
runtimeRequest,
|
|
13
|
+
takeFlag,
|
|
14
|
+
takeIntegerOption,
|
|
15
|
+
takeOption,
|
|
16
|
+
type RuntimeApiDeps,
|
|
17
|
+
} from "./runtime-api";
|
|
18
|
+
|
|
19
|
+
type LogRow = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export const LOG_HISTORY_USAGE = `Usage:
|
|
22
|
+
ocx logs history [--provider <name>] [--model <id>] [--requested-model <id>]
|
|
23
|
+
[--status <code>] [--conversation <id>] [--surface <name>] [--protocol <responses|chat|messages>]
|
|
24
|
+
[--api-key-id <id>] [--profile <id>] [--fallback <true|false>]
|
|
25
|
+
[--from <epoch-ms|ISO-datetime>] [--to <epoch-ms|ISO-datetime>]
|
|
26
|
+
[--cursor <token>] [--limit <1-${REQUEST_HISTORY_MAX_PAGE_SIZE}>] [--json|--jsonl]
|
|
27
|
+
ocx logs get <request-id> [--json]`;
|
|
28
|
+
|
|
29
|
+
const GET_USAGE = "Usage: ocx logs get <request-id> [--json]";
|
|
30
|
+
const PROTOCOLS = ["responses", "chat", "messages"] as const;
|
|
31
|
+
|
|
32
|
+
function query(params: Record<string, string | number | boolean | undefined>): string {
|
|
33
|
+
const search = new URLSearchParams();
|
|
34
|
+
for (const [key, value] of Object.entries(params)) {
|
|
35
|
+
if (value !== undefined) search.set(key, String(value));
|
|
36
|
+
}
|
|
37
|
+
const encoded = search.toString();
|
|
38
|
+
return encoded ? `?${encoded}` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function timestampOption(args: string[], flag: string): number | undefined {
|
|
42
|
+
const raw = takeOption(args, flag);
|
|
43
|
+
if (raw === undefined) return undefined;
|
|
44
|
+
try {
|
|
45
|
+
return parseUsageTimeWindow(raw, raw)!.since;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new CliUsageError(error instanceof Error ? `${flag}: ${error.message}` : `${flag} is invalid`, LOG_HISTORY_USAGE);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
52
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatCost(row: Record<string, unknown>): string {
|
|
56
|
+
const metrics = record(row.displayMetrics) ? row.displayMetrics : undefined;
|
|
57
|
+
const cost = metrics && record(metrics.cost) ? metrics.cost : undefined;
|
|
58
|
+
if (!cost || cost.kind !== "value" || !record(cost.estimate) || !record(cost.estimate.cost)) return "";
|
|
59
|
+
const total = cost.estimate.cost.total;
|
|
60
|
+
return typeof total === "number" && Number.isFinite(total) ? `~$${total.toFixed(4)}` : "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatHistoryRow(row: LogRow, formatRow: (row: LogRow) => string): string {
|
|
64
|
+
const tokens = typeof row.totalTokens === "number" && Number.isFinite(row.totalTokens)
|
|
65
|
+
? `${row.totalTokens}tok`
|
|
66
|
+
: "";
|
|
67
|
+
return [formatRow(row), tokens, formatCost(row)].filter(Boolean).join(" ");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function historyPage(raw: unknown): {
|
|
71
|
+
entries: Record<string, unknown>[];
|
|
72
|
+
nextCursor?: string;
|
|
73
|
+
hasMore: boolean;
|
|
74
|
+
index?: Record<string, unknown>;
|
|
75
|
+
} {
|
|
76
|
+
if (!record(raw) || !Array.isArray(raw.entries) || typeof raw.hasMore !== "boolean") {
|
|
77
|
+
throw new Error("Invalid request-history page. Upgrade the proxy and retry.");
|
|
78
|
+
}
|
|
79
|
+
const entries = raw.entries;
|
|
80
|
+
if (!entries.every(record)) throw new Error("Invalid request-history page. Upgrade the proxy and retry.");
|
|
81
|
+
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor.length > 0 ? raw.nextCursor : undefined;
|
|
82
|
+
const index = record(raw.index) ? raw.index : undefined;
|
|
83
|
+
return { entries, nextCursor, hasMore: raw.hasMore, index };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Page the derived request-history index through GET /api/request-history. */
|
|
87
|
+
export async function handleLogHistoryCommand(
|
|
88
|
+
argv: string[],
|
|
89
|
+
deps: RuntimeApiDeps,
|
|
90
|
+
formatRow: (row: LogRow) => string,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const args = [...argv];
|
|
93
|
+
const wantsJson = takeFlag(args, "--json");
|
|
94
|
+
const wantsJsonl = takeFlag(args, "--jsonl");
|
|
95
|
+
const provider = takeOption(args, "--provider");
|
|
96
|
+
const model = takeOption(args, "--model");
|
|
97
|
+
const requestedModel = takeOption(args, "--requested-model");
|
|
98
|
+
const conversationId = takeOption(args, "--conversation") ?? takeOption(args, "--conversationId");
|
|
99
|
+
const surface = takeOption(args, "--surface");
|
|
100
|
+
const protocol = takeOption(args, "--protocol");
|
|
101
|
+
const apiKeyId = takeOption(args, "--api-key-id");
|
|
102
|
+
const profileId = takeOption(args, "--profile");
|
|
103
|
+
const fallbackRaw = takeOption(args, "--fallback");
|
|
104
|
+
const cursor = takeOption(args, "--cursor");
|
|
105
|
+
const status = takeIntegerOption(args, "--status", { min: 100 });
|
|
106
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? REQUEST_HISTORY_DEFAULT_PAGE_SIZE;
|
|
107
|
+
const from = timestampOption(args, "--from");
|
|
108
|
+
const to = timestampOption(args, "--to");
|
|
109
|
+
rejectArgs(args, LOG_HISTORY_USAGE);
|
|
110
|
+
if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", LOG_HISTORY_USAGE);
|
|
111
|
+
if (status !== undefined && status > 599) throw new CliUsageError("--status must be an integer from 100 to 599", LOG_HISTORY_USAGE);
|
|
112
|
+
if (limit > REQUEST_HISTORY_MAX_PAGE_SIZE) {
|
|
113
|
+
throw new CliUsageError(`--limit must be between 1 and ${REQUEST_HISTORY_MAX_PAGE_SIZE}`, LOG_HISTORY_USAGE);
|
|
114
|
+
}
|
|
115
|
+
if (protocol !== undefined && !PROTOCOLS.includes(protocol as (typeof PROTOCOLS)[number])) {
|
|
116
|
+
throw new CliUsageError(`--protocol must be one of ${PROTOCOLS.join(", ")}`, LOG_HISTORY_USAGE);
|
|
117
|
+
}
|
|
118
|
+
let fallback: boolean | undefined;
|
|
119
|
+
if (fallbackRaw !== undefined) {
|
|
120
|
+
if (fallbackRaw !== "true" && fallbackRaw !== "false") {
|
|
121
|
+
throw new CliUsageError("--fallback must be true or false", LOG_HISTORY_USAGE);
|
|
122
|
+
}
|
|
123
|
+
fallback = fallbackRaw === "true";
|
|
124
|
+
}
|
|
125
|
+
if (from !== undefined && to !== undefined && from > to) {
|
|
126
|
+
throw new CliUsageError("--from must not be after --to", LOG_HISTORY_USAGE);
|
|
127
|
+
}
|
|
128
|
+
const data = await runtimeRequest(`/api/request-history${query({
|
|
129
|
+
provider, model, requestedModel, status, conversationId, surface,
|
|
130
|
+
inboundProtocol: protocol, apiKeyId, profileId, fallback, from, to, cursor, limit,
|
|
131
|
+
})}`, {}, deps);
|
|
132
|
+
const page = historyPage(data);
|
|
133
|
+
if (wantsJson) {
|
|
134
|
+
printData(data, true);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (wantsJsonl) {
|
|
138
|
+
for (const row of page.entries) console.log(JSON.stringify(row));
|
|
139
|
+
if (page.hasMore && page.nextCursor) console.error(`nextCursor=${page.nextCursor}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const indexed = typeof page.index?.indexedRows === "number" ? page.index.indexedRows : undefined;
|
|
143
|
+
if (page.entries.length === 0) {
|
|
144
|
+
printData(data, false, [
|
|
145
|
+
"No request-history row matched the filters.",
|
|
146
|
+
...(indexed !== undefined ? [`indexedRows=${indexed}`] : []),
|
|
147
|
+
]);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const footer = [
|
|
151
|
+
...(indexed !== undefined ? [`indexedRows=${indexed}`] : []),
|
|
152
|
+
`hasMore=${page.hasMore}`,
|
|
153
|
+
...(page.nextCursor ? [`nextCursor=${page.nextCursor}`] : []),
|
|
154
|
+
];
|
|
155
|
+
printData(data, false, [...page.entries.map(row => formatHistoryRow(row, formatRow)), ...footer]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Read one canonical request-history row through GET /api/request-history/{id}. */
|
|
159
|
+
export async function handleLogGetCommand(
|
|
160
|
+
argv: string[],
|
|
161
|
+
deps: RuntimeApiDeps,
|
|
162
|
+
formatRow: (row: LogRow) => string,
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const args = [...argv];
|
|
165
|
+
const requestId = args.shift();
|
|
166
|
+
const wantsJson = takeFlag(args, "--json");
|
|
167
|
+
if (!requestId) throw new CliUsageError("request id is required", GET_USAGE);
|
|
168
|
+
rejectArgs(args, GET_USAGE);
|
|
169
|
+
const result = await runtimeRequest(`/api/request-history/${encodeURIComponent(requestId)}`, {}, deps);
|
|
170
|
+
if (!record(result)) throw new Error("Invalid request-history row. Upgrade the proxy and retry.");
|
|
171
|
+
printData(result, wantsJson, wantsJson ? undefined : [formatHistoryRow(result, formatRow)]);
|
|
172
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A failed connect cannot distinguish an absent loopback address from a slow owner. */
|
|
2
|
+
export type LoopbackFamilyAvailability = "available" | "unavailable" | "unknown";
|
|
3
|
+
|
|
4
|
+
function bindLoopback(hostname: string): { stop(): void } {
|
|
5
|
+
return Bun.listen({ hostname, port: 0, socket: { data(socket) { socket.end(); } } });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ask the kernel whether the concrete local address can be bound, independently of
|
|
10
|
+
* routing/connect timeouts. Only address/family absence permits skipping discovery;
|
|
11
|
+
* permissions, resource exhaustion and other failures leave owner evidence unknown.
|
|
12
|
+
* The ephemeral listener is closed synchronously and never accepts client traffic.
|
|
13
|
+
*/
|
|
14
|
+
export function loopbackFamilyAvailability(
|
|
15
|
+
hostname: string,
|
|
16
|
+
bindFn: (hostname: string) => { stop(): void } = bindLoopback,
|
|
17
|
+
): LoopbackFamilyAvailability {
|
|
18
|
+
const address = hostname === "[::1]" ? "::1" : hostname;
|
|
19
|
+
if (address !== "::1" && address !== "127.0.0.1") return "unknown";
|
|
20
|
+
let listener: { stop(): void };
|
|
21
|
+
try {
|
|
22
|
+
listener = bindFn(address);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const code = (error as { code?: unknown } | null)?.code;
|
|
25
|
+
return code === "EADDRNOTAVAIL" || code === "EAFNOSUPPORT"
|
|
26
|
+
|| code === "EPROTONOSUPPORT"
|
|
27
|
+
? "unavailable" : "unknown";
|
|
28
|
+
}
|
|
29
|
+
listener.stop();
|
|
30
|
+
return "available";
|
|
31
|
+
}
|
|
@@ -21,7 +21,7 @@ import { redactSecretString } from "../lib/redact";
|
|
|
21
21
|
import type { ProviderCostOverlay } from "../types";
|
|
22
22
|
import { resolveMatchedPrice } from "../usage/cost";
|
|
23
23
|
import { MAX_COST4_RATE } from "../usage/expected-prices";
|
|
24
|
-
import { isValidCost4Rate } from "../usage/user-cost-overlays";
|
|
24
|
+
import { isValidCost4Rate, validCost4 } from "../usage/user-cost-overlays";
|
|
25
25
|
|
|
26
26
|
const USAGE = `Usage:
|
|
27
27
|
ocx models display-name <provider/raw-model> (--set <text> | --clear) [--json]
|
|
@@ -48,6 +48,7 @@ const USAGE = `Usage:
|
|
|
48
48
|
ocx models new-arrivals [--json]
|
|
49
49
|
${MODELS_CONTEXT_USAGE}
|
|
50
50
|
ocx models shadow <status|set> [model|-] [--enabled <on|off>] [--json]
|
|
51
|
+
ocx models auto-review <status|set> [model|-] [--enabled <on|off>] [--json]
|
|
51
52
|
|
|
52
53
|
Prices are USD per 1M tokens. Omitted cache rates default to 0.
|
|
53
54
|
Price selectors use the exact upstream model ID after the first slash.`;
|
|
@@ -89,8 +90,8 @@ function priceRecord(value: unknown): value is Record<string, unknown> {
|
|
|
89
90
|
const PRICE_RATE_KEYS = ["input", "output", "cacheRead", "cacheWrite"] as const;
|
|
90
91
|
|
|
91
92
|
function validPriceCost(value: unknown): value is ProviderCostOverlay {
|
|
92
|
-
return priceRecord(value) &&
|
|
93
|
-
&&
|
|
93
|
+
return priceRecord(value) && validCost4(value)
|
|
94
|
+
&& Object.keys(value).every(key => key === "promptLengthPricing" || (PRICE_RATE_KEYS as readonly string[]).includes(key));
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
async function price(write: boolean, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -135,7 +136,9 @@ async function priceRequest(write: boolean, argv: string[], deps: RuntimeApiDeps
|
|
|
135
136
|
}
|
|
136
137
|
// The API map owns manual overrides; bundled defaults remain derived rather
|
|
137
138
|
// than being persisted as overrides that would mask later catalog updates.
|
|
138
|
-
const effectiveCost = cost
|
|
139
|
+
const effectiveCost = cost
|
|
140
|
+
? { input: cost.input, output: cost.output, cacheRead: cost.cacheRead, cacheWrite: cost.cacheWrite }
|
|
141
|
+
: resolveMatchedPrice(provider, modelId, undefined, [])?.cost4 ?? null;
|
|
139
142
|
printData({ provider, modelId, cost, effectiveCost }, wantsJson, [
|
|
140
143
|
effectiveCost === null ? `${selector}: automatic pricing (unknown)`
|
|
141
144
|
: `${selector}: ${JSON.stringify(effectiveCost)} USD per 1M tokens${cost === null ? " (automatic estimate)" : ""}`,
|
|
@@ -161,17 +164,42 @@ async function priceRequest(write: boolean, argv: string[], deps: RuntimeApiDeps
|
|
|
161
164
|
}
|
|
162
165
|
return value;
|
|
163
166
|
};
|
|
164
|
-
|
|
167
|
+
// Validate every rate before any request, so a bad flag never reaches the management plane.
|
|
168
|
+
const rates: ProviderCostOverlay | undefined = auto ? undefined : {
|
|
165
169
|
input: rate(input!, "--input"),
|
|
166
170
|
output: rate(output!, "--output"),
|
|
167
171
|
cacheRead: rate(cacheRead ?? "0", "--cache-read"),
|
|
168
172
|
cacheWrite: rate(cacheWrite ?? "0", "--cache-write"),
|
|
169
173
|
};
|
|
174
|
+
// A base-rate write replaces the whole row, so read the current row first and carry the
|
|
175
|
+
// stored prompt-length policy for exactly this model forward instead of silently deleting it.
|
|
176
|
+
let carriedPromptLengthPricing: ProviderCostOverlay["promptLengthPricing"];
|
|
177
|
+
if (rates) {
|
|
178
|
+
const current = await runtimeRequest<unknown>(path, {}, deps);
|
|
179
|
+
if (!priceRecord(current) || current.provider !== provider || !priceRecord(current.modelCosts)
|
|
180
|
+
|| !Object.values(current.modelCosts).every(validPriceCost)) {
|
|
181
|
+
throw new Error("Invalid model price response");
|
|
182
|
+
}
|
|
183
|
+
if (Object.hasOwn(current.modelCosts, modelId)) {
|
|
184
|
+
carriedPromptLengthPricing = (current.modelCosts[modelId] as ProviderCostOverlay).promptLengthPricing;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const cost: ProviderCostOverlay | null = rates
|
|
188
|
+
? { ...rates, ...(carriedPromptLengthPricing ? { promptLengthPricing: carriedPromptLengthPricing } : {}) }
|
|
189
|
+
: null;
|
|
170
190
|
const result = await runtimeRequest(path, { method: "PUT", body: JSON.stringify({ modelId, cost }) }, deps);
|
|
191
|
+
// Absent and { mode: "automatic" } are the same policy (the server stores automatic as
|
|
192
|
+
// absent), and key order carries no meaning in a JSON receipt, so compare canonically.
|
|
193
|
+
const canonicalPolicy = (policy: ProviderCostOverlay["promptLengthPricing"]): string =>
|
|
194
|
+
policy === undefined || policy.mode === "automatic" ? `{"mode":"automatic"}`
|
|
195
|
+
: JSON.stringify(policy, (_key, item: unknown) => (item && typeof item === "object" && !Array.isArray(item)
|
|
196
|
+
? Object.fromEntries(Object.entries(item as Record<string, unknown>).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
197
|
+
: item));
|
|
171
198
|
const receivedCost = priceRecord(result) ? result.cost : undefined;
|
|
172
199
|
if (!priceRecord(result) || result.ok !== true || result.provider !== provider || result.modelId !== modelId
|
|
173
200
|
|| (cost === null ? receivedCost !== null : !validPriceCost(receivedCost)
|
|
174
|
-
|| !PRICE_RATE_KEYS.every(key => receivedCost[key] === cost[key])
|
|
201
|
+
|| !PRICE_RATE_KEYS.every(key => receivedCost[key] === cost[key])
|
|
202
|
+
|| canonicalPolicy(receivedCost.promptLengthPricing) !== canonicalPolicy(cost.promptLengthPricing))) {
|
|
175
203
|
throw new Error("Invalid model price persistence receipt");
|
|
176
204
|
}
|
|
177
205
|
// Project the acknowledged fields only; unrelated response fields are not CLI output.
|
|
@@ -566,6 +594,39 @@ async function shadow(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
566
594
|
printData(result, wantsJson, ["Shadow-call settings updated."]);
|
|
567
595
|
}
|
|
568
596
|
|
|
597
|
+
async function autoReview(argv: string[], deps: RuntimeApiDeps): Promise<number | void> {
|
|
598
|
+
const args = [...argv];
|
|
599
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
600
|
+
const wantsJson = takeFlag(args, "--json");
|
|
601
|
+
if (action === "status") {
|
|
602
|
+
rejectArgs(args, USAGE);
|
|
603
|
+
const result = await runtimeRequest("/api/auto-review-settings", {}, deps);
|
|
604
|
+
printData(result, wantsJson);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (action !== "set") throw new CliUsageError("unknown auto-review action " + action, USAGE);
|
|
608
|
+
const modelRaw = args[0] && !args[0].startsWith("--") ? args.shift() : undefined;
|
|
609
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
610
|
+
rejectArgs(args, USAGE);
|
|
611
|
+
const body: Record<string, unknown> = {};
|
|
612
|
+
if (modelRaw !== undefined) body.model = modelRaw === "-" ? "" : modelRaw;
|
|
613
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
614
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("model and/or --enabled is required", USAGE);
|
|
615
|
+
const result = await runtimeRequest<{ catalogRefresh?: { status?: string; retryable?: boolean } }>(
|
|
616
|
+
"/api/auto-review-settings", { method: "PUT", body: JSON.stringify(body) }, deps,
|
|
617
|
+
);
|
|
618
|
+
const refresh = result.catalogRefresh;
|
|
619
|
+
printData(result, wantsJson, [
|
|
620
|
+
"Auto-review settings updated.",
|
|
621
|
+
...(refresh?.status === "failed"
|
|
622
|
+
? ["Settings saved, but the Codex model catalog refresh failed. Resolve the refresh issue before running ocx sync."]
|
|
623
|
+
: refresh?.status === "skipped" && refresh.retryable === true
|
|
624
|
+
? ["Settings saved, but the Codex model catalog refresh is pending. Run ocx sync to retry."]
|
|
625
|
+
: []),
|
|
626
|
+
]);
|
|
627
|
+
if (refresh?.status === "failed") return 1;
|
|
628
|
+
}
|
|
629
|
+
|
|
569
630
|
export async function handleModelsRuntimeCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
570
631
|
if (sub === "order" || sub === "display-name") {
|
|
571
632
|
const { handleModelsOrderCommand, handleModelsDisplayNameCommand } = await import("./models-order");
|
|
@@ -574,7 +635,7 @@ export async function handleModelsRuntimeCommand(sub: string, argv: string[], de
|
|
|
574
635
|
// The dispatch below and MODELS_RUNTIME_SUBCOMMANDS must name the same set;
|
|
575
636
|
// tests/cli/cli-models-runtime-dispatch.test.ts fails if they drift (#3094).
|
|
576
637
|
if (!isModelsRuntimeSubcommand(sub)) return null;
|
|
577
|
-
let action: (() => Promise<void>) | undefined;
|
|
638
|
+
let action: (() => Promise<number | void>) | undefined;
|
|
578
639
|
if (sub === "live") action = () => live(argv, deps);
|
|
579
640
|
else if (sub === "price") action = () => price(false, argv, deps);
|
|
580
641
|
else if (sub === "set-price") action = () => price(true, argv, deps);
|
|
@@ -589,8 +650,11 @@ export async function handleModelsRuntimeCommand(sub: string, argv: string[], de
|
|
|
589
650
|
else if (sub === "new-arrivals") action = () => newArrivals(argv, deps);
|
|
590
651
|
else if (sub === "context") action = () => context(argv, deps);
|
|
591
652
|
else if (sub === "shadow") action = () => shadow(argv, deps);
|
|
653
|
+
else if (sub === "auto-review") action = () => autoReview(argv, deps);
|
|
592
654
|
if (!action) return null;
|
|
593
|
-
|
|
655
|
+
let outcome: number | void = 0;
|
|
656
|
+
const exit = await runCliAction(async () => { outcome = await action(); });
|
|
657
|
+
return exit || outcome || 0;
|
|
594
658
|
}
|
|
595
659
|
|
|
596
660
|
export const MODELS_RUNTIME_USAGE = USAGE;
|
|
@@ -14,6 +14,8 @@ export interface ObserveStreamDeps extends RuntimeApiDeps {
|
|
|
14
14
|
/** Reduced timings for isolated tests only; never command-line options. */
|
|
15
15
|
pollIntervalMs?: number;
|
|
16
16
|
requestTimeoutMs?: number;
|
|
17
|
+
/** Frozen clock for usage-compare tests; never a command-line option. */
|
|
18
|
+
now?: () => number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export class ObservationError extends Error {}
|
package/src/cli/observe.ts
CHANGED
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
} from "./runtime-api";
|
|
15
15
|
import { followLogs } from "./log-follow";
|
|
16
16
|
import { handleLogFilterCommand } from "./log-view-filter";
|
|
17
|
+
import { handleLogGetCommand, handleLogHistoryCommand } from "./log-history";
|
|
17
18
|
import { followInjection } from "./injection-follow";
|
|
18
19
|
import type { ObserveStreamDeps } from "./observe-stream";
|
|
19
20
|
import { formatUsageReport } from "./usage-report";
|
|
21
|
+
import { handleUsageCompareCommand } from "./usage-compare";
|
|
20
22
|
import { selectUsageModelView, takeUsageSearchOption } from "./usage-model-search";
|
|
21
23
|
import { USAGE_RANGES, USAGE_SURFACES, type UsageSummary, type UsageFilterEcho } from "../usage/summary";
|
|
22
24
|
import { parseUsageTimeWindow, type UsageTimeWindow } from "../usage/time-range";
|
|
@@ -30,12 +32,16 @@ const USAGE = `Usage:
|
|
|
30
32
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
31
33
|
[--conversation <id>] [--account <label>] [--limit <n>] [--follow] [--json|--jsonl] [--events]
|
|
32
34
|
ocx logs explain <request-id> [--json]
|
|
35
|
+
ocx logs history [selectors] [--cursor <token>] [--limit <1-100>] [--json|--jsonl]
|
|
36
|
+
ocx logs get <request-id> [--json]
|
|
33
37
|
ocx logs filter [selectors] [--scan-limit <1..2000>] [--limit <1..2000>] [--json|--jsonl]
|
|
34
38
|
ocx logs rebuild-index
|
|
35
39
|
ocx logs index-status
|
|
36
40
|
ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
37
41
|
[--since <epoch-ms|ISO-datetime>] [--until <epoch-ms|ISO-datetime>]
|
|
38
|
-
[--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--json]
|
|
42
|
+
[--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--top <1-1000>] [--json]
|
|
43
|
+
ocx usage compare [--range <today|1d|7d|30d>] [--surface <all|codex|claude|grok>]
|
|
44
|
+
[--provider <name>] [--model <id>] [--api-key-id <id>] [--json]
|
|
39
45
|
ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
|
|
40
46
|
ocx observe memory [--json]
|
|
41
47
|
ocx observe debug [--json]
|
|
@@ -174,6 +180,7 @@ async function indexStatus(argv: string[], deps: RuntimeApiDeps): Promise<void>
|
|
|
174
180
|
async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
175
181
|
const args = [...argv];
|
|
176
182
|
const search = takeUsageSearchOption(args);
|
|
183
|
+
const top = takeIntegerOption(args, "--top", { min: 1, max: 1000 });
|
|
177
184
|
const wantsJson = takeFlag(args, "--json");
|
|
178
185
|
const range = takeOption(args, "--range") ?? "30d";
|
|
179
186
|
const surface = takeOption(args, "--surface") ?? "all";
|
|
@@ -246,10 +253,10 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
246
253
|
// the call, so passing formatUsageReport(...) inline would run the human
|
|
247
254
|
// renderer during --json and let its assumptions affect a path that is meant
|
|
248
255
|
// to bypass it entirely.
|
|
249
|
-
const view = search === undefined ? undefined : selectUsageModelView(result, search);
|
|
256
|
+
const view = search === undefined ? undefined : selectUsageModelView(result, search, wantsJson ? undefined : top);
|
|
250
257
|
const displayed = view ?? result;
|
|
251
258
|
if (wantsJson) printData(displayed, true);
|
|
252
|
-
else printData(displayed, false, formatUsageReport(displayed as Parameters<typeof formatUsageReport>[0],
|
|
259
|
+
else printData(displayed, false, formatUsageReport(displayed as Parameters<typeof formatUsageReport>[0], { modelView: view?.modelView, modelRowLimit: top }));
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -320,12 +327,17 @@ export async function handleObserveCommand(argv: string[], deps: ObserveStreamDe
|
|
|
320
327
|
if (sub === "logs") {
|
|
321
328
|
const action = rest[0];
|
|
322
329
|
if (action === "filter") streamExit = await handleLogFilterCommand(rest.slice(1), deps, formatLog);
|
|
330
|
+
else if (action === "history") await handleLogHistoryCommand(rest.slice(1), deps, formatLog);
|
|
331
|
+
else if (action === "get") await handleLogGetCommand(rest.slice(1), deps, formatLog);
|
|
323
332
|
else if (action === "explain") await explain(rest.slice(1), deps);
|
|
324
333
|
else if (action === "rebuild-index") await rebuildIndex(rest.slice(1), deps);
|
|
325
334
|
else if (action === "index-status") await indexStatus(rest.slice(1), deps);
|
|
326
335
|
else streamExit = await logs(rest, deps);
|
|
327
336
|
}
|
|
328
|
-
else if (sub === "usage")
|
|
337
|
+
else if (sub === "usage") {
|
|
338
|
+
if (rest[0] === "compare") await handleUsageCompareCommand(rest.slice(1), deps);
|
|
339
|
+
else await usage(rest, deps);
|
|
340
|
+
}
|
|
329
341
|
else if (sub === "storage") await storage(rest, deps);
|
|
330
342
|
else if (sub === "memory") await simple("/api/system/memory", rest, deps);
|
|
331
343
|
else if (sub === "debug") await simple("/api/debug", rest, deps);
|