@iislee/opencodex 2.31.0 → 2.35.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 +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -22,6 +22,10 @@ const USAGE = `Usage:
|
|
|
22
22
|
ocx models <enable|disable> <provider/model|native-model> [--native] [--json]
|
|
23
23
|
ocx models provider <name> <on|off> [--json]
|
|
24
24
|
ocx models selected <provider> [--set <id,id...>|--clear] [--json]
|
|
25
|
+
ocx models preset show [--provider <name>] [--json]
|
|
26
|
+
ocx models preset apply <provider> [--all] [--json]
|
|
27
|
+
ocx models new-policy [on|off] [--provider <name>] [--json]
|
|
28
|
+
ocx models new-arrivals [--json]
|
|
25
29
|
ocx models context <status|value <tokens> [--set-all]|provider <name> on [--value <tokens>]|provider <name> off|all <on|off>> [--json]
|
|
26
30
|
ocx models shadow <status|set> [model|-] [--enabled <on|off>] [--json]`;
|
|
27
31
|
|
|
@@ -162,6 +166,94 @@ async function selected(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
162
166
|
printData(result, wantsJson, [`${provider}: ${models.length ? models.join(", ") : "all models"}`]);
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
|
|
170
|
+
interface ModelPresetView {
|
|
171
|
+
mode: string;
|
|
172
|
+
appliedVersion?: number;
|
|
173
|
+
availableVersion: number;
|
|
174
|
+
presetIds: string[];
|
|
175
|
+
presetCount: number;
|
|
176
|
+
totalCount: number;
|
|
177
|
+
fallback?: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function presetLine(name: string, view: ModelPresetView): string {
|
|
181
|
+
const parts = [`${name}: mode=${view.mode}`];
|
|
182
|
+
if (view.appliedVersion !== undefined && view.appliedVersion !== view.availableVersion) {
|
|
183
|
+
parts.push(`applied v${view.appliedVersion}, available v${view.availableVersion}`);
|
|
184
|
+
} else {
|
|
185
|
+
parts.push(`preset v${view.availableVersion}`);
|
|
186
|
+
}
|
|
187
|
+
parts.push(`(${view.presetCount} of ${view.totalCount} models)`);
|
|
188
|
+
if (view.fallback) parts.push(`fallback=${view.fallback}`);
|
|
189
|
+
return parts.join(" ");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function preset(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
193
|
+
const args = [...argv];
|
|
194
|
+
const action = (args.shift() ?? "show").toLowerCase();
|
|
195
|
+
const wantsJson = takeFlag(args, "--json");
|
|
196
|
+
if (action === "show") {
|
|
197
|
+
const only = takeOption(args, "--provider")?.trim();
|
|
198
|
+
rejectArgs(args, USAGE);
|
|
199
|
+
const result = await runtimeRequest<{ providers?: Record<string, ModelPresetView> }>("/api/model-presets", {}, deps);
|
|
200
|
+
const providers = result.providers ?? {};
|
|
201
|
+
const entries = Object.entries(providers).filter(([name]) => !only || name === only);
|
|
202
|
+
const lines = entries.length > 0
|
|
203
|
+
? entries.map(([name, view]) => presetLine(name, view))
|
|
204
|
+
// A provider with no shipped preset is not an error: it simply has nothing to curate.
|
|
205
|
+
: [only ? `${only}: no model preset is shipped for this provider` : "no providers have a shipped model preset"];
|
|
206
|
+
printData(only ? providers[only] ?? {} : result, wantsJson, lines);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (action !== "apply") throw new CliUsageError(`unknown preset action '${action}'`, USAGE);
|
|
210
|
+
const provider = args.shift()?.trim();
|
|
211
|
+
const all = takeFlag(args, "--all");
|
|
212
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
213
|
+
rejectArgs(args, USAGE);
|
|
214
|
+
const mode = all ? "all" : "preset";
|
|
215
|
+
const result = await runtimeRequest<{ selected?: string[]; fallback?: string; appliedVersion?: number }>(
|
|
216
|
+
"/api/model-presets",
|
|
217
|
+
{ method: "PUT", body: JSON.stringify({ provider, mode }) },
|
|
218
|
+
deps,
|
|
219
|
+
);
|
|
220
|
+
const selectedIds = result.selected ?? [];
|
|
221
|
+
const line = result.fallback === "preset-empty"
|
|
222
|
+
// Never silently narrow to nothing: empty means ALL, so a zero-match preset keeps what was
|
|
223
|
+
// there and says so.
|
|
224
|
+
? `${provider}: preset matched no models — selection unchanged (fallback to all)`
|
|
225
|
+
: all
|
|
226
|
+
? `${provider}: showing all models (allowlist cleared)`
|
|
227
|
+
: `${provider}: preset v${result.appliedVersion ?? "?"} applied — ${selectedIds.length} models selected`;
|
|
228
|
+
printData(result, wantsJson, [line]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function newPolicy(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
232
|
+
const args = [...argv];
|
|
233
|
+
const state = args[0] && !args[0].startsWith("--") ? args.shift()!.toLowerCase() : undefined;
|
|
234
|
+
const provider = takeOption(args, "--provider")?.trim();
|
|
235
|
+
const wantsJson = takeFlag(args, "--json");
|
|
236
|
+
if (state !== undefined && state !== "on" && state !== "off") throw new CliUsageError("new policy must be on or off", USAGE);
|
|
237
|
+
rejectArgs(args, USAGE);
|
|
238
|
+
if (!state) {
|
|
239
|
+
const result = await runtimeRequest<{ policy: string; providers: Record<string, string> }>("/api/model-discovery", {}, deps);
|
|
240
|
+
const value = provider ? result.providers[provider] ?? "inherit" : result.policy;
|
|
241
|
+
printData(provider ? { provider, policy: value } : result, wantsJson, [`${provider ?? "global"}: ${value}`]);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const result = await runtimeRequest<{ baselineBootstrapped?: boolean }>("/api/model-discovery", {
|
|
245
|
+
method: "PUT", body: JSON.stringify({ policy: state, provider: provider ?? null }),
|
|
246
|
+
}, deps);
|
|
247
|
+
printData(result, wantsJson, [`${provider ?? "global"}: ${state}${result.baselineBootstrapped ? " (current models recorded as known)" : ""}`]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function newArrivals(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
251
|
+
const args = [...argv]; const wantsJson = takeFlag(args, "--json"); rejectArgs(args, USAGE);
|
|
252
|
+
const result = await runtimeRequest<{ recentArrivals: Record<string, Array<{ id: string; at: string; state: string }>> }>("/api/model-discovery", {}, deps);
|
|
253
|
+
const lines = Object.entries(result.recentArrivals).flatMap(([provider, rows]) => rows.map(row => `${provider}/${row.id} [${row.state}] ${row.at}`));
|
|
254
|
+
printData(result.recentArrivals, wantsJson, lines.length ? lines : ["no recent model arrivals"]);
|
|
255
|
+
}
|
|
256
|
+
|
|
165
257
|
async function context(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
166
258
|
const args = [...argv];
|
|
167
259
|
const action = (args.shift() ?? "status").toLowerCase();
|
|
@@ -236,6 +328,9 @@ export async function handleModelsRuntimeCommand(sub: string, argv: string[], de
|
|
|
236
328
|
else if (sub === "disable") action = () => visibility(false, argv, deps);
|
|
237
329
|
else if (sub === "provider") action = () => providerVisibility(argv, deps);
|
|
238
330
|
else if (sub === "selected") action = () => selected(argv, deps);
|
|
331
|
+
else if (sub === "preset") action = () => preset(argv, deps);
|
|
332
|
+
else if (sub === "new-policy") action = () => newPolicy(argv, deps);
|
|
333
|
+
else if (sub === "new-arrivals") action = () => newArrivals(argv, deps);
|
|
239
334
|
else if (sub === "context") action = () => context(argv, deps);
|
|
240
335
|
else if (sub === "shadow") action = () => shadow(argv, deps);
|
|
241
336
|
if (!action) return null;
|
package/src/cli/models.ts
CHANGED
|
@@ -5,8 +5,13 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { syncModelsToCodex } from "../codex/sync";
|
|
7
7
|
import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
canonicalizeReasoningEfforts,
|
|
10
|
+
configuredReasoningEfforts,
|
|
11
|
+
isDeclaredReasoningEffort,
|
|
12
|
+
modelRecordValue,
|
|
13
|
+
} from "../reasoning-effort";
|
|
14
|
+
import { encodedModelIdCollides, resolveSlugSelection, routedSlug } from "../providers/slug-codec";
|
|
10
15
|
import { knownModelIdsForProvider } from "../router";
|
|
11
16
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
12
17
|
import { modelInList, type OcxConfig, type OcxCustomModel } from "../types";
|
|
@@ -91,7 +96,6 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
91
96
|
const seen = new Set<string>();
|
|
92
97
|
const contextWindows = prov.modelContextWindows ?? {};
|
|
93
98
|
const inputModalities = prov.modelInputModalities ?? {};
|
|
94
|
-
const reasoningEfforts = prov.modelReasoningEfforts ?? {};
|
|
95
99
|
const globalContext = prov.contextWindow ?? null;
|
|
96
100
|
|
|
97
101
|
const addModel = (model: string, isDefault: boolean) => {
|
|
@@ -107,7 +111,13 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
107
111
|
// an exact `gpt-oss:120b` entry that lists "image", and the proxy rejects the image.
|
|
108
112
|
const noVision = modelInList(prov.noVisionModels, model);
|
|
109
113
|
const modalities = noVision ? ["text"] : (modelRecordValue(inputModalities, model) ?? null);
|
|
110
|
-
|
|
114
|
+
// Same reason, for the ladder: `configuredReasoningEfforts` is what the catalog
|
|
115
|
+
// (`provider-fetch`) and the effort cap (`effort-policy`) resolve through, and it
|
|
116
|
+
// does three things this expression did not — it returns [] for a noReasoningModels
|
|
117
|
+
// match, drops levels Codex does not declare, and re-adds tiers the wire map proves
|
|
118
|
+
// the model emits. Restating two of its five lines here reported a ladder the proxy
|
|
119
|
+
// strips, and unsanitized junk as a supported level.
|
|
120
|
+
const efforts = configuredReasoningEfforts(prov, model) ?? null;
|
|
111
121
|
|
|
112
122
|
entries.push({
|
|
113
123
|
provider: provName,
|
|
@@ -267,11 +277,17 @@ async function handleCustomRemove(args: string[]): Promise<void> {
|
|
|
267
277
|
|
|
268
278
|
const config = loadConfig();
|
|
269
279
|
const existing = config.customModels ?? [];
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
280
|
+
// Slug matching goes through the shared resolver so this command sees the same collision
|
|
281
|
+
// class catalog filtering and persisted sync see (#2491). `slugEquals` compares the raw and
|
|
282
|
+
// encoded spellings of ONE id, so a selector written in the native slash form matched only
|
|
283
|
+
// that row while the dash form matched both — the two relations disagreed on the same
|
|
284
|
+
// config. Removal stays exact-or-refuse: an ambiguous selector still aborts below, which is
|
|
285
|
+
// the right default for a destructive command.
|
|
286
|
+
const matchingIndexes = existing.flatMap((model, index) => {
|
|
287
|
+
if (!target.includes("/")) return model.id === target ? [index] : [];
|
|
288
|
+
const resolved = resolveSlugSelection(model.provider, target, [model.modelId]);
|
|
289
|
+
return resolved.matched.length > 0 ? [index] : [];
|
|
290
|
+
});
|
|
275
291
|
if (matchingIndexes.length === 0) fail(`custom model "${target}" not found`);
|
|
276
292
|
if (matchingIndexes.length > 1) {
|
|
277
293
|
fail(`custom model selector "${target}" is ambiguous; use the custom model id`);
|
|
@@ -412,7 +428,7 @@ export async function handleModels(args: string[]): Promise<void> {
|
|
|
412
428
|
handleCustomList(rest);
|
|
413
429
|
return;
|
|
414
430
|
}
|
|
415
|
-
if (["live", "edit", "enable", "disable", "provider", "selected", "context", "shadow"].includes(subcommand ?? "")) {
|
|
431
|
+
if (["live", "edit", "enable", "disable", "provider", "selected", "preset", "context", "shadow"].includes(subcommand ?? "")) {
|
|
416
432
|
const { handleModelsRuntimeCommand } = await import("./models-runtime");
|
|
417
433
|
const code = await handleModelsRuntimeCommand(subcommand!, rest);
|
|
418
434
|
if (code !== null) process.exitCode = code;
|
package/src/cli/observe.ts
CHANGED
|
@@ -10,14 +10,17 @@ import {
|
|
|
10
10
|
takeOption,
|
|
11
11
|
type RuntimeApiDeps,
|
|
12
12
|
} from "./runtime-api";
|
|
13
|
+
import { formatUsageReport } from "./usage-report";
|
|
14
|
+
import { USAGE_RANGES, USAGE_SURFACES } from "../usage/summary";
|
|
13
15
|
|
|
14
16
|
const USAGE = `Usage:
|
|
15
17
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
16
|
-
[--limit <n>] [--follow] [--json|--jsonl]
|
|
18
|
+
[--conversation <id>] [--limit <n>] [--follow] [--json|--jsonl]
|
|
17
19
|
ocx logs explain <request-id> [--json]
|
|
18
20
|
ocx logs rebuild-index
|
|
19
21
|
ocx logs index-status
|
|
20
|
-
ocx observe usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
22
|
+
ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
23
|
+
[--provider <name>] [--model <id>] [--json]
|
|
21
24
|
ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
|
|
22
25
|
ocx observe memory [--json]
|
|
23
26
|
ocx observe debug [--json]
|
|
@@ -47,7 +50,12 @@ function formatLog(row: LogEntry): string {
|
|
|
47
50
|
const route = [row.provider, row.model].filter(Boolean).join("/");
|
|
48
51
|
const status = row.status ?? row.statusCode ?? "?";
|
|
49
52
|
const duration = row.durationMs !== undefined ? `${String(row.durationMs)}ms` : "";
|
|
50
|
-
|
|
53
|
+
// The conversation id is shown because a conversation FILTER whose output never names the
|
|
54
|
+
// conversation is hard to trust: an empty result and a wrong-id result look identical (#2704).
|
|
55
|
+
const conversation = typeof row.conversationId === "string" && row.conversationId.length > 0
|
|
56
|
+
? `conv=${row.conversationId}`
|
|
57
|
+
: "";
|
|
58
|
+
return [time, String(status), route, duration, conversation].filter(Boolean).join(" ");
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -58,13 +66,16 @@ async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
58
66
|
const provider = takeOption(args, "--provider");
|
|
59
67
|
const model = takeOption(args, "--model");
|
|
60
68
|
const status = takeOption(args, "--status");
|
|
69
|
+
// Both spellings, because the server accepts both (`request-log.ts:1032`) and an operator
|
|
70
|
+
// should not have to remember which one this surface wanted.
|
|
71
|
+
const conversationId = takeOption(args, "--conversation") ?? takeOption(args, "--conversationId");
|
|
61
72
|
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 200;
|
|
62
73
|
rejectArgs(args, USAGE);
|
|
63
74
|
if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", USAGE);
|
|
64
75
|
if (follow && wantsJson) throw new CliUsageError("--follow uses --jsonl, not --json", USAGE);
|
|
65
76
|
let seen = new Set<string>();
|
|
66
77
|
do {
|
|
67
|
-
const data = await runtimeRequest(`/api/logs${query({ provider, model, status, limit })}`, {}, deps);
|
|
78
|
+
const data = await runtimeRequest(`/api/logs${query({ provider, model, status, conversationId, limit })}`, {}, deps);
|
|
68
79
|
const rows = logRows(data);
|
|
69
80
|
if (!follow && wantsJson) printData(data, true);
|
|
70
81
|
else {
|
|
@@ -131,11 +142,23 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
131
142
|
const wantsJson = takeFlag(args, "--json");
|
|
132
143
|
const range = takeOption(args, "--range") ?? "30d";
|
|
133
144
|
const surface = takeOption(args, "--surface") ?? "all";
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
const provider = takeOption(args, "--provider");
|
|
146
|
+
const model = takeOption(args, "--model");
|
|
147
|
+
// `1d` is accepted here as well as server-side so the CLI does not reject an
|
|
148
|
+
// alias the API would have understood.
|
|
149
|
+
const ranges = [...USAGE_RANGES, "1d"];
|
|
150
|
+
if (!ranges.includes(range)) throw new CliUsageError(`--range must be one of ${USAGE_RANGES.join(", ")} (1d aliases today)`, USAGE);
|
|
151
|
+
if (!USAGE_SURFACES.includes(surface as (typeof USAGE_SURFACES)[number])) {
|
|
152
|
+
throw new CliUsageError(`--surface must be one of ${USAGE_SURFACES.join(", ")}`, USAGE);
|
|
153
|
+
}
|
|
136
154
|
rejectArgs(args, USAGE);
|
|
137
|
-
const result = await runtimeRequest(`/api/usage${query({ range, surface })}`, {}, deps);
|
|
138
|
-
|
|
155
|
+
const result = await runtimeRequest(`/api/usage${query({ range, surface, provider, model })}`, {}, deps);
|
|
156
|
+
// Built only when it will be printed: JavaScript evaluates arguments before
|
|
157
|
+
// the call, so passing formatUsageReport(...) inline would run the human
|
|
158
|
+
// renderer during --json and let its assumptions affect a path that is meant
|
|
159
|
+
// to bypass it entirely.
|
|
160
|
+
if (wantsJson) printData(result, true);
|
|
161
|
+
else printData(result, false, formatUsageReport(result as Parameters<typeof formatUsageReport>[0]));
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -11,6 +11,13 @@ import {
|
|
|
11
11
|
takeOption,
|
|
12
12
|
type RuntimeApiDeps,
|
|
13
13
|
} from "./runtime-api";
|
|
14
|
+
import { providerQuotaLine } from "./account-extended";
|
|
15
|
+
import type { ProviderQuotaReportDto } from "./account-api";
|
|
16
|
+
|
|
17
|
+
interface ProviderQuotasDto {
|
|
18
|
+
generatedAt?: number;
|
|
19
|
+
reports?: ProviderQuotaReportDto[];
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
const USAGE = `Usage:
|
|
16
23
|
ocx provider edit <name> [--adapter <id>] [--base-url <url>] [--default-model <id|->]
|
|
@@ -107,8 +114,15 @@ async function quota(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
107
114
|
const wantsJson = takeFlag(args, "--json");
|
|
108
115
|
const refresh = takeFlag(args, "--refresh");
|
|
109
116
|
rejectArgs(args, USAGE);
|
|
110
|
-
const result = await runtimeRequest(`/api/provider-quotas${refresh ? "?refresh=1" : ""}`, {}, deps);
|
|
111
|
-
|
|
117
|
+
const result = await runtimeRequest<ProviderQuotasDto>(`/api/provider-quotas${refresh ? "?refresh=1" : ""}`, {}, deps);
|
|
118
|
+
// `summaryLines` is a depth-1 flattener: it renders a non-scalar array as "N item(s)", which
|
|
119
|
+
// collapsed the whole report to a count and made the command useless for its stated purpose
|
|
120
|
+
// (#2565). Render one line per report with the same formatter `ocx account refresh` uses.
|
|
121
|
+
const reports = Array.isArray(result?.reports) ? result.reports : [];
|
|
122
|
+
const lines = reports.length > 0
|
|
123
|
+
? reports.map(report => providerQuotaLine(report.provider, report))
|
|
124
|
+
: ["no quota reports available"];
|
|
125
|
+
printData(result, wantsJson, lines);
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
async function presets(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
package/src/cli/provider.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* show <name> Show provider config details (secrets masked)
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig } from "../config";
|
|
12
|
+
import { apiKeyTransportConfigError } from "../config/provider-validation";
|
|
12
13
|
import { hasHelpFlag } from "./help";
|
|
13
14
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
14
15
|
import { providerConfigSeed } from "../providers/derive";
|
package/src/cli/registry.ts
CHANGED
|
@@ -36,8 +36,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
name: "recover-history",
|
|
39
|
-
usage: "ocx recover-history --legacy-openai",
|
|
40
|
-
summary: "
|
|
39
|
+
usage: "ocx recover-history --legacy-openai --yes",
|
|
40
|
+
summary: "Force all user-message opencodex rows to OpenAI for legacy recovery.",
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: "uninstall",
|
|
@@ -88,20 +88,22 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
88
88
|
{ name: "ensure", usage: "ocx ensure", summary: "Ensure the proxy is running and Codex config/cache are current." },
|
|
89
89
|
{
|
|
90
90
|
name: "sync",
|
|
91
|
-
usage: "ocx sync [--restart-codex]",
|
|
91
|
+
usage: "ocx sync [--restart-codex] [--restart-desktop-app]",
|
|
92
92
|
summary: "Fetch provider models and inject them into Codex config.",
|
|
93
93
|
details: [
|
|
94
94
|
"After writing the catalog, warns if long-lived Codex app-server processes are still running.",
|
|
95
95
|
"--restart-codex sends SIGTERM only to matching app-server / code-mode-host processes (may interrupt active turns).",
|
|
96
|
+
"--restart-desktop-app (Windows only, opt-in) fully restarts the Codex desktop app so its model picker re-reads the catalog. Never implied by --restart-codex: it ends live conversations.",
|
|
96
97
|
],
|
|
97
98
|
},
|
|
98
99
|
{
|
|
99
100
|
name: "sync-cache",
|
|
100
|
-
usage: "ocx sync-cache [--restart-codex]",
|
|
101
|
+
usage: "ocx sync-cache [--restart-codex] [--restart-desktop-app]",
|
|
101
102
|
summary: "Refresh Codex's model cache from the active catalog.",
|
|
102
103
|
details: [
|
|
103
104
|
"Warns when Codex app-server processes still hold an in-memory model list.",
|
|
104
105
|
"--restart-codex sends SIGTERM only to matching app-server / code-mode-host processes (may interrupt active turns).",
|
|
106
|
+
"--restart-desktop-app (Windows only, opt-in) fully restarts the Codex desktop app so its model picker re-reads the catalog. Never implied by --restart-codex: it ends live conversations.",
|
|
105
107
|
],
|
|
106
108
|
},
|
|
107
109
|
{ name: "status", usage: "ocx status", summary: "Check proxy server status." },
|
|
@@ -162,10 +164,15 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
162
164
|
"A selection-order change applies from the next unbound request and never moves a bound thread.",
|
|
163
165
|
],
|
|
164
166
|
},
|
|
167
|
+
{
|
|
168
|
+
name: "alias",
|
|
169
|
+
usage: "ocx alias <list|set|rm|defaults> ...",
|
|
170
|
+
summary: "Manage short provider and model names.",
|
|
171
|
+
},
|
|
165
172
|
{
|
|
166
173
|
name: "models",
|
|
167
174
|
aliases: ["model"],
|
|
168
|
-
usage: "ocx models <list|live|add|edit|remove|enable|disable|provider|selected|context|shadow> ...",
|
|
175
|
+
usage: "ocx models <list|live|add|edit|remove|enable|disable|provider|selected|preset|context|shadow> ...",
|
|
169
176
|
summary: "List models and manage custom (manually registered) models.",
|
|
170
177
|
details: [
|
|
171
178
|
"List available models from static config with no subcommand (liveModels may add more at runtime).",
|
|
@@ -205,13 +212,29 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
205
212
|
usage: "ocx observe <logs|usage|storage|memory|debug|claude-inbound|injection> ...",
|
|
206
213
|
summary: "Inspect proxy requests, usage, storage, memory, and debug data.",
|
|
207
214
|
},
|
|
215
|
+
{
|
|
216
|
+
name: "inspect",
|
|
217
|
+
usage: "ocx inspect <config|catalog|routing-analytics|pacing|key-providers|codex-prompt|client-config|star|windows-tray> ...",
|
|
218
|
+
summary: "Read effective config, catalog, analytics, pacing, and the generated client-config snippet.",
|
|
219
|
+
details: [
|
|
220
|
+
"`inspect star` reads the repository star status only. Starring uses your GitHub identity and is available from the dashboard alone.",
|
|
221
|
+
],
|
|
222
|
+
},
|
|
208
223
|
{ name: "logs", usage: "ocx logs [filters] [--follow] [--json|--jsonl]", summary: "Alias of ocx observe logs." },
|
|
209
224
|
{
|
|
210
225
|
name: "usage",
|
|
211
|
-
usage: "ocx usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>] [--json]",
|
|
226
|
+
usage: "ocx usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>] [--provider <name>] [--model <id>] [--json]",
|
|
212
227
|
summary: "Alias of ocx observe usage.",
|
|
213
228
|
},
|
|
214
|
-
{
|
|
229
|
+
{
|
|
230
|
+
name: "storage",
|
|
231
|
+
usage: "ocx storage <report|cleanup|trash|policy> ...",
|
|
232
|
+
summary: "Storage report, archived-session cleanup, trash restore, and the cleanup policy.",
|
|
233
|
+
details: [
|
|
234
|
+
"A bare `ocx storage` prints the report, as it did when this was an alias of `observe storage`.",
|
|
235
|
+
"`cleanup` previews by default and only deletes under --yes; `trash restore` and `policy run` also require --yes.",
|
|
236
|
+
],
|
|
237
|
+
},
|
|
215
238
|
{ name: "memory", usage: "ocx memory [--json]", summary: "Alias of ocx observe memory." },
|
|
216
239
|
{
|
|
217
240
|
name: "access",
|
|
@@ -221,8 +244,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
221
244
|
{ name: "api-key", usage: "ocx api-key <list|create|remove> ...", summary: "Alias of ocx access key." },
|
|
222
245
|
{
|
|
223
246
|
name: "export",
|
|
224
|
-
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime> [--json] [--out <path>] [--force]",
|
|
225
|
-
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent) wired to the running proxy.",
|
|
247
|
+
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|cline> [--json] [--out <path>] [--force]",
|
|
248
|
+
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Cline) wired to the running proxy.",
|
|
226
249
|
details: [
|
|
227
250
|
"--json prints the generated document as JSON on stdout; use --out for the client's native format.",
|
|
228
251
|
"--out <path> writes the native config there and refuses to replace an existing file without --force.",
|
|
@@ -235,7 +258,14 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
235
258
|
usage: "ocx grok <status|exclude|include|set|clear|apply> ...",
|
|
236
259
|
summary: "Manage and apply the Grok Build model fence.",
|
|
237
260
|
},
|
|
238
|
-
{
|
|
261
|
+
{
|
|
262
|
+
name: "integration",
|
|
263
|
+
usage: "ocx integration <claude|grok|client|native> ...",
|
|
264
|
+
summary: "Manage supported client integrations, and the native client toggles.",
|
|
265
|
+
details: [
|
|
266
|
+
"`native` shows or flips the native Claude/Claude Desktop/Codex/Grok toggles; the other subcommands manage the reversible file integrations.",
|
|
267
|
+
],
|
|
268
|
+
},
|
|
239
269
|
{
|
|
240
270
|
name: "system",
|
|
241
271
|
usage: "ocx system <status|settings|startup|diagnostics|sync|update> ...",
|
|
@@ -342,6 +372,15 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
342
372
|
summary: "Check proxy health. Exits 0 if healthy, 1 otherwise.",
|
|
343
373
|
details: ["Use --json for structured output: {ok, pid, port}."],
|
|
344
374
|
},
|
|
375
|
+
{
|
|
376
|
+
name: "capabilities",
|
|
377
|
+
usage: "ocx capabilities [--json] [--mutating-only] [--route <path>]",
|
|
378
|
+
summary: "List the declared CLI capabilities and the management routes they drive.",
|
|
379
|
+
details: [
|
|
380
|
+
"The machine-readable surface index: start here when driving ocx programmatically instead of parsing help text.",
|
|
381
|
+
"--route <path> answers the inverse question: which commands drive this management route.",
|
|
382
|
+
],
|
|
383
|
+
},
|
|
345
384
|
{
|
|
346
385
|
name: "ready",
|
|
347
386
|
usage: "ocx ready [--json] [--wait [--timeout <seconds>]]",
|
package/src/cli/runtime-api.ts
CHANGED
|
@@ -47,15 +47,37 @@ export async function runtimeBaseUrl(deps: RuntimeApiDeps = {}): Promise<string>
|
|
|
47
47
|
return `http://${probeHostname(live.hostname)}:${live.port}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function stringField(record: Record<string, unknown>, key: string): string | undefined {
|
|
51
|
+
const value = record[key];
|
|
52
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compose the operator-facing message from a management error body.
|
|
57
|
+
*
|
|
58
|
+
* The server states WHY a request was refused under `reason` and WHAT TO DO under
|
|
59
|
+
* `hint` (see management-auth.ts, which sets both on a 503 when the management plane
|
|
60
|
+
* is unavailable). Both were dropped here, so a fenced management plane was
|
|
61
|
+
* indistinguishable from a generic failure and an operator had no way to tell a port
|
|
62
|
+
* collision from an ACL refusal from a stopped proxy (#2698).
|
|
63
|
+
*/
|
|
50
64
|
function responseMessage(body: unknown, status: number): string {
|
|
51
|
-
if (body && typeof body === "object") {
|
|
52
|
-
const record = body as Record<string, unknown>;
|
|
53
|
-
for (const key of ["error", "message", "detail"]) {
|
|
54
|
-
if (typeof record[key] === "string" && record[key]) return record[key];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
65
|
if (typeof body === "string" && body.trim()) return body.trim().slice(0, 400);
|
|
58
|
-
return `Management request failed (${status})`;
|
|
66
|
+
if (!body || typeof body !== "object") return `Management request failed (${status})`;
|
|
67
|
+
const record = body as Record<string, unknown>;
|
|
68
|
+
let primary: string | undefined;
|
|
69
|
+
for (const key of ["error", "message", "detail"]) {
|
|
70
|
+
primary = stringField(record, key);
|
|
71
|
+
if (primary) break;
|
|
72
|
+
}
|
|
73
|
+
const parts = [primary ?? `Management request failed (${status})`];
|
|
74
|
+
const reason = stringField(record, "reason");
|
|
75
|
+
// A body of {ok:false, reason:"…"} with no `error` key used to degrade to the
|
|
76
|
+
// generic line, discarding the only actionable field.
|
|
77
|
+
if (reason && reason !== primary) parts.push(`reason: ${reason}`);
|
|
78
|
+
const hint = stringField(record, "hint");
|
|
79
|
+
if (hint && hint !== primary) parts.push(`hint: ${hint}`);
|
|
80
|
+
return parts.join("\n").slice(0, 1200);
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
export async function runtimeRequest<T = unknown>(
|
|
@@ -94,6 +116,28 @@ export function takeFlag(args: string[], flag: string): boolean {
|
|
|
94
116
|
return true;
|
|
95
117
|
}
|
|
96
118
|
|
|
119
|
+
/** Unicode dashes that copy-paste and IME input substitute for ASCII `-`. */
|
|
120
|
+
const DASH_CLASS = /[\u2010-\u2015\u2212]/g;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* True for `--json`, `--json=true`, `-json`, and Unicode-dash spellings.
|
|
124
|
+
* Matching only the exact token `--json` is the same defect logout had: each
|
|
125
|
+
* spelling that slips through is a silent success (or, for doctor, prose on
|
|
126
|
+
* stdout after the caller asked for JSON).
|
|
127
|
+
*/
|
|
128
|
+
export function isJsonOption(arg: string): boolean {
|
|
129
|
+
const body = arg.replace(DASH_CLASS, "-").replace(/^-+/, "");
|
|
130
|
+
return body === "json" || body.startsWith("json=");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Remove one JSON-request spelling from `args`. Returns whether one was present. */
|
|
134
|
+
export function takeJsonFlag(args: string[]): boolean {
|
|
135
|
+
const index = args.findIndex(isJsonOption);
|
|
136
|
+
if (index === -1) return false;
|
|
137
|
+
args.splice(index, 1);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
97
141
|
export function takeOption(args: string[], flag: string): string | undefined {
|
|
98
142
|
const index = args.indexOf(flag);
|
|
99
143
|
if (index === -1) return undefined;
|
package/src/cli/status.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { durableBunRuntime } from "../lib/bun-runtime";
|
|
2
|
-
import { codexAutoStartEnabled, getConfigPath,
|
|
2
|
+
import { codexAutoStartEnabled, getConfigPath, readConfigDiagnostics } from "../config";
|
|
3
|
+
import { getPidPath, readPid, readRuntimePort, type RuntimePortState } from "../config/process-state";
|
|
3
4
|
import { diagnoseCodexBundledPlugins, type CodexPluginsDiagnostic } from "../codex/plugins-doctor";
|
|
4
5
|
import { findLiveProxy, isOpencodexHealthz, probeHostname } from "../server/proxy-liveness";
|
|
5
6
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
@@ -9,6 +10,8 @@ import { collectStartupHealth, type StartupHealth } from "../codex/autostart-hea
|
|
|
9
10
|
import { getCodexRoutingKind } from "../codex/inject";
|
|
10
11
|
import { diagnoseCodexShim } from "../codex/shim";
|
|
11
12
|
import { displayCodexRuntimePath, effortClampAppliesToRuntime, loadLastEffortClamp, resolveCodexRuntime } from "../codex/runtime";
|
|
13
|
+
import { packageVersion } from "./help";
|
|
14
|
+
import { computeVersionSkew, type VersionSkew } from "./version-skew";
|
|
12
15
|
import { redactSecretString, redactUserPath } from "../lib/redact";
|
|
13
16
|
import { collectOrcaCodexHomeDiagnostic, type OrcaCodexHomeDiagnostic } from "../codex/home";
|
|
14
17
|
import { grokFenceEndpointDrift, readGrokStatus } from "../grok/status";
|
|
@@ -69,6 +72,13 @@ export type CliStatusJson = {
|
|
|
69
72
|
};
|
|
70
73
|
};
|
|
71
74
|
codexHome: OrcaCodexHomeDiagnostic;
|
|
75
|
+
/**
|
|
76
|
+
* This CLI's version against the running proxy's (#2701).
|
|
77
|
+
*
|
|
78
|
+
* Additive and optional-by-value, so `schemaVersion` stays 1: an existing consumer that
|
|
79
|
+
* ignores the key is unaffected, and `proxyVersion` is null when nothing is live.
|
|
80
|
+
*/
|
|
81
|
+
versionSkew: VersionSkew;
|
|
72
82
|
};
|
|
73
83
|
|
|
74
84
|
export type CliStatusView = {
|
|
@@ -117,6 +127,29 @@ export function proxyHealthFailureReason(error: unknown, signal: AbortSignal): "
|
|
|
117
127
|
: "unreachable";
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
/**
|
|
131
|
+
* `ocx status` greens on process liveness alone, so a proxy that answers
|
|
132
|
+
* /healthz reads healthy even when Codex is not pointed at it and every routed
|
|
133
|
+
* request goes to OpenAI instead (#2411). The proxy line is not wrong — the
|
|
134
|
+
* listener really is up — so it keeps its check, and this supplies the signal
|
|
135
|
+
* that was missing rather than corrupting the one that was already honest.
|
|
136
|
+
*
|
|
137
|
+
* Only `native` warns. `custom-local` and `unknown` are also "this proxy is
|
|
138
|
+
* unused", but startupHealthSummary already renders both as AT RISK with a
|
|
139
|
+
* remedy command, and `custom-remote` is a deliberate operator choice. Warning
|
|
140
|
+
* on all four would teach operators to skip the line that matters.
|
|
141
|
+
*/
|
|
142
|
+
export function unusedProxyWarningLines(input: {
|
|
143
|
+
proxyUp: boolean;
|
|
144
|
+
routingKind: StartupHealth["routingKind"];
|
|
145
|
+
}): string[] {
|
|
146
|
+
if (!input.proxyUp || input.routingKind !== "native") return [];
|
|
147
|
+
return [
|
|
148
|
+
"⚠️ Codex routing is native — the running proxy is unused.",
|
|
149
|
+
" Codex requests go to OpenAI, not this proxy. Re-point with: ocx start",
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
async function checkProxyHealth(target: ListenTarget): Promise<HealthCheck> {
|
|
121
154
|
const url = target.healthUrl;
|
|
122
155
|
const controller = new AbortController();
|
|
@@ -156,6 +189,9 @@ export async function collectStatus(): Promise<CliStatusView> {
|
|
|
156
189
|
const pidFile = readPid();
|
|
157
190
|
// Preserve an authoritative null from orphan/legacy liveness — do not restore pidFile.
|
|
158
191
|
const pid = resolveStatusPid(live, pidFile);
|
|
192
|
+
// No extra request: findLiveProxy's identity probe already parsed and validated the
|
|
193
|
+
// healthz body, so the version came back with the liveness result.
|
|
194
|
+
const versionSkew = computeVersionSkew(packageVersion(), live?.version);
|
|
159
195
|
const listen = live
|
|
160
196
|
? {
|
|
161
197
|
port: live.port,
|
|
@@ -323,6 +359,10 @@ export async function collectStatus(): Promise<CliStatusView> {
|
|
|
323
359
|
codexPlugins,
|
|
324
360
|
codexRuntime,
|
|
325
361
|
codexHome,
|
|
362
|
+
// Own field rather than a line in `codexRuntime.warning`: a stale ocx on PATH is a
|
|
363
|
+
// fact about this install, not about the Codex runtime, and filing it there would
|
|
364
|
+
// print it under the wrong heading (#2701).
|
|
365
|
+
versionSkew,
|
|
326
366
|
},
|
|
327
367
|
};
|
|
328
368
|
}
|