@bitkyc08/opencodex 2.41.0 → 2.43.0-preview.20260906
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-VGeQEZ_v.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +54 -24
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/identity.ts +8 -2
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +259 -29
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +47 -13
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +49 -9
- package/src/cli/account-extended.ts +7 -1
- package/src/cli/capabilities.ts +15 -4
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/observe.ts +3 -1
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +272 -32
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +44 -5
- package/src/codex/catalog/metadata.ts +149 -14
- package/src/codex/catalog/native-models.ts +116 -4
- package/src/codex/catalog/parsing.ts +122 -8
- package/src/codex/catalog/provider-fetch.ts +154 -23
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/catalog.ts +1 -1
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +169 -0
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +99 -34
- package/src/codex/injected-marker.ts +30 -4
- package/src/codex/journal.ts +14 -0
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +474 -178
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/account-quota-rank.ts +40 -1
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/chatgpt-device.ts +187 -0
- package/src/oauth/chatgpt.ts +31 -4
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +140 -29
- package/src/oauth/log.ts +3 -0
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/muse-subscription-usage.ts +95 -0
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +625 -98
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +122 -28
- package/src/server/live.ts +18 -4
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +45 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +510 -57
- package/src/server/responses/empty-completion-guard.ts +4 -0
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/request.ts +8 -0
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-B2YjLA-i.css +0 -1
- package/gui/dist/assets/index-aPup8CKb.js +0 -112
package/src/claude/inbound.ts
CHANGED
|
@@ -10,130 +10,24 @@
|
|
|
10
10
|
* - top_k is accepted and silently dropped (no Responses equivalent, CCR parity).
|
|
11
11
|
*/
|
|
12
12
|
import type { OcxClaudeCodeConfig } from "../types";
|
|
13
|
-
import { isAnthropicOutputSchema } from "../adapters/anthropic-output-schema";
|
|
14
|
-
import { resolveAlias } from "./alias";
|
|
15
|
-
import { stripOneMillionMarker } from "./context-windows";
|
|
16
|
-
import { resolveDesktop3pAlias } from "./desktop-3p";
|
|
17
|
-
import { isClaudeWebSearchToolName } from "./outbound";
|
|
18
13
|
import { createHash } from "node:crypto";
|
|
19
14
|
|
|
20
|
-
export
|
|
15
|
+
export { AnthropicRequestError } from "./inbound-records";
|
|
16
|
+
export { resolveInboundModel, effortForThinkingBudget, effortFromOutputConfig, extractOcxRouteDirective, extractOcxEffortDirective } from "./inbound-model-options";
|
|
17
|
+
import { AnthropicRequestError, isRec, type Rec } from "./inbound-records";
|
|
18
|
+
import { resolveInboundModel, effortForThinkingBudget, effortFromOutputConfig, formatFromOutputConfig } from "./inbound-model-options";
|
|
19
|
+
import { systemToInstructions, toolsToResponses, toolChoiceToResponses } from "./inbound-content-options";
|
|
21
20
|
|
|
22
|
-
type Rec = Record<string, unknown>;
|
|
23
21
|
|
|
24
|
-
function isRec(v: unknown): v is Rec {
|
|
25
|
-
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isClaudeClassifierModel(model: string): boolean {
|
|
29
|
-
const stripped = model.replace(/-\d{8}$/, "");
|
|
30
|
-
return /^claude-opus-[45]/.test(stripped);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Explicitly configured classifier route for Claude Code Auto Mode safety checks (#1697).
|
|
35
|
-
*
|
|
36
|
-
* Only OPERATOR-DECLARED targets are used: `classifierModel`, then the ordered
|
|
37
|
-
* `classifierFallbacks`. Both are qualified `provider/model` strings the operator chose, so
|
|
38
|
-
* routing them crosses no boundary the operator did not ask for.
|
|
39
|
-
*
|
|
40
|
-
* Deliberately NOT here: inferring a provider from `claudeCode.model`. That value is the
|
|
41
|
-
* injected/default config slot, not the provider the live session actually selected, so it goes
|
|
42
|
-
* stale the moment the user changes the model picker -- and acting on it would silently move a
|
|
43
|
-
* classifier turn onto a provider with its own privacy and billing consequences. Live session
|
|
44
|
-
* affinity needs the request/session state this function does not have; it is tracked as
|
|
45
|
-
* follow-up work rather than approximated from static config.
|
|
46
|
-
*/
|
|
47
|
-
function configuredClassifierRoute(cc?: OcxClaudeCodeConfig): string | undefined {
|
|
48
|
-
const explicit = typeof cc?.classifierModel === "string" ? cc.classifierModel.trim() : "";
|
|
49
|
-
if (explicit.length > 0) return explicit;
|
|
50
|
-
if (Array.isArray(cc?.classifierFallbacks)) {
|
|
51
|
-
for (const candidate of cc.classifierFallbacks) {
|
|
52
|
-
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate.trim();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Alias first, then modelMap: exact id, then date-suffix-stripped (`-\d{8}$`), then classifier affinity/config, else passthrough. */
|
|
59
|
-
export function resolveInboundModel(model: string, cc?: OcxClaudeCodeConfig): string {
|
|
60
|
-
// Defensive: Desktop/CLI strip the [1m] context-variant marker client-side, but a
|
|
61
|
-
// leaking build must not break alias decode (devlog 138 — the 1M signal is the
|
|
62
|
-
// anthropic-beta header, never the id). Case-insensitive: the CLI matches /\[1m\]/i.
|
|
63
|
-
model = stripOneMillionMarker(model);
|
|
64
|
-
const aliased = resolveAlias(model);
|
|
65
|
-
if (aliased) return aliased;
|
|
66
|
-
// Desktop 3P aliases: claude-opus-4-{code} → provider/model route key
|
|
67
|
-
const desktop3p = resolveDesktop3pAlias(model);
|
|
68
|
-
if (desktop3p) {
|
|
69
|
-
// Native pseudo-provider returns bare slug; routed returns provider/model
|
|
70
|
-
const sep = desktop3p.indexOf("/");
|
|
71
|
-
if (sep > 0 && desktop3p.slice(0, sep) === "native") return desktop3p.slice(sep + 1);
|
|
72
|
-
return desktop3p;
|
|
73
|
-
}
|
|
74
|
-
const map = cc?.modelMap ?? {};
|
|
75
|
-
const exact = map[model];
|
|
76
|
-
if (typeof exact === "string" && exact.length > 0) return exact;
|
|
77
|
-
const stripped = model.replace(/-\d{8}$/, "");
|
|
78
|
-
const dateless = map[stripped];
|
|
79
|
-
if (typeof dateless === "string" && dateless.length > 0) return dateless;
|
|
80
|
-
|
|
81
|
-
// Claude Code Auto Mode classifier routing (#1697). Bare classifier checks such as
|
|
82
|
-
// `claude-opus-5` carry no provider, so without this they fall through to defaultProvider --
|
|
83
|
-
// which may not speak Anthropic at all. Only an operator-declared target is used.
|
|
84
|
-
if (isClaudeClassifierModel(model)) {
|
|
85
|
-
const configured = configuredClassifierRoute(cc);
|
|
86
|
-
if (configured) return configured;
|
|
87
|
-
}
|
|
88
|
-
return model;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** budget_tokens ladder -> Responses reasoning effort (003: real API min is 1024; never forward raw). */
|
|
92
|
-
export function effortForThinkingBudget(budget: number): string {
|
|
93
|
-
if (budget <= 4096) return "low";
|
|
94
|
-
if (budget <= 16384) return "medium";
|
|
95
|
-
return "high";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Adaptive-thinking wire (devlog 080): Claude Code /effort sends
|
|
100
|
-
* `thinking:{type:"adaptive"}` + `output_config:{effort:"..."}` (verified by local
|
|
101
|
-
* capture of claude 2.1.207 and CLIProxyAPI#1540). Forward the level verbatim when it
|
|
102
|
-
* is a known Responses effort; unknown strings are dropped so downstream defaults win.
|
|
103
|
-
*/
|
|
104
|
-
const OUTPUT_CONFIG_EFFORTS = new Set(["minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
105
|
-
export function effortFromOutputConfig(outputConfig: unknown): string | undefined {
|
|
106
|
-
if (!isRec(outputConfig)) return undefined;
|
|
107
|
-
const effort = outputConfig.effort;
|
|
108
|
-
return typeof effort === "string" && OUTPUT_CONFIG_EFFORTS.has(effort) ? effort : undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function formatFromOutputConfig(outputConfig: unknown): Rec | undefined {
|
|
112
|
-
if (!isRec(outputConfig) || !isRec(outputConfig.format)) return undefined;
|
|
113
|
-
const format = outputConfig.format;
|
|
114
|
-
if (
|
|
115
|
-
format.type !== "json_schema"
|
|
116
|
-
|| !isRec(format.schema)
|
|
117
|
-
|| !isAnthropicOutputSchema(format.schema)
|
|
118
|
-
) return undefined;
|
|
119
|
-
return { type: "json_schema", name: "response", schema: format.schema };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function systemToInstructions(system: unknown): string | undefined {
|
|
123
|
-
if (typeof system === "string") return system.length > 0 ? system : undefined;
|
|
124
|
-
if (Array.isArray(system)) {
|
|
125
|
-
const parts: string[] = [];
|
|
126
|
-
for (const block of system) {
|
|
127
|
-
if (isRec(block) && block.type === "text" && typeof block.text === "string") parts.push(block.text);
|
|
128
|
-
}
|
|
129
|
-
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
130
|
-
}
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
22
|
|
|
134
23
|
function imageBlockToInputImage(block: Rec): Rec | null {
|
|
135
24
|
const source = block.source;
|
|
136
25
|
if (!isRec(source)) return null;
|
|
26
|
+
if (source.type === "file") {
|
|
27
|
+
throw new AnthropicRequestError(
|
|
28
|
+
"File-backed images require native Anthropic passthrough; use base64 or URL images on translated routes.",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
137
31
|
if (source.type === "base64" && typeof source.data === "string") {
|
|
138
32
|
const media = typeof source.media_type === "string" ? source.media_type : "image/png";
|
|
139
33
|
return { type: "input_image", image_url: `data:${media};base64,${source.data}` };
|
|
@@ -194,47 +88,6 @@ export function effectiveBlockedSkillNames(cc?: Pick<OcxClaudeCodeConfig, "block
|
|
|
194
88
|
.filter(name => name.length > 0))];
|
|
195
89
|
}
|
|
196
90
|
|
|
197
|
-
/**
|
|
198
|
-
* ocx-route directive (devlog 072): injected agent-definition bodies carry
|
|
199
|
-
* `<!-- ocx-route: <model> -->` because Claude Code 2.1.207 ignores custom
|
|
200
|
-
* gateway ids in agent frontmatter (live-proven fallback to sonnet). The body
|
|
201
|
-
* rides the subagent's system prompt, so the proxy re-routes here. Only the
|
|
202
|
-
* FIRST directive wins; the scan is bounded to the system field.
|
|
203
|
-
*/
|
|
204
|
-
const OCX_ROUTE_RE = /<!--\s*ocx-route:\s*([^\s]+)\s*-->/;
|
|
205
|
-
const OCX_EFFORT_RE = /<!--\s*ocx-effort:\s*(low|medium|high|xhigh|max)\s*-->/;
|
|
206
|
-
|
|
207
|
-
function systemText(body: unknown): string | null {
|
|
208
|
-
if (!isRec(body)) return null;
|
|
209
|
-
const system = body.system;
|
|
210
|
-
if (typeof system === "string") return system || null;
|
|
211
|
-
if (!Array.isArray(system)) return null;
|
|
212
|
-
const text = system
|
|
213
|
-
.filter((b): b is Rec => isRec(b) && b.type === "text" && typeof b.text === "string")
|
|
214
|
-
.map(b => b.text as string)
|
|
215
|
-
.join("\n");
|
|
216
|
-
return text || null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function extractOcxRouteDirective(body: unknown): string | null {
|
|
220
|
-
const text = systemText(body);
|
|
221
|
-
if (!text) return null;
|
|
222
|
-
const match = OCX_ROUTE_RE.exec(text);
|
|
223
|
-
return match ? match[1]! : null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Claude Code 2.1.220 collapses custom-agent frontmatter `effort: max` and
|
|
228
|
-
* `effort: xhigh` into the legacy `thinking.budget_tokens` shape. Preserve the
|
|
229
|
-
* exact generated-agent setting through the same trusted system-body channel as
|
|
230
|
-
* ocx-route so the inbound translator can restore `output_config.effort`.
|
|
231
|
-
*/
|
|
232
|
-
export function extractOcxEffortDirective(body: unknown): NonNullable<OcxClaudeCodeConfig["subagentEffort"]> | null {
|
|
233
|
-
const text = systemText(body);
|
|
234
|
-
if (!text) return null;
|
|
235
|
-
const match = OCX_EFFORT_RE.exec(text);
|
|
236
|
-
return match ? match[1] as NonNullable<OcxClaudeCodeConfig["subagentEffort"]> : null;
|
|
237
|
-
}
|
|
238
91
|
|
|
239
92
|
/** Injected-skill payloads below this size are never stubbed (not worth it). */
|
|
240
93
|
const SKILL_ELISION_MIN_CHARS = 10_000;
|
|
@@ -391,51 +244,6 @@ function assistantMessageToItems(content: unknown, input: Rec[]): void {
|
|
|
391
244
|
flush();
|
|
392
245
|
}
|
|
393
246
|
|
|
394
|
-
function toolsToResponses(tools: unknown): Rec[] | undefined {
|
|
395
|
-
if (!Array.isArray(tools) || tools.length === 0) return undefined;
|
|
396
|
-
const out: Rec[] = [];
|
|
397
|
-
for (const raw of tools) {
|
|
398
|
-
if (!isRec(raw)) continue;
|
|
399
|
-
const type = typeof raw.type === "string" ? raw.type : "";
|
|
400
|
-
if (type.startsWith("web_search")) {
|
|
401
|
-
out.push({ type: "web_search" }); // hosted sidecar path
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
if (typeof raw.name === "string" && raw.name.length > 0 && isRec(raw.input_schema)) {
|
|
405
|
-
out.push({
|
|
406
|
-
type: "function",
|
|
407
|
-
name: raw.name,
|
|
408
|
-
...(typeof raw.description === "string" ? { description: raw.description } : {}),
|
|
409
|
-
parameters: raw.input_schema as Record<string, unknown>,
|
|
410
|
-
});
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
// Other server tools (bash_*, text_editor_*, ...) have no routed equivalent: drop.
|
|
414
|
-
}
|
|
415
|
-
return out.length > 0 ? out : undefined;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function toolChoiceToResponses(choice: unknown, body: Rec): void {
|
|
419
|
-
if (!isRec(choice)) return;
|
|
420
|
-
if (choice.disable_parallel_tool_use === true) body.parallel_tool_calls = false;
|
|
421
|
-
switch (choice.type) {
|
|
422
|
-
case "auto": body.tool_choice = "auto"; break;
|
|
423
|
-
case "none": body.tool_choice = "none"; break;
|
|
424
|
-
case "any": body.tool_choice = "required"; break;
|
|
425
|
-
case "tool":
|
|
426
|
-
if (typeof choice.name !== "string" || choice.name.length === 0) {
|
|
427
|
-
throw new AnthropicRequestError("tool_choice.tool requires a name");
|
|
428
|
-
}
|
|
429
|
-
// Anthropic represents hosted WebSearch as a named tool choice, while
|
|
430
|
-
// Responses requires the choice type to match the hosted declaration.
|
|
431
|
-
// Preserve forced-tool intent rather than weakening it to `auto`.
|
|
432
|
-
body.tool_choice = isClaudeWebSearchToolName(choice.name)
|
|
433
|
-
? { type: "web_search" }
|
|
434
|
-
: { type: "function", name: choice.name };
|
|
435
|
-
break;
|
|
436
|
-
default: break;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
247
|
|
|
440
248
|
/** Recursive canonical JSON (keys sorted at every depth) — stable cache-cohort input. */
|
|
441
249
|
function canonicalJson(value: unknown): string {
|
package/src/claude/model-info.ts
CHANGED
|
@@ -111,9 +111,32 @@ export function buildAnthropicModelInfos(
|
|
|
111
111
|
aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
|
|
112
112
|
nativeContextCap?: NativeContextLimitsInput,
|
|
113
113
|
fastMode?: boolean,
|
|
114
|
+
// Presence is the feature gate: the caller passes undefined when `fastRows` is off, so a
|
|
115
|
+
// default install publishes nothing. The predicate answers ELIGIBILITY, not enablement.
|
|
116
|
+
fastRows?: (model: CatalogModel | { provider: string; id: string }) => boolean,
|
|
114
117
|
): AnthropicModelInfo[] {
|
|
115
118
|
const out: AnthropicModelInfo[] = [];
|
|
116
119
|
const seen = new Set<string>();
|
|
120
|
+
// Every id the loops below will really emit, computed BEFORE either runs. `seen` alone is
|
|
121
|
+
// not enough: it grows as they run, so whether a synthetic id collided with a real one
|
|
122
|
+
// would depend on iteration order. With both `foo` and a real `foo--fast` in the roster,
|
|
123
|
+
// the synthetic id for `foo` IS the real model's id, and whichever ran first would win it.
|
|
124
|
+
const realDiscoveryIds = new Set<string>([
|
|
125
|
+
...nativeSlugs.map(slug => (
|
|
126
|
+
idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug)
|
|
127
|
+
)),
|
|
128
|
+
...routedModels.map(m => {
|
|
129
|
+
// The same asymmetry the routed loop applies: readable uses the LISTED id, so a
|
|
130
|
+
// fastMode-rewritten Cursor row is counted under the id it is really published as,
|
|
131
|
+
// while Desktop 3P hashes the RAW id.
|
|
132
|
+
const listed = fastMode === true && m.provider === "cursor" && idStyle === "readable"
|
|
133
|
+
? cursorFastIdFor(m.id) ?? m.id
|
|
134
|
+
: m.id;
|
|
135
|
+
return idStyle === "readable"
|
|
136
|
+
? claudeCodeAlias(m.provider, listed)
|
|
137
|
+
: aliasForRoute(m.provider, m.id);
|
|
138
|
+
}),
|
|
139
|
+
]);
|
|
117
140
|
// [1m] picker variant (devlog 260712 B1): Claude Code accounts exactly 1M for ids
|
|
118
141
|
// carrying the marker (2.1.207 binary: /\[1m\]/i → 1e6, compaction preserved), so
|
|
119
142
|
// ONLY models with an authoritative >=1M window get a second selectable row —
|
|
@@ -140,6 +163,21 @@ export function buildAnthropicModelInfos(
|
|
|
140
163
|
: ONE_MILLION;
|
|
141
164
|
out.push({ ...base, id, display_name: `${base.display_name} · 1M`, max_input_tokens: advertised });
|
|
142
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* Publish a Fast sibling beside a row, following `push1mVariant` rather than the
|
|
168
|
+
* `fastMode` rewrite below: `fastMode` is a global switch with no per-request choice, so
|
|
169
|
+
* it REPLACES the listed id, while a selector has to leave the default pickable beside it.
|
|
170
|
+
*
|
|
171
|
+
* Because it only ADDS a row, it is safe for the Desktop 3P hashed style too — the
|
|
172
|
+
* exclusion `fastMode` needs exists because rewriting a hash strands a saved selection.
|
|
173
|
+
*/
|
|
174
|
+
const pushFastVariant = (base: AnthropicModelInfo) => {
|
|
175
|
+
const id = `${base.id}--fast`;
|
|
176
|
+
// A real model always wins its own id, whatever the iteration order.
|
|
177
|
+
if (realDiscoveryIds.has(id) || seen.has(id)) return;
|
|
178
|
+
seen.add(id);
|
|
179
|
+
out.push({ ...base, id, display_name: `${base.display_name} · Fast` });
|
|
180
|
+
};
|
|
143
181
|
for (const slug of nativeSlugs) {
|
|
144
182
|
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);
|
|
145
183
|
if (seen.has(id)) continue;
|
|
@@ -151,6 +189,9 @@ export function buildAnthropicModelInfos(
|
|
|
151
189
|
const info = modelInfo(id, `${slug} (native)`, nativeEffectiveLadder(slug), true, nativeMaxInput ?? nativeWindow);
|
|
152
190
|
out.push(info);
|
|
153
191
|
push1mVariant(info, nativeWindow, nativeMaxInput);
|
|
192
|
+
// Natives too, not only routed rows: gpt-5.6-sol is the flagship Fast model, and
|
|
193
|
+
// omitting it would leave this surface without the model the feature exists for.
|
|
194
|
+
if (fastRows?.({ provider: "native", id: slug }) === true) pushFastVariant(info);
|
|
154
195
|
}
|
|
155
196
|
for (const m of routedModels) {
|
|
156
197
|
// Global Fast has no toggle on this surface, so the fast identity is what gets listed —
|
|
@@ -180,6 +221,10 @@ export function buildAnthropicModelInfos(
|
|
|
180
221
|
// Anthropic passthrough guard (audit 021 #3): never auto-widen canonical claude
|
|
181
222
|
// routes — only a genuine >=1M window earns the variant row there.
|
|
182
223
|
push1mVariant(info, m.contextWindow, routedMaxInput);
|
|
224
|
+
// The whole model is passed, not a (provider, id) pair: a combo row lives in its own
|
|
225
|
+
// namespace with no config.providers entry, so the caller classifies it from the
|
|
226
|
+
// aggregated supportsServiceTier the row already carries.
|
|
227
|
+
if (fastRows?.(m) === true) pushFastVariant(info);
|
|
183
228
|
}
|
|
184
229
|
return out;
|
|
185
230
|
}
|
package/src/cli/account-auth.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { writeSync } from "node:fs";
|
|
2
|
+
import { modelSelectionGuidance, modelSelectionNextSteps } from "./model-selection-guidance";
|
|
2
3
|
import { warnIfCodexCatalogRefreshPending } from "./account-catalog-refresh";
|
|
4
|
+
import { isCodexResetCreditOperationId } from "../codex/reset-credit-recovery";
|
|
3
5
|
import {
|
|
4
6
|
CliUsageError,
|
|
5
7
|
printData,
|
|
@@ -31,10 +33,15 @@ function writeStdoutFully(text: string): void {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const USAGE = `Usage:
|
|
34
|
-
ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
|
|
36
|
+
ocx account login <provider> [--id <account-id>] [--reauth] [--device] [--code -] [--no-wait] [--json]
|
|
35
37
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
36
38
|
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
37
|
-
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
39
|
+
ocx account reset-credits <account-id|main> [--consume --yes [--operation-id <uuid>]] [--json]
|
|
40
|
+
|
|
41
|
+
--device runs the OpenAI device-code login instead of the browser callback: use
|
|
42
|
+
it when the proxy has no browser or nothing can reach localhost:1455, such as a
|
|
43
|
+
headless or remote hub. Enter the printed code at the printed URL from any other
|
|
44
|
+
machine.
|
|
38
45
|
|
|
39
46
|
The redirect URL or authorization code is a short-lived credential. Pipe it in
|
|
40
47
|
rather than passing it as an argument, where it lands in shell history and is
|
|
@@ -54,6 +61,9 @@ interface LoginStart {
|
|
|
54
61
|
/** `-` means "read it from stdin", the documented way to pass a code silently. */
|
|
55
62
|
const STDIN_SENTINEL = "-";
|
|
56
63
|
|
|
64
|
+
/** Providers whose ONLY login is already a device flow; --device is redundant, not wrong. */
|
|
65
|
+
const DEVICE_NATIVE_PROVIDERS = new Set(["kimi", "nous", "github-copilot"]);
|
|
66
|
+
|
|
57
67
|
const ARGV_WARNING =
|
|
58
68
|
"warning: the authorization code was passed as a command-line argument, so it is now in your shell history and was visible in the process list while this ran. Pipe it on stdin instead, or pass `-` to read from stdin.";
|
|
59
69
|
|
|
@@ -86,10 +96,17 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
86
96
|
const wantsJson = takeFlag(args, "--json");
|
|
87
97
|
const noWait = takeFlag(args, "--no-wait");
|
|
88
98
|
const reauth = takeFlag(args, "--reauth");
|
|
99
|
+
const device = takeFlag(args, "--device");
|
|
89
100
|
const id = takeOption(args, "--id");
|
|
90
101
|
const suppliedCode = takeOptionWithSyntax(args, "--code");
|
|
91
102
|
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
92
103
|
rejectArgs(args, USAGE);
|
|
104
|
+
// kimi, nous, and github-copilot are already device flows, so --device is a
|
|
105
|
+
// true statement about them and is accepted as a no-op rather than an error.
|
|
106
|
+
// Anything else has no device grant at all and must fail loudly.
|
|
107
|
+
if (device && !CODEX_NAMES.has(provider) && !DEVICE_NATIVE_PROVIDERS.has(provider)) {
|
|
108
|
+
throw new CliUsageError(`--device is not supported for provider '${provider}'`, USAGE);
|
|
109
|
+
}
|
|
93
110
|
// Only resolve when --code was actually given: a plain `ocx account login`
|
|
94
111
|
// opens the browser flow and polls, and must not block on stdin.
|
|
95
112
|
const code = await resolveCode(suppliedCode, deps, false);
|
|
@@ -97,13 +114,18 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
97
114
|
if (CODEX_NAMES.has(provider)) {
|
|
98
115
|
const start = await runtimeRequest<LoginStart>("/api/codex-auth/login", {
|
|
99
116
|
method: "POST",
|
|
100
|
-
body: JSON.stringify({
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
...(id ? { id } : {}),
|
|
119
|
+
...(reauth ? { reauth: true } : {}),
|
|
120
|
+
...(device ? { device: true } : {}),
|
|
121
|
+
}),
|
|
101
122
|
}, deps);
|
|
102
123
|
if (!wantsJson) {
|
|
103
124
|
// One atomic pre-poll block, flushed synchronously so a piped parent
|
|
104
125
|
// reads the URL before the polling window starts (#1007).
|
|
105
126
|
const block = [
|
|
106
127
|
start.url ? `Open this URL to sign in:\n${start.url}` : "",
|
|
128
|
+
start.deviceCode ? `Device code: ${start.deviceCode}` : "",
|
|
107
129
|
start.instructions ?? "",
|
|
108
130
|
start.flowId ? `Flow: ${start.flowId}` : "",
|
|
109
131
|
].filter(line => line !== "").join("\n");
|
|
@@ -116,18 +138,23 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
116
138
|
}, deps);
|
|
117
139
|
}
|
|
118
140
|
if (noWait) {
|
|
119
|
-
|
|
141
|
+
printData({ ...start, modelSelection: modelSelectionNextSteps("openai", true) }, wantsJson, modelSelectionGuidance("openai", true));
|
|
120
142
|
return;
|
|
121
143
|
}
|
|
122
144
|
if (!start.flowId) throw new CliUsageError("login did not return a flow id");
|
|
123
|
-
|
|
145
|
+
// A device login is deliberately slow: the user leaves this machine to
|
|
146
|
+
// enter the code elsewhere. Match the 15-minute grant instead of giving up
|
|
147
|
+
// at minute five while it is still valid, plus settlement margin for the
|
|
148
|
+
// token exchange and credential write after the final poll.
|
|
149
|
+
const maxAttempts = device ? 480 : 150;
|
|
150
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
124
151
|
await Bun.sleep(2_000);
|
|
125
152
|
const state = await runtimeRequest<Record<string, unknown>>(
|
|
126
153
|
`/api/codex-auth/login-status?flowId=${encodeURIComponent(start.flowId)}${id ? `&accountId=${encodeURIComponent(id)}` : ""}${reauth ? "&reauth=1" : ""}`,
|
|
127
154
|
{}, deps,
|
|
128
155
|
);
|
|
129
156
|
if (state.status === "done") {
|
|
130
|
-
printData(state, wantsJson, [`Logged in${state.email ? ` as ${String(state.email)}` : ""}
|
|
157
|
+
printData({ ...state, modelSelection: modelSelectionNextSteps("openai") }, wantsJson, [`Logged in${state.email ? ` as ${String(state.email)}` : ""}.`, ...modelSelectionGuidance("openai")]);
|
|
131
158
|
if (!wantsJson) warnIfCodexCatalogRefreshPending(state);
|
|
132
159
|
return;
|
|
133
160
|
}
|
|
@@ -158,7 +185,7 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
158
185
|
}, deps);
|
|
159
186
|
}
|
|
160
187
|
if (noWait) {
|
|
161
|
-
|
|
188
|
+
printData({ ...start, modelSelection: modelSelectionNextSteps(provider, true) }, wantsJson, modelSelectionGuidance(provider, true));
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
164
191
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
@@ -166,7 +193,7 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
166
193
|
const state = await runtimeRequest<Record<string, unknown>>(`/api/oauth/status?provider=${encodeURIComponent(provider)}`, {}, deps);
|
|
167
194
|
if (state.error) throw new CliUsageError(String(state.error));
|
|
168
195
|
if (state.loggedIn === true) {
|
|
169
|
-
printData(state, wantsJson, [`Logged in to ${provider}
|
|
196
|
+
printData({ ...state, modelSelection: modelSelectionNextSteps(provider) }, wantsJson, [`Logged in to ${provider}.`, ...modelSelectionGuidance(provider)]);
|
|
170
197
|
return;
|
|
171
198
|
}
|
|
172
199
|
}
|
|
@@ -227,12 +254,25 @@ async function resetCredits(argv: string[], deps: RuntimeApiDeps): Promise<void>
|
|
|
227
254
|
const wantsJson = takeFlag(args, "--json");
|
|
228
255
|
const consume = takeFlag(args, "--consume");
|
|
229
256
|
const yes = takeFlag(args, "--yes");
|
|
257
|
+
// Before rejectArgs: takeOption splices its two tokens out of `args`.
|
|
258
|
+
const operationId = takeOption(args, "--operation-id");
|
|
230
259
|
if (!rawId) throw new CliUsageError("account id is required", USAGE);
|
|
231
260
|
if (consume && !yes) throw new CliUsageError("consuming a reset credit requires --yes", USAGE);
|
|
261
|
+
if (operationId !== undefined && !consume) {
|
|
262
|
+
throw new CliUsageError("--operation-id requires --consume", USAGE);
|
|
263
|
+
}
|
|
264
|
+
if (operationId !== undefined && !isCodexResetCreditOperationId(operationId)) {
|
|
265
|
+
throw new CliUsageError("--operation-id must be a UUIDv4", USAGE);
|
|
266
|
+
}
|
|
232
267
|
rejectArgs(args, USAGE);
|
|
233
268
|
const accountId = rawId === "main" ? "__main__" : rawId;
|
|
234
269
|
const result = consume
|
|
235
|
-
? await runtimeRequest("/api/codex-auth/reset-credits/consume", {
|
|
270
|
+
? await runtimeRequest("/api/codex-auth/reset-credits/consume", {
|
|
271
|
+
method: "POST",
|
|
272
|
+
// Spread, not `operationId: undefined`: the server distinguishes an absent
|
|
273
|
+
// key (legacy random id) from a caller who asked for a stable identity.
|
|
274
|
+
body: JSON.stringify({ accountId, ...(operationId === undefined ? {} : { operationId }) }),
|
|
275
|
+
}, deps)
|
|
236
276
|
: await runtimeRequest(`/api/codex-auth/reset-credits?accountId=${encodeURIComponent(accountId)}`, {}, deps);
|
|
237
277
|
printData(result, wantsJson);
|
|
238
278
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "../config";
|
|
2
|
+
import { hasPassiveAccountQuota } from "../providers/quota";
|
|
2
3
|
import { closeSync, openSync, readSync } from "node:fs";
|
|
3
4
|
import {
|
|
4
5
|
MAX_ACCOUNT_PRIORITY,
|
|
@@ -330,7 +331,12 @@ export async function cmdRefresh(args: string[], deps: AccountDeps): Promise<num
|
|
|
330
331
|
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
331
332
|
if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}`, result.status);
|
|
332
333
|
if (wantsJson) console.log(JSON.stringify({ provider: name, report: result.report }, null, 2));
|
|
333
|
-
else
|
|
334
|
+
else if (result.report) console.log(providerQuotaLine(name, result.report));
|
|
335
|
+
// A passive provider has no probe to run, so "no report available" reads as a
|
|
336
|
+
// failure of something that was never attempted. Say what is actually true.
|
|
337
|
+
else if (hasPassiveAccountQuota(name)) {
|
|
338
|
+
console.log(`${name} reports usage only during a streaming response; there is nothing to refresh. Run a request through this provider to update it, then see \`ocx account list ${name}\`.`);
|
|
339
|
+
} else console.log(`no quota report available for ${name}`);
|
|
334
340
|
return 0;
|
|
335
341
|
}
|
|
336
342
|
const result = await fetchCodexRows(deps, baseUrl, true);
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* top-level `const USAGE`, evaluated at import time, so a cycle back into this table
|
|
12
12
|
* would resolve to `undefined` under ESM rather than throwing -- silently emptying the
|
|
13
13
|
* usage text that `rejectArgs` hands to `CliUsageError`, in the exact error-reporting
|
|
14
|
-
* surface the CLI-operability issues are about. `tests/cli-capabilities.test.ts` asserts
|
|
14
|
+
* surface the CLI-operability issues are about. `tests/cli/cli-capabilities.test.ts` asserts
|
|
15
15
|
* the absence of those imports and that every rendered usage string is non-empty, so the
|
|
16
16
|
* failure mode is loud instead of degraded.
|
|
17
17
|
*
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `HEAD_CAPABILITIES`. They exit in the CLI head (`root.ts`) before dispatch and have no
|
|
20
20
|
* runner key, so listing them as ordinary capabilities would break the registry parity
|
|
21
21
|
* assertion that every canonical entry is a direct runner. `help` is excluded from
|
|
22
|
-
* `CLI_COMMANDS` deliberately -- `tests/cli-registry.test.ts` documents it as a
|
|
22
|
+
* `CLI_COMMANDS` deliberately -- `tests/cli/cli-registry.test.ts` documents it as a
|
|
23
23
|
* head-handled pseudo-case -- and that decision is preserved here rather than reversed.
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -153,6 +153,17 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
153
153
|
json: "envelope",
|
|
154
154
|
details: ["Reads local config; drives no management API route."],
|
|
155
155
|
},
|
|
156
|
+
{
|
|
157
|
+
command: ["provider", "resets"],
|
|
158
|
+
summary: "Recently detected quota resets and whether reset notifications are enabled.",
|
|
159
|
+
routes: [{ method: "GET", path: "/api/quota-resets" }],
|
|
160
|
+
flags: [
|
|
161
|
+
{ name: "--json", value: "boolean", summary: "Emit reset events as JSON." },
|
|
162
|
+
{ name: "--limit", value: "number", summary: "Limit returned events; defaults to 20, capped at 100." },
|
|
163
|
+
],
|
|
164
|
+
mutates: false,
|
|
165
|
+
json: "payload",
|
|
166
|
+
},
|
|
156
167
|
{
|
|
157
168
|
command: ["provider", "keychain"],
|
|
158
169
|
summary: "Move a provider's API key into the OS keychain, restore it, or report where it lives.",
|
|
@@ -247,7 +258,7 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
247
258
|
"A bare invocation reads and never writes.",
|
|
248
259
|
"The APPLIED value is echoed, not the requested one, so a server-side normalization stays visible.",
|
|
249
260
|
"Values are not re-validated in the CLI: the server owns the strategy names and the 1-100 sticky bound.",
|
|
250
|
-
"`anthropic`
|
|
261
|
+
"`anthropic` owns the full pool contract. Other OAuth providers reach the same endpoint with a generic subset (enabled/strategy/autoSwitchThreshold) whose settings persist but do not yet steer selection; `sticky` and `quotaWindow` are refused for them.",
|
|
251
262
|
],
|
|
252
263
|
},
|
|
253
264
|
{
|
|
@@ -274,7 +285,7 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
274
285
|
{ name: "--conversation", value: "string", summary: "Restrict to one conversation id (`--conversationId` is accepted too)." },
|
|
275
286
|
{ name: "--status", value: "string", summary: "An exact code (429) or a class (5xx)." },
|
|
276
287
|
{ name: "--limit", value: "number", summary: "Row cap; defaults to 200." },
|
|
277
|
-
{ name: "--follow", value: "boolean", summary: "
|
|
288
|
+
{ name: "--follow", value: "boolean", summary: "Poll for new rows; add --jsonl to emit JSONL." },
|
|
278
289
|
{ name: "--json", value: "boolean", summary: "Emit the server payload as JSON." },
|
|
279
290
|
{ name: "--jsonl", value: "boolean", summary: "Emit one row per line." },
|
|
280
291
|
],
|