@bitkyc08/opencodex 2.80.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
package/src/claude/outbound.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type TranslatorBudget,
|
|
21
21
|
} from "../lib/translator-budget";
|
|
22
22
|
import { sseFieldOffset, sseFieldValue } from "../lib/sse-decoder";
|
|
23
|
-
import { decodeReasoningEnvelope, encodeReasoningEnvelope } from "../responses/reasoning-envelope";
|
|
23
|
+
import { decodeReasoningEnvelope, encodeReasoningEnvelope, OCX_REASONING_PREFIX } from "../responses/reasoning-envelope";
|
|
24
24
|
|
|
25
25
|
type Rec = Record<string, unknown>;
|
|
26
26
|
|
|
@@ -70,8 +70,67 @@ export function anthropicErrorType(status: number): string {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Claude Code compacts reactively only when an oversized-prompt refusal is worded the way the
|
|
75
|
+
* Anthropic API words it: it looks for `prompt is too long` (or `input is too long for requested
|
|
76
|
+
* model`) in the message and reads `prompt is too long: <actual> tokens > <limit> maximum` for the
|
|
77
|
+
* gap (2.1.288 binary). Routed providers word the same refusal differently, so a
|
|
78
|
+
* context_length_exceeded envelope is rewritten into that form. Counts are carried over only when
|
|
79
|
+
* the upstream states both of them; otherwise the original text is prefixed and no number is
|
|
80
|
+
* guessed. Without this the client ends the turn on a fatal error instead of compacting
|
|
81
|
+
* (devlog/_plan/261009_claude_1m_default/010).
|
|
82
|
+
*/
|
|
83
|
+
const CLAUDE_PROMPT_TOO_LONG_RE = /prompt is too long|input is too long for requested model/i;
|
|
84
|
+
const STATED_CONTEXT_COUNTS_RE = /maximum context length is (\d+) tokens[\s\S]*?(?:resulted in|you requested) (\d+) tokens/i;
|
|
85
|
+
|
|
86
|
+
export function claudePromptTooLongMessage(message: string): string {
|
|
87
|
+
if (CLAUDE_PROMPT_TOO_LONG_RE.test(message)) return message;
|
|
88
|
+
const counts = STATED_CONTEXT_COUNTS_RE.exec(message);
|
|
89
|
+
if (counts) return `prompt is too long: ${counts[2]} tokens > ${counts[1]} maximum`;
|
|
90
|
+
return `prompt is too long: ${message}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Provider wordings of an oversized-input refusal, for lanes that relay a provider's own error text. */
|
|
94
|
+
const CONTEXT_OVERFLOW_TEXT_RE = /prompt is too long|input is too long|context window|context length|maximum context|too many tokens|model token limit/i;
|
|
95
|
+
|
|
96
|
+
export function isContextOverflowText(message: string): boolean {
|
|
97
|
+
return CONTEXT_OVERFLOW_TEXT_RE.test(message);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* SSE payload rewrite for the native Messages lane: an `error` event that refuses an oversized
|
|
102
|
+
* input gets the Anthropic wording; every other payload passes through byte-identical. The type
|
|
103
|
+
* gate keeps a rate limit that happens to mention the context window untouched.
|
|
104
|
+
*/
|
|
105
|
+
export function claudeOverflowSsePayload(payload: string): string {
|
|
106
|
+
if (!payload.includes("\"error\"")) return payload;
|
|
107
|
+
let parsed: unknown;
|
|
108
|
+
try { parsed = JSON.parse(payload); } catch { return payload; }
|
|
109
|
+
if (!isRec(parsed) || parsed.type !== "error" || !isRec(parsed.error)) return payload;
|
|
110
|
+
const error = parsed.error;
|
|
111
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
112
|
+
const sized = error.type === "invalid_request_error" || error.type === "request_too_large";
|
|
113
|
+
if (!sized || isThroughputLimitText(message)) return payload;
|
|
114
|
+
if (!(error.code === "context_length_exceeded" || isContextOverflowText(message))) return payload;
|
|
115
|
+
const next = claudePromptTooLongMessage(message);
|
|
116
|
+
return next === message ? payload : JSON.stringify({ ...parsed, error: { ...error, message: next } });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A throughput limit that the shared classifier files under context_length_exceeded (its "too many
|
|
121
|
+
* tokens" match also catches "too many tokens per minute"). Worded as an overflow it would make
|
|
122
|
+
* Claude Code compact a conversation that only needs to wait, so it keeps its own text.
|
|
123
|
+
*/
|
|
124
|
+
const THROUGHPUT_LIMIT_TEXT_RE = /\bper (?:second|minute|hour|day)\b|\brate limit|\bquota\b|\btpm\b/i;
|
|
125
|
+
|
|
126
|
+
export function isThroughputLimitText(message: string): boolean {
|
|
127
|
+
return THROUGHPUT_LIMIT_TEXT_RE.test(message);
|
|
128
|
+
}
|
|
129
|
+
|
|
73
130
|
export function anthropicErrorBody(status: number, message: string, type?: string, code?: string): Rec {
|
|
74
|
-
|
|
131
|
+
const overflow = code === "context_length_exceeded" && !isThroughputLimitText(message);
|
|
132
|
+
const text = overflow ? claudePromptTooLongMessage(message) : message;
|
|
133
|
+
return { type: "error", error: { type: type ?? anthropicErrorType(status), message: text, ...(code ? { code } : {}) } };
|
|
75
134
|
}
|
|
76
135
|
|
|
77
136
|
export function anthropicErrorResponse(status: number, message: string, type?: string, code?: string): Response {
|
|
@@ -280,6 +339,13 @@ interface OpenBlock {
|
|
|
280
339
|
thinkingBuf?: string;
|
|
281
340
|
thinkingBufBytes?: number;
|
|
282
341
|
reasoningSig?: string;
|
|
342
|
+
/** The provider's own encrypted reasoning for this item; signed into the block on close. */
|
|
343
|
+
reasoningNative?: { enc: string; id?: string };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** A provider-minted (not ocxr1) `encrypted_content`, or `""`. */
|
|
347
|
+
function nativeEncryptedReasoning(encrypted: string): string {
|
|
348
|
+
return encrypted.length > 0 && !encrypted.startsWith(OCX_REASONING_PREFIX) ? encrypted : "";
|
|
283
349
|
}
|
|
284
350
|
|
|
285
351
|
/** Streaming: Responses SSE bytes -> Anthropic Messages SSE bytes. */
|
|
@@ -294,6 +360,7 @@ export function responsesSseToAnthropicSse(
|
|
|
294
360
|
* upstream sent no confirmed usage before the first frame. See `messageSnapshot` (#4857).
|
|
295
361
|
*/
|
|
296
362
|
inputTokenFloor?: number;
|
|
363
|
+
nativeReasoningTagFor?: (blob: string) => string | undefined;
|
|
297
364
|
},
|
|
298
365
|
): ReadableStream<Uint8Array> {
|
|
299
366
|
const translatorBudget = opts.translatorBudget;
|
|
@@ -382,6 +449,13 @@ export function responsesSseToAnthropicSse(
|
|
|
382
449
|
open.webSearchArgsEmitted = true;
|
|
383
450
|
}
|
|
384
451
|
if (open.kind === "thinking") {
|
|
452
|
+
const native = open.reasoningNative;
|
|
453
|
+
const tag = native && opts.nativeReasoningTagFor?.(native.enc);
|
|
454
|
+
if (!open.thinkingBuf && !open.reasoningSig && !tag) {
|
|
455
|
+
releaseThinkingBuffer(open);
|
|
456
|
+
open = null;
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
385
459
|
// Delay the index and all thinking frames until closure so a matching
|
|
386
460
|
// done envelope can put its redacted blocks first. The existing buffer
|
|
387
461
|
// remains charged through signature emission, including queued frames.
|
|
@@ -396,7 +470,10 @@ export function responsesSseToAnthropicSse(
|
|
|
396
470
|
delta: { type: "thinking_delta", thinking: open.thinkingBuf },
|
|
397
471
|
});
|
|
398
472
|
}
|
|
399
|
-
const signature = open.reasoningSig ?? encodeReasoningEnvelope({
|
|
473
|
+
const signature = open.reasoningSig ?? encodeReasoningEnvelope({
|
|
474
|
+
txt: open.thinkingBuf ?? "",
|
|
475
|
+
...(tag ? { nat: { ...native, model, tag } } : {}),
|
|
476
|
+
}, translatorBudget);
|
|
400
477
|
emit("content_block_delta", {
|
|
401
478
|
type: "content_block_delta", index: open.index,
|
|
402
479
|
delta: { type: "signature_delta", signature },
|
|
@@ -684,11 +761,15 @@ export function responsesSseToAnthropicSse(
|
|
|
684
761
|
emit("content_block_start", { type: "content_block_start", index: idx, content_block: { type: "redacted_thinking", data } });
|
|
685
762
|
emit("content_block_stop", { type: "content_block_stop", index: idx });
|
|
686
763
|
}
|
|
687
|
-
|
|
764
|
+
// A provider's own blob is the only copy of its reasoning the client can hold, so
|
|
765
|
+
// it gets a thinking block even when no summary text arrived.
|
|
766
|
+
const native = nativeEncryptedReasoning(encrypted);
|
|
767
|
+
if ((env?.sig || native) && open?.kind !== "thinking") {
|
|
688
768
|
ensureBlock("thinking");
|
|
689
769
|
}
|
|
690
770
|
if (open?.kind === "thinking") {
|
|
691
771
|
if (env?.sig) open.reasoningSig = env.sig;
|
|
772
|
+
if (native) open.reasoningNative = typeof item.id === "string" && item.id.length > 0 ? { enc: native, id: item.id } : { enc: native };
|
|
692
773
|
closeOpenBlock();
|
|
693
774
|
}
|
|
694
775
|
}
|
|
@@ -881,7 +962,7 @@ export function responsesSseToAnthropicSse(
|
|
|
881
962
|
}
|
|
882
963
|
|
|
883
964
|
/** Non-streaming: /v1/responses JSON -> Anthropic message JSON. */
|
|
884
|
-
export function responsesJsonToAnthropicMessage(json: unknown, model: string, translatorBudget?: TranslatorBudget): Rec {
|
|
965
|
+
export function responsesJsonToAnthropicMessage(json: unknown, model: string, translatorBudget?: TranslatorBudget, nativeReasoningTagFor?: (blob: string) => string | undefined): Rec {
|
|
885
966
|
const body = isRec(json) ? json : {};
|
|
886
967
|
const output = Array.isArray(body.output) ? body.output : [];
|
|
887
968
|
const content: Rec[] = [];
|
|
@@ -917,10 +998,14 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
|
|
|
917
998
|
// Legacy combined envelopes place redacted blocks before the signed block,
|
|
918
999
|
// matching the Anthropic adapter. New bridge output uses separate items.
|
|
919
1000
|
for (const data of env?.red ?? []) content.push({ type: "redacted_thinking", data });
|
|
920
|
-
// env.txt may be locally hidden text. Do not expose it here
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
|
|
1001
|
+
// env.txt may be locally hidden text. Do not expose it here. A provider's own blob is
|
|
1002
|
+
// carried in the envelope so the next request can hand it back (see inbound.ts).
|
|
1003
|
+
const native = nativeEncryptedReasoning(encrypted);
|
|
1004
|
+
const tag = native && nativeReasoningTagFor?.(native);
|
|
1005
|
+
if (parts.length > 0 || env?.sig || tag) {
|
|
1006
|
+
const text = parts.join("\n\n");
|
|
1007
|
+
const nat = tag ? { enc: native, model, tag, ...(typeof raw.id === "string" && raw.id.length > 0 ? { id: raw.id } : {}) } : undefined;
|
|
1008
|
+
content.push({ type: "thinking", thinking: text, signature: env?.sig ?? encodeReasoningEnvelope({ txt: text, ...(nat ? { nat } : {}) }, translatorBudget) });
|
|
924
1009
|
}
|
|
925
1010
|
break;
|
|
926
1011
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OcxConfig } from "../types";
|
|
2
2
|
import { claudeCodeAlias, claudeCodeNativeAlias, resolveAlias } from "./alias";
|
|
3
|
-
import { AUTO_CONTEXT_OFF, shouldMarkOneMillion, stripOneMillionMarker, withOneMillionMarker } from "./context-windows";
|
|
3
|
+
import { AUTO_CONTEXT_OFF, shouldMarkOneMillion, stripOneMillionMarker, UNPAIRED_AUTO_CONTEXT, withOneMillionMarker, type AutoContextMode } from "./context-windows";
|
|
4
|
+
import { isAnthropicClaudeRoute } from "./long-context";
|
|
4
5
|
import { hasOwnProvider } from "../config";
|
|
5
6
|
import { knownModelIdsForProvider } from "../router";
|
|
6
7
|
import { decodeRoutedModelIdOrThrow } from "../providers/slug-codec";
|
|
@@ -14,20 +15,43 @@ export { SAFE_AGENT_MODEL_ID } from "../config/subagent-models";
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Generated subagent defs cannot rely on the parent's auto-context compaction
|
|
17
|
-
* pairing, so their [1m] marker follows the
|
|
18
|
-
* the effective window (exact selector, then the
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* pairing, so their [1m] marker follows the unpaired long-context rule
|
|
19
|
+
* (long-context.ts): mark when the effective window (exact selector, then the
|
|
20
|
+
* canonical [1m] form, then bare) is >= 1M or can host the default compact window,
|
|
21
|
+
* whose overflow Claude Code compacts on as `prompt is too long`; strip an inherited
|
|
22
|
+
* marker the window cannot carry; with no window information, keep the selector as it
|
|
23
|
+
* was. Genuine routed [1m] ids are preserved through the canonical-exact lookup.
|
|
24
|
+
* (#854; devlog/_plan/261009_claude_1m_default/020)
|
|
22
25
|
*/
|
|
23
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The marking mode a subagent selector gets: real Anthropic models (a bare `claude-*` passthrough
|
|
28
|
+
* id, or a Claude row on either Anthropic pool) need a genuine 1M; everything else follows the
|
|
29
|
+
* unpaired long-context rule. Connected launches read windows from the published catalog, which
|
|
30
|
+
* does not drop capped Anthropic rows, so this cannot rely on the window map alone.
|
|
31
|
+
*/
|
|
32
|
+
function subagentSelectorMode(bare: string): AutoContextMode {
|
|
33
|
+
const route = resolveAlias(bare) ?? resolveDesktop3pAlias(bare) ?? bare;
|
|
34
|
+
const slash = route.indexOf("/");
|
|
35
|
+
const anthropic = slash < 0
|
|
36
|
+
? route.startsWith("claude-")
|
|
37
|
+
: isAnthropicClaudeRoute(route.slice(0, slash), route.slice(slash + 1));
|
|
38
|
+
return anthropic ? AUTO_CONTEXT_OFF : UNPAIRED_AUTO_CONTEXT;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `accounting200k` (claudeCode.contextAccounting "200k") stops automatic marking only: an unmarked
|
|
43
|
+
* selector stays bare, while an explicit [1m] still follows its selector's safety rule.
|
|
44
|
+
*/
|
|
45
|
+
export function withSubagentContextMarker(selector: string, windows: Record<string, number>, accounting200k = false): string {
|
|
24
46
|
const bare = stripOneMillionMarker(selector);
|
|
25
47
|
const wasMarked = selector !== bare;
|
|
26
48
|
const canonicalExact = wasMarked ? `${bare}[1m]` : selector;
|
|
27
49
|
const authoritativeWindow = windows[selector] ?? windows[canonicalExact] ?? windows[bare];
|
|
28
50
|
if (typeof authoritativeWindow === "number" && authoritativeWindow > 0) {
|
|
29
|
-
|
|
30
|
-
|
|
51
|
+
if (accounting200k && !wasMarked) return bare;
|
|
52
|
+
const mode = subagentSelectorMode(bare);
|
|
53
|
+
return shouldMarkOneMillion(authoritativeWindow, mode)
|
|
54
|
+
? (withOneMillionMarker(selector, windows, mode) ?? selector)
|
|
31
55
|
: bare;
|
|
32
56
|
}
|
|
33
57
|
return wasMarked ? selector : bare;
|
|
@@ -56,6 +80,11 @@ function selectorIdentity(selector: string): string {
|
|
|
56
80
|
return fullSelectorIdentity(stripOneMillionMarker(selector));
|
|
57
81
|
}
|
|
58
82
|
|
|
83
|
+
/** Whether the config opted into 200k accounting (shared by roster, self and force markers). */
|
|
84
|
+
export function accountsAt200k(config: OcxConfig): boolean {
|
|
85
|
+
return config.claudeCode?.contextAccounting === "200k";
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
/** Resolve only currently exposed entries; retained roster rows are not exposure proof. */
|
|
60
89
|
export function resolveSubagentForceModel(config: OcxConfig, windows: Record<string, number>, available: SubagentForceExposure): string | null {
|
|
61
90
|
const entry = config.claudeCode?.subagentModelForce;
|
|
@@ -77,8 +106,8 @@ export function resolveSubagentForceModel(config: OcxConfig, windows: Record<str
|
|
|
77
106
|
const window = windows[parts.alias] ?? windows[`${bare}[1m]`] ?? windows[bare];
|
|
78
107
|
// Identity admission must not let an appended context promise ride on an
|
|
79
108
|
// ordinary exposed model. Exact catalog ids such as kimi/k3[1m] remain literal.
|
|
80
|
-
if (requestedMarker && !exactMarkedExposure && !(typeof window === "number" && Number.isFinite(window) && window
|
|
81
|
-
const marked = exactMarkedExposure ? parts.alias : withSubagentContextMarker(parts.alias, windows);
|
|
109
|
+
if (requestedMarker && !exactMarkedExposure && !(typeof window === "number" && Number.isFinite(window) && shouldMarkOneMillion(window, subagentSelectorMode(bare)))) return null;
|
|
110
|
+
const marked = exactMarkedExposure ? parts.alias : withSubagentContextMarker(parts.alias, windows, accountsAt200k(config));
|
|
82
111
|
// Bare Anthropic names are indistinguishable from the old CLI's fallback.
|
|
83
112
|
// Encode only the force selector; handlers restore native identity before
|
|
84
113
|
// their existing credential/model-map checks. Roster generation is unchanged.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
1
2
|
import { apiError, apiJson, proxyUnreachable, resolveBaseUrl, type AccountDeps } from "./account-api";
|
|
2
3
|
|
|
4
|
+
/** A 200 that names another pool is refused locally; there is no upstream status to forward. */
|
|
5
|
+
export function anthropicProviderIdentityError(json: Record<string, unknown>, provider: AnthropicInstanceId, label: string): number | undefined {
|
|
6
|
+
return json.provider === provider ? undefined : apiError({}, `invalid ${label} identity`, 400);
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/** A separate account selector prevents accidentally changing the whole Anthropic pool. */
|
|
4
|
-
export async function cmdAnthropicAccountThreshold(args: string[], action: string, wantsJson: boolean, deps: AccountDeps): Promise<number> {
|
|
10
|
+
export async function cmdAnthropicAccountThreshold(args: string[], action: string, wantsJson: boolean, deps: AccountDeps, provider: AnthropicInstanceId = "anthropic"): Promise<number> {
|
|
5
11
|
const selector = args.indexOf("--account");
|
|
6
12
|
const accountId = selector >= 0 ? args[selector + 1] : undefined;
|
|
7
13
|
if (selector >= 0) args.splice(selector, 2);
|
|
@@ -15,8 +21,8 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
15
21
|
const base = await resolveBaseUrl(deps);
|
|
16
22
|
if (!base) return proxyUnreachable();
|
|
17
23
|
const response = action === "status"
|
|
18
|
-
? await apiJson(deps, base, "GET",
|
|
19
|
-
: await apiJson(deps, base, "PUT", "/api/oauth/accounts/auto-switch", { provider
|
|
24
|
+
? await apiJson(deps, base, "GET", `/api/oauth/accounts?provider=${provider}`)
|
|
25
|
+
: await apiJson(deps, base, "PUT", "/api/oauth/accounts/auto-switch", { provider, accountId, threshold });
|
|
20
26
|
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
21
27
|
if (response.status !== 200) return apiError(response.json, "failed to update account threshold", response.status);
|
|
22
28
|
if (!response.json || typeof response.json !== "object" || Array.isArray(response.json)) return apiError({}, "invalid account threshold response", 400);
|
|
@@ -28,7 +34,10 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
28
34
|
const validPercent = (value: unknown) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 100;
|
|
29
35
|
if ((result.autoSwitchThresholdOverride !== null && !validPercent(result.autoSwitchThresholdOverride))
|
|
30
36
|
|| !validPercent(result.effectiveAutoSwitchThreshold)) return apiError({}, "invalid account threshold response", 400);
|
|
31
|
-
const
|
|
37
|
+
const responseProvider = action === "status" ? response.json.provider : result.provider;
|
|
38
|
+
if ((provider === "anthropic2" || responseProvider !== undefined) && responseProvider !== provider) return apiError({}, "invalid account threshold identity", 400);
|
|
39
|
+
if (action !== "status" && (provider === "anthropic2" || result.accountId !== undefined) && result.accountId !== accountId) return apiError({}, "invalid account threshold identity", 400);
|
|
40
|
+
const payload = { provider, accountId, autoSwitchThresholdOverride: result.autoSwitchThresholdOverride,
|
|
32
41
|
effectiveAutoSwitchThreshold: result.effectiveAutoSwitchThreshold };
|
|
33
42
|
if (wantsJson) console.log(JSON.stringify(payload, null, 2));
|
|
34
43
|
else console.log(`auto-switch: ${payload.autoSwitchThresholdOverride === null ? "inherited" : "custom"} (${payload.effectiveAutoSwitchThreshold === 0 ? "usage-based switching disabled" : `${payload.effectiveAutoSwitchThreshold}%`})`);
|
|
@@ -36,6 +45,6 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
function invalid(): number {
|
|
39
|
-
console.error("Usage: ocx account auto-switch anthropic <status|inherit|on|off|threshold <0-100>> --account <id> [--json]");
|
|
48
|
+
console.error("Usage: ocx account auto-switch <anthropic|anthropic2> <status|inherit|on|off|threshold <0-100>> --account <id> [--json]");
|
|
40
49
|
return 2;
|
|
41
50
|
}
|
package/src/cli/account-api.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { configuredAnthropicInstance, isAnthropicOAuthInstance } from "../providers/anthropic-instance";
|
|
1
2
|
import { parseQuotaFailureCode, type QuotaFailureCode, type ProviderQuota, type AccountQuotaMode } from "../providers/quota-types";
|
|
2
3
|
/**
|
|
3
4
|
* Data-access layer for `ocx account` (issue #180) — live-proxy HTTP client and
|
|
@@ -110,6 +111,7 @@ export function classifyAccount(config: OcxConfig, name: string): ClassifyResult
|
|
|
110
111
|
if (provider && !provider.authMode && (provider.apiKey || (provider.apiKeyPool?.length ?? 0) > 0)) {
|
|
111
112
|
return { type: "api-key" };
|
|
112
113
|
}
|
|
114
|
+
if (name === "anthropic2" && !configuredAnthropicInstance(config, name)) return { error: "Anthropic pool instance is not configured" };
|
|
113
115
|
if (isPublicOAuthProvider(name)) return { type: "oauth" };
|
|
114
116
|
if (provider) return { type: "api-key" };
|
|
115
117
|
return { error: `unknown provider "${name}"` };
|
|
@@ -408,7 +410,7 @@ async function fetchOAuthRows(
|
|
|
408
410
|
needsReauth: a.needsReauth,
|
|
409
411
|
...projectAccountHealth(a, name, a.id),
|
|
410
412
|
...(a.paused === true ? { paused: true } : {}),
|
|
411
|
-
...(name
|
|
413
|
+
...(isAnthropicOAuthInstance(name) && Object.hasOwn(a, "autoSwitchThresholdOverride") ? { autoSwitchThresholdOverride: a.autoSwitchThresholdOverride } : {}),
|
|
412
414
|
...(a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
413
415
|
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
414
416
|
? { autoSelectable: a.autoSelectable } : {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isAnthropicOAuthInstance } from "../providers/anthropic-instance";
|
|
1
2
|
import { loadConfig } from "../config";
|
|
2
|
-
import { cmdAnthropicAccountThreshold } from "./account-anthropic-threshold";
|
|
3
|
+
import { anthropicProviderIdentityError, cmdAnthropicAccountThreshold } from "./account-anthropic-threshold";
|
|
3
4
|
import { isReservedCodexAccountWord, reportCodexAccountTargetError, resolveCodexAccountTarget } from "./account-target";
|
|
4
5
|
import { hasPassiveAccountQuota } from "../providers/quota";
|
|
5
6
|
import { closeSync, openSync, readSync, readFileSync, statSync } from "node:fs";
|
|
@@ -41,7 +42,7 @@ const AUTO_NOTE = "auto (no pin — lowest-usage account is selected per request
|
|
|
41
42
|
const EXTENDED_USAGE = `Usage:
|
|
42
43
|
ocx account refresh <provider> [--json]
|
|
43
44
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
44
|
-
ocx account auto-switch anthropic <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
45
|
+
ocx account auto-switch <anthropic|anthropic2> <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
45
46
|
ocx account alias <provider> <id|alias|main> <display-name|-> [--json]
|
|
46
47
|
ocx account priority <provider> <id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
47
48
|
ocx account pause <provider> <id|alias|main> [--json]
|
|
@@ -49,7 +50,7 @@ const EXTENDED_USAGE = `Usage:
|
|
|
49
50
|
ocx account pause-exhausted <provider> [--json]
|
|
50
51
|
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
51
52
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
52
|
-
ocx account routes anthropic [--file <json-file>|--clear] [--json]
|
|
53
|
+
ocx account routes <anthropic|anthropic2> [--file <json-file>|--clear] [--json]
|
|
53
54
|
ocx account remove <provider> <id|alias|main> --yes [--json]
|
|
54
55
|
ocx account clear-cooldown <provider> <id|alias|main> [--json]
|
|
55
56
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -360,8 +361,8 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<
|
|
|
360
361
|
const classified = configAndType(deps, name);
|
|
361
362
|
// Anthropic keeps its threshold on its own pool contract; generic OAuth providers (#695)
|
|
362
363
|
// and the Codex pool are accepted here.
|
|
363
|
-
if (!("error" in classified) && classified.type === "oauth" && name
|
|
364
|
-
if ("error" in classified || classified.type === "api-key" || name
|
|
364
|
+
if (!("error" in classified) && classified.type === "oauth" && isAnthropicOAuthInstance(name)) return cmdAnthropicAccountThreshold(args, action, wantsJson, deps, name);
|
|
365
|
+
if ("error" in classified || classified.type === "api-key" || isAnthropicOAuthInstance(name)) {
|
|
365
366
|
return usage("Error: auto-switch only applies to the openai Codex account pool or a generic OAuth provider pool");
|
|
366
367
|
}
|
|
367
368
|
const genericPool = classified.type === "oauth";
|
|
@@ -638,7 +639,7 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
|
|
|
638
639
|
if (!name || !requestedId || args.length) return usage();
|
|
639
640
|
const classified = configAndType(deps, name);
|
|
640
641
|
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
641
|
-
if (classified.type !== "codex" && !(classified.type === "oauth" && name
|
|
642
|
+
if (classified.type !== "codex" && !(classified.type === "oauth" && isAnthropicOAuthInstance(name))) {
|
|
642
643
|
return usage(`Error: ${name} has no operator-clearable account cooldown`);
|
|
643
644
|
}
|
|
644
645
|
const baseUrl = await resolveBaseUrl(deps);
|
|
@@ -1108,7 +1109,8 @@ export async function cmdRoutes(args: string[], deps: AccountDeps): Promise<numb
|
|
|
1108
1109
|
const wantsJson = flag(args, "--json");
|
|
1109
1110
|
const file = flagValue(args, "--file");
|
|
1110
1111
|
const clear = flag(args, "--clear");
|
|
1111
|
-
|
|
1112
|
+
const provider = args.shift();
|
|
1113
|
+
if (!isAnthropicOAuthInstance(provider) || args.length || (file.found && (!file.value || clear))) return usage();
|
|
1112
1114
|
const baseUrl = await resolveBaseUrl(deps);
|
|
1113
1115
|
if (!baseUrl) return proxyUnreachable();
|
|
1114
1116
|
let routes: unknown;
|
|
@@ -1123,10 +1125,12 @@ export async function cmdRoutes(args: string[], deps: AccountDeps): Promise<numb
|
|
|
1123
1125
|
}
|
|
1124
1126
|
const writing = file.found || clear;
|
|
1125
1127
|
const response = await apiJson(deps, baseUrl, writing ? "PUT" : "GET",
|
|
1126
|
-
writing ? "/api/pool/settings" :
|
|
1127
|
-
writing ? { provider
|
|
1128
|
+
writing ? "/api/pool/settings" : `/api/pool/settings?provider=${provider}`,
|
|
1129
|
+
writing ? { provider, routes: clear ? null : routes } : undefined);
|
|
1128
1130
|
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
1129
1131
|
if (response.status !== 200) return apiError(response.json, "failed to manage Anthropic routes", response.status);
|
|
1132
|
+
const identityError = anthropicProviderIdentityError(response.json, provider, "Anthropic routes");
|
|
1133
|
+
if (identityError !== undefined) return identityError;
|
|
1130
1134
|
if (wantsJson) console.log(JSON.stringify(response.json, null, 2));
|
|
1131
1135
|
else console.log(JSON.stringify(response.json.routes ?? [], null, 2));
|
|
1132
1136
|
return 0;
|
|
@@ -51,7 +51,7 @@ const grant = z.object({ id: grantId, label: text, resetsTotal: z.number().int()
|
|
|
51
51
|
percentUsed: z.object({ five_hour: threshold.optional(), seven_day: threshold.optional(),
|
|
52
52
|
seven_day_overage_included: threshold.optional() }),
|
|
53
53
|
}).refine(value => value.resetsLeft <= value.resetsTotal);
|
|
54
|
-
export const grantsSchema = z.object({ accountId: text.min(1), eligible: z.boolean(),
|
|
54
|
+
export const grantsSchema = z.object({ provider: z.enum(["anthropic", "anthropic2"]).optional(), accountId: text.min(1), eligible: z.boolean(),
|
|
55
55
|
ineligibleReason: z.enum(["config_off", "tier", "seat", "mobile", "surface", "cli_version", "no_grant", "tenure", "other_experiment", "unavailable", "unknown"]).nullable(),
|
|
56
56
|
atLimit: z.boolean(), grants: z.array(grant).refine(values => new Set(values.map(value => value.id)).size === values.length), nextGrantId: grantId.nullable(), weeklyResetsAt: date, cooldownUntil: date,
|
|
57
57
|
pendingOperation: z.object({ operationId: z.string().uuid(), grantId, createdAt: z.number().finite(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
1
2
|
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/account-id";
|
|
2
3
|
import { isValidProviderName } from "../config/provider-name";
|
|
3
4
|
import { resolveCodexAccountTargetFromRows } from "./account-target";
|
|
@@ -13,7 +14,7 @@ const USAGE = "Usage: ocx account pool <provider> [--enabled on|off] [--threshol
|
|
|
13
14
|
+ " ocx account auto-switch openai <status|on|off|inherit|threshold N> --account ID [--json]\n"
|
|
14
15
|
+ " ocx account credits openai <ID on|off|--all on|off> [--json]\n"
|
|
15
16
|
+ " ocx account quota-activation openai ID --window <fiveHour|weekly> <on|off> [--json]\n"
|
|
16
|
-
+ " ocx account anthropic-reset-grants [ID] [--json]";
|
|
17
|
+
+ " ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]";
|
|
17
18
|
const READ_ERRORS = { no_account: "No matching Anthropic OAuth account.", auth_failed: "Sign in to this Anthropic account again.",
|
|
18
19
|
upstream_unavailable: "Anthropic did not return the reset-grant status.", ledger_unavailable: "The reset journal is unavailable.",
|
|
19
20
|
ledger_busy: "The reset journal is busy. No grant was consumed." };
|
|
@@ -67,14 +68,14 @@ async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promis
|
|
|
67
68
|
patch.strategy = strategy;
|
|
68
69
|
}
|
|
69
70
|
if (window !== undefined) {
|
|
70
|
-
if (provider
|
|
71
|
+
if (!isAnthropicInstanceId(provider) || !["five-hour", "weekly", "max-utilization"].includes(window)) usage();
|
|
71
72
|
patch.quotaWindow = window;
|
|
72
73
|
}
|
|
73
74
|
done(args);
|
|
74
75
|
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
75
76
|
const before = parseDto(poolSchema, await request(`/api/pool/settings?provider=${encodeURIComponent(provider)}`, pinned));
|
|
76
77
|
requireEqual(before.provider, provider);
|
|
77
|
-
requireEqual(before.kind, provider === "openai" ? "codex" : provider
|
|
78
|
+
requireEqual(before.kind, provider === "openai" ? "codex" : isAnthropicInstanceId(provider) ? "anthropic" : "generic");
|
|
78
79
|
if (Object.keys(patch).some(key => !(before.supported as string[]).includes(key))
|
|
79
80
|
|| (before.kind === "codex" && enabled !== undefined)) throw new CliUsageError("The selected pool does not support a requested field.");
|
|
80
81
|
if (!Object.keys(patch).length) return output(before, json);
|
|
@@ -157,10 +158,18 @@ export async function handleAccountPolicyCommand(sub: Subcommand, args: string[]
|
|
|
157
158
|
if (rest.includes("--json")) usage();
|
|
158
159
|
if (sub === "pool") return pool(rest, json, deps);
|
|
159
160
|
if (sub !== "anthropic-reset-grants") return codex(sub, rest, json, deps);
|
|
161
|
+
const selectedProvider = option(rest, "--provider");
|
|
162
|
+
const provider = selectedProvider ?? "anthropic";
|
|
163
|
+
if (!isAnthropicInstanceId(provider)) usage();
|
|
160
164
|
const id = rest.length ? selector(rest.shift()) : undefined;
|
|
161
165
|
done(rest);
|
|
162
166
|
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
163
|
-
|
|
167
|
+
// Omission preserves legacy A request syntax; explicit calls verify the echoed instance.
|
|
168
|
+
const query = new URLSearchParams();
|
|
169
|
+
if (id) query.set("accountId", id);
|
|
170
|
+
if (selectedProvider !== undefined) query.set("provider", provider);
|
|
171
|
+
const result = parseDto(grantsSchema, await request(`/api/anthropic/reset-grants${query.size ? `?${query}` : ""}`, pinned));
|
|
172
|
+
if (selectedProvider !== undefined || result.provider !== undefined) requireEqual(result.provider, provider);
|
|
164
173
|
if (id) requireEqual(result.accountId, id);
|
|
165
174
|
return output(result, json, "Reset-grant status read; no grant was consumed.");
|
|
166
175
|
}, READ_ERRORS);
|
package/src/cli/account.ts
CHANGED
|
@@ -52,7 +52,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
52
52
|
ocx account clear <provider> [--json]
|
|
53
53
|
ocx account refresh <provider> [--json]
|
|
54
54
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
55
|
-
ocx account auto-switch anthropic <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
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
57
|
ocx account priority <provider> <account-id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
58
58
|
ocx account pause <provider> <account-id|alias|main> [--json]
|
|
@@ -60,7 +60,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
60
60
|
ocx account pause-exhausted <provider> [--json]
|
|
61
61
|
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
62
62
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
63
|
-
ocx account routes anthropic [--file <json-file>|--clear] [--json]
|
|
63
|
+
ocx account routes <anthropic|anthropic2> [--file <json-file>|--clear] [--json]
|
|
64
64
|
ocx account remove <provider> <account-or-key-id|alias|main> --yes [--json]
|
|
65
65
|
ocx account clear-cooldown <provider> <account-id|alias|main> [--json]
|
|
66
66
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -70,7 +70,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
70
70
|
ocx account pool <provider> [--enabled on|off] [--threshold N] [--strategy NAME] [--sticky N] [--quota-window W] [--json]
|
|
71
71
|
ocx account credits openai <ID on|off|--all on|off> [--json]
|
|
72
72
|
ocx account quota-activation openai ID --window fiveHour|weekly <on|off> [--json]
|
|
73
|
-
ocx account anthropic-reset-grants [ID] [--json]
|
|
73
|
+
ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]
|
|
74
74
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
75
75
|
ocx account cancel <provider> [--flow <flow-id>] [--json] (--flow required for codex)
|
|
76
76
|
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
package/src/cli/agent.ts
CHANGED
|
@@ -30,7 +30,7 @@ const USAGE = `Usage:
|
|
|
30
30
|
ocx agent subagents <status|set|clear> [model,model...] [--json]
|
|
31
31
|
ocx agent subagents force <model|-> [--json]
|
|
32
32
|
ocx agent fallback <status|set|clear> [model,model...] [--poll-ms <5000-600000>] [--json]
|
|
33
|
-
ocx agent roles [status|set <role> <model
|
|
33
|
+
ocx agent roles [status|set <role> <model> [--effort <level>]|suggest [--model <id>] [--apply]] [--json]
|
|
34
34
|
ocx agent sidecar <status|web|vision> [--list] [--model <id|->]
|
|
35
35
|
[--backend web:<openai|anthropic|xai|gemini|exa|-> vision:<openai|anthropic|routed|->]
|
|
36
36
|
[--reasoning <level>] [--max-descriptions <n> (vision)] [--enabled <on|off>]
|
|
@@ -311,7 +311,7 @@ async function sidecar(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
311
311
|
interface CodexAgentRolesStatus {
|
|
312
312
|
lazycodex?: { detected?: boolean };
|
|
313
313
|
omoJsonc?: { state?: string } | null;
|
|
314
|
-
roles?: Array<{ role: string; model: string | null; omoJsoncModel: string | null }>;
|
|
314
|
+
roles?: Array<{ role: string; model: string | null; effort?: string | null; omoJsoncModel: string | null }>;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
interface CodexRoleProposal {
|
|
@@ -393,7 +393,7 @@ async function roles(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
393
393
|
const rows = result.roles ?? [];
|
|
394
394
|
printData(result, wantsJson, [
|
|
395
395
|
"omo (Codex / LazyCodex): detected",
|
|
396
|
-
...(rows.length === 0 ? ["No Codex agent roles found."] : rows.map(row => `${row.role}: ${row.model ?? "(no model pin)"}`)),
|
|
396
|
+
...(rows.length === 0 ? ["No Codex agent roles found."] : rows.map(row => `${row.role}: ${row.model ?? "(no model pin)"}${row.effort ? ` (${row.effort})` : ""}`)),
|
|
397
397
|
`omo.jsonc: ${result.omoJsonc?.state ?? "unknown"}`,
|
|
398
398
|
]);
|
|
399
399
|
return;
|
|
@@ -403,18 +403,22 @@ async function roles(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
403
403
|
return;
|
|
404
404
|
}
|
|
405
405
|
if (action !== "set") throw new CliUsageError(`unknown roles action ${action}`, USAGE);
|
|
406
|
+
const effort = takeOption(args, "--effort");
|
|
407
|
+
// An empty value is a supplied flag, not an absent one: dropping it would change the model and
|
|
408
|
+
// silently ignore the effort the operator asked for.
|
|
409
|
+
if (effort !== undefined && effort.trim() === "") throw new CliUsageError("--effort needs a reasoning level", USAGE);
|
|
406
410
|
const role = args.shift();
|
|
407
411
|
const model = args.shift();
|
|
408
412
|
if (!role || !model) throw new CliUsageError("a role and a model are required", USAGE);
|
|
409
413
|
rejectArgs(args, USAGE);
|
|
410
414
|
const result = await runtimeRequest<{ toml?: { status?: string }; omoJsonc?: { status?: string } }>(
|
|
411
415
|
`/api/codex-agent-roles/${encodeURIComponent(role)}`,
|
|
412
|
-
{ method: "PUT", body: JSON.stringify({ model }) },
|
|
416
|
+
{ method: "PUT", body: JSON.stringify(effort !== undefined ? { model, effort } : { model }) },
|
|
413
417
|
deps,
|
|
414
418
|
);
|
|
415
419
|
const omo = result.omoJsonc?.status;
|
|
416
420
|
printData(result, wantsJson, [
|
|
417
|
-
`${role}: ${model} (role TOML ${result.toml?.status ?? "unknown"})`,
|
|
421
|
+
`${role}: ${model}${effort ? ` (${effort})` : ""} (role TOML ${result.toml?.status ?? "unknown"})`,
|
|
418
422
|
omo === "skipped_comments"
|
|
419
423
|
? "omo.jsonc: not written, because it contains comments that a rewrite would lose."
|
|
420
424
|
: `omo.jsonc: ${omo ?? "unknown"}`,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery guidance for Aside profiles whose sync preference is off while their managed block is stale (#6757).
|
|
3
|
+
*
|
|
4
|
+
* Sync refreshes only profiles whose preference is on, and an OFF preference is the operator's decision, so an
|
|
5
|
+
* "off (stale)" profile needs an explicit, reviewed enable. This module renders text only: it issues no request,
|
|
6
|
+
* reads no state and never changes a preference. Sync cannot name real profile IDs without a status read that may
|
|
7
|
+
* run integration maintenance, so its empty result points at status, which already holds the rows.
|
|
8
|
+
*/
|
|
9
|
+
export const ASIDE_SYNC_EMPTY_LINES: readonly string[] = [
|
|
10
|
+
"No eligible Aside profiles to synchronize. Sync refreshes only profiles whose sync preference is on.",
|
|
11
|
+
"List profiles with: ocx integration client status --client aside",
|
|
12
|
+
"For a profile shown as off (stale), review: ocx integration client preview --client aside --operation apply --profile <N>",
|
|
13
|
+
"Then, if the preview permits the change and you accept it: ocx integration client enable --client aside --profile <N>",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function recoveryProfileId(row: unknown): number | null {
|
|
17
|
+
if (!row || typeof row !== "object") return null;
|
|
18
|
+
const { profileId, enabled, state } = row as Record<string, unknown>;
|
|
19
|
+
// Only an explicit OFF beside a stale block qualifies. Conflict and unsafe blocks need inspection, not an enable.
|
|
20
|
+
if (enabled !== false || state !== "stale") return null;
|
|
21
|
+
return typeof profileId === "number" && Number.isSafeInteger(profileId) && profileId >= 0 && !Object.is(profileId, -0)
|
|
22
|
+
? profileId : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Per-profile preview-then-enable commands for status rows that are off with a stale managed block. */
|
|
26
|
+
export function asideProfileRecoveryLines(rows: readonly unknown[]): string[] {
|
|
27
|
+
const ids = [...new Set(rows.map(recoveryProfileId).filter((id): id is number => id !== null))];
|
|
28
|
+
return ids.flatMap(id => [
|
|
29
|
+
`Aside profile ${id} is off (stale). To reconnect it, review: ocx integration client preview --client aside --operation apply --profile ${id}`,
|
|
30
|
+
`Then, if the preview permits the change and you accept it: ocx integration client enable --client aside --profile ${id}`,
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -184,10 +184,10 @@ export const ACCOUNT_CAPABILITIES: readonly Capability[] = [
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
command: ["account","anthropic-reset-grants"],
|
|
187
|
-
usage: "ocx account anthropic-reset-grants [account-id] [--json]",
|
|
187
|
+
usage: "ocx account anthropic-reset-grants [account-id] [--provider anthropic|anthropic2] [--json]",
|
|
188
188
|
summary: "Read Anthropic reset-grant eligibility and pending status without consuming a grant.",
|
|
189
189
|
routes: [{"method":"GET","path":"/api/anthropic/reset-grants"}],
|
|
190
|
-
flags: [{"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
|
|
190
|
+
flags: [{ name: "--provider", value: "string", summary: "Anthropic OAuth instance: anthropic or anthropic2; omission keeps the legacy Anthropic pool." }, {"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
|
|
191
191
|
mutates: false,
|
|
192
192
|
json: "envelope",
|
|
193
193
|
details: ["Optional identity is the exact Anthropic account ID. Omission delegates active/fallback account choice to the server, not the Codex alias resolver.","This GET can refresh access/status information upstream. Empty grants, unavailable status, pending operation and unavailable journal remain distinct. Consume remains a GUI-session-only human handoff; no session acquisition or retry is performed."],
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { Capability } from "./capability-types";
|
|
2
2
|
|
|
3
3
|
export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
4
|
+
{
|
|
5
|
+
command: ["message", "sessions"],
|
|
6
|
+
summary: "Discover already-loaded local Codex sessions without reading conversation history.",
|
|
7
|
+
routes: [], flags: [{ name: "--json", value: "boolean", summary: "Emit a versioned local session directory." }],
|
|
8
|
+
mutates: false, json: "envelope",
|
|
9
|
+
bannerLines: ["ocx message <sub> Loaded local Codex sessions and queued peer messages (sessions|send)"],
|
|
10
|
+
details: ["Linux/macOS Unix socket only. Uses effective CODEX_HOME; starts no daemon and returns no partial directory."],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
command: ["message", "send"],
|
|
14
|
+
summary: "Submit one correlated peer message to an exact loaded local Codex destination.",
|
|
15
|
+
routes: [], mutates: true, json: "envelope",
|
|
16
|
+
flags: [
|
|
17
|
+
{ name: "--thread", value: "string", summary: "Exact loaded UUID; choose this or --name." },
|
|
18
|
+
{ name: "--name", value: "string", summary: "Unique exact loaded name; choose this or --thread." },
|
|
19
|
+
{ name: "--stdin", value: "boolean", required: true, summary: "Read at most 16 KiB of UTF-8 message text." },
|
|
20
|
+
{ name: "--kind", value: "string", summary: "request (default), response or notification." },
|
|
21
|
+
{ name: "--in-reply-to", value: "string", summary: "Request message UUID; required only for a response." },
|
|
22
|
+
{ name: "--json", value: "boolean", summary: "Emit a receipt with not_sent, queued or unknown status." },
|
|
23
|
+
],
|
|
24
|
+
details: ["Requires daemon support for experimental thread/queue/add. Sender context comes from CODEX_THREAD_ID, not an authentication claim.",
|
|
25
|
+
"queued means submitted, not processed. unknown must not be replayed; no automatic retry, daemon start or thread resume.",
|
|
26
|
+
"Exit 0: queued; 1: not sent; 3: unknown; 64: invalid usage. No remote/Claude transport or skill installation."],
|
|
27
|
+
},
|
|
4
28
|
{
|
|
5
29
|
command: ["agent", "status"],
|
|
6
30
|
usage: "ocx agent status [--json]",
|