@bitkyc08/opencodex 2.52.0 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-BlO4Yl6q.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/cli/account.ts
CHANGED
|
@@ -41,6 +41,7 @@ const REPLACEMENT_STYLE_OAUTH = new Set<string>();
|
|
|
41
41
|
|
|
42
42
|
const ACCOUNT_USAGE = `Usage:
|
|
43
43
|
ocx account list [provider] [--json] [--all] [--quota [--refresh]]
|
|
44
|
+
ocx account history openai <pool-account-id> [--limit <1-200>] [--json]
|
|
44
45
|
ocx account current <provider> [--json]
|
|
45
46
|
ocx account use <provider> <account-or-key-id|main> [--json]
|
|
46
47
|
ocx account refresh <provider> [--json]
|
|
@@ -50,7 +51,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
50
51
|
ocx account pause <provider> <account-id|main> [--json]
|
|
51
52
|
ocx account resume <provider> <account-id|main> [--json]
|
|
52
53
|
ocx account pause-exhausted <provider> [--json]
|
|
53
|
-
ocx account strategy <provider> [<quota|round-robin|fill-first>] [--json]
|
|
54
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first|reset-first>] [--json]
|
|
54
55
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
55
56
|
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
56
57
|
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
@@ -61,7 +62,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
61
62
|
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
62
63
|
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
63
64
|
ocx account grok-reset-coupons [<account-id>] [--consume --yes] [--token-id <token-id>] [--json]
|
|
64
|
-
ocx account main <doctor|list|register|add|switch|recover> ...
|
|
65
|
+
ocx account main <doctor|list|register|add|reauth|switch|recover> ...
|
|
65
66
|
|
|
66
67
|
List and switch provider accounts and API-key pools (masked output only).
|
|
67
68
|
'main' selects the Codex App login for the openai account pool.`;
|
|
@@ -101,6 +102,9 @@ function statusText(row: AccountRow): string {
|
|
|
101
102
|
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
102
103
|
if (row.needsReauth) parts.push("needs-reauth");
|
|
103
104
|
if (row.validationPending) parts.push("validation-pending");
|
|
105
|
+
if (row.selectionExcludedReason === "plan_excluded") {
|
|
106
|
+
parts.push(`not-auto-selected(plan=${row.selectionExcludedPlan ?? row.plan ?? "unknown"})`);
|
|
107
|
+
}
|
|
104
108
|
return parts.join(" ");
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -115,7 +119,7 @@ function priorityText(row: AccountRow): string {
|
|
|
115
119
|
* decides on before a long session. The full breakdown stays in `--json`.
|
|
116
120
|
*/
|
|
117
121
|
function quotaText(row: AccountRow): string {
|
|
118
|
-
if (
|
|
122
|
+
if (row.quotaUnavailable) return row.quotaFailure ? `unavailable (${row.quotaFailure})` : "unavailable";
|
|
119
123
|
const quota = row.quota;
|
|
120
124
|
if (!quota) return "-";
|
|
121
125
|
const parts: string[] = [];
|
|
@@ -335,6 +339,10 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
335
339
|
const [sub, ...rest] = args;
|
|
336
340
|
try {
|
|
337
341
|
if (sub === "list") return await cmdList(rest, deps);
|
|
342
|
+
if (sub === "history") {
|
|
343
|
+
const { cmdAccountHistory } = await import("./account-history");
|
|
344
|
+
return await cmdAccountHistory(rest, deps);
|
|
345
|
+
}
|
|
338
346
|
if (sub === "current") return await cmdCurrent(rest, deps);
|
|
339
347
|
if (sub === "use") return await cmdUse(rest, deps);
|
|
340
348
|
if (sub === "refresh") return await cmdRefresh(rest, deps);
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -95,6 +95,85 @@ export const HEAD_CAPABILITIES: readonly HeadCapability[] = [
|
|
|
95
95
|
* A capability must not name a route the command does not actually fetch.
|
|
96
96
|
*/
|
|
97
97
|
export const CAPABILITIES: readonly Capability[] = [
|
|
98
|
+
{
|
|
99
|
+
"command": [
|
|
100
|
+
"remote-workspace",
|
|
101
|
+
"pair"
|
|
102
|
+
],
|
|
103
|
+
"summary": "Enroll this executor with one Hub using a one-time code from stdin and locally approved roots.",
|
|
104
|
+
"routes": [],
|
|
105
|
+
"flags": [
|
|
106
|
+
{
|
|
107
|
+
"name": "--json",
|
|
108
|
+
"value": "boolean",
|
|
109
|
+
"summary": "Emit the public local executor status."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "--pairing-code-stdin",
|
|
113
|
+
"value": "boolean",
|
|
114
|
+
"summary": "Read the one-time pairing code from stdin."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "--root",
|
|
118
|
+
"value": "string",
|
|
119
|
+
"summary": "Approve an absolute workspace directory; repeatable."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "--toolchain-root",
|
|
123
|
+
"value": "string",
|
|
124
|
+
"summary": "Approve a read-only toolchain directory; repeatable."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "--executor-helper",
|
|
128
|
+
"value": "string",
|
|
129
|
+
"summary": "Select a reviewed native helper file."
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "--name",
|
|
133
|
+
"value": "string",
|
|
134
|
+
"summary": "Name this executor."
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"mutates": true,
|
|
138
|
+
"json": "payload",
|
|
139
|
+
"details": [
|
|
140
|
+
"Executor-local operation; Hub consent and session control stay in the dashboard."
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"command": [
|
|
145
|
+
"remote-workspace",
|
|
146
|
+
"agent"
|
|
147
|
+
],
|
|
148
|
+
"summary": "Keep the paired executor connected to its Hub.",
|
|
149
|
+
"routes": [],
|
|
150
|
+
"flags": [],
|
|
151
|
+
"mutates": true,
|
|
152
|
+
"json": "none",
|
|
153
|
+
"details": [
|
|
154
|
+
"Executor-local operation; Hub consent and session control stay in the dashboard."
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"command": [
|
|
159
|
+
"remote-workspace",
|
|
160
|
+
"status"
|
|
161
|
+
],
|
|
162
|
+
"summary": "Read local executor enrollment and available capabilities without printing credentials.",
|
|
163
|
+
"routes": [],
|
|
164
|
+
"flags": [
|
|
165
|
+
{
|
|
166
|
+
"name": "--json",
|
|
167
|
+
"value": "boolean",
|
|
168
|
+
"summary": "Emit the public local executor status."
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"mutates": false,
|
|
172
|
+
"json": "payload",
|
|
173
|
+
"details": [
|
|
174
|
+
"Executor-local operation; Hub consent and session control stay in the dashboard."
|
|
175
|
+
]
|
|
176
|
+
},
|
|
98
177
|
{
|
|
99
178
|
command: ["models", "price"],
|
|
100
179
|
summary: "Read the saved manual price for an exact provider/model selector.",
|
|
@@ -235,6 +314,40 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
235
314
|
"Headless services usually have no unlocked keychain session; prefer ${ENV_VAR} references there.",
|
|
236
315
|
],
|
|
237
316
|
},
|
|
317
|
+
{
|
|
318
|
+
command: ["account", "history"],
|
|
319
|
+
summary: "Cached quota observations for one stored Codex pool account.",
|
|
320
|
+
routes: [{ method: "GET", path: "/api/codex-auth/quota/history" }],
|
|
321
|
+
flags: [
|
|
322
|
+
{ name: "--json", value: "boolean", summary: "Emit the bounded observation history." },
|
|
323
|
+
{ name: "--limit", value: "number", summary: "Return the newest 1 to 200 observations." },
|
|
324
|
+
],
|
|
325
|
+
mutates: false,
|
|
326
|
+
json: "payload",
|
|
327
|
+
details: ["Use account history openai <pool-account-id>. Reads cached observations only; no refresh or warmup. Native main is not included."],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
command: ["account", "main", "reauth"],
|
|
331
|
+
summary: "Reauthenticate the native main Codex login with a device code (#3898); headless hubs need no Codex App or keyring.",
|
|
332
|
+
routes: [
|
|
333
|
+
{ method: "POST", path: "/api/codex-auth/main/reauth-device" },
|
|
334
|
+
{ method: "GET", path: "/api/codex-auth/main/reauth-device" },
|
|
335
|
+
{ method: "DELETE", path: "/api/codex-auth/main/reauth-device" },
|
|
336
|
+
],
|
|
337
|
+
flags: [
|
|
338
|
+
{ name: "--device", value: "boolean", summary: "Run the device-code flow (the only reauth mode)." },
|
|
339
|
+
{ name: "--no-wait", value: "boolean", summary: "Print the flow handle and code without waiting for completion." },
|
|
340
|
+
{ name: "--flow", value: "string", summary: "Flow id for status and cancel." },
|
|
341
|
+
{ name: "--json", value: "boolean", summary: "Emit the flow status as JSON." },
|
|
342
|
+
],
|
|
343
|
+
mutates: true,
|
|
344
|
+
json: "payload",
|
|
345
|
+
details: [
|
|
346
|
+
"Same-identity reauth only: the device login must complete for the ChatGPT account that already holds the native main slot, and the commit is fenced by the exclusive claim plus a path/hash/inode snapshot.",
|
|
347
|
+
"/api/codex-auth/login stays pool-only and keeps rejecting __main__; this namespace is the only device-reauth surface for the native main slot.",
|
|
348
|
+
"Payloads carry only flowId, status, the verification URL, the device code, and a closed set of failure codes -- never tokens, emails, or raw account ids.",
|
|
349
|
+
],
|
|
350
|
+
},
|
|
238
351
|
{
|
|
239
352
|
command: ["account", "list"],
|
|
240
353
|
summary: "Codex OAuth accounts with pool priority and pause state.",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { afterCatalogWriteHandleAppServers } from "../codex/app-server-processes";
|
|
2
|
+
import { pullRemoteCatalog, RemoteCatalogError } from "../codex/catalog/remote";
|
|
3
|
+
import { hasHelpFlag, printSubcommandUsage } from "./help";
|
|
4
|
+
|
|
5
|
+
export interface CatalogPullEnvelope {
|
|
6
|
+
schemaVersion: 1;
|
|
7
|
+
ok: boolean;
|
|
8
|
+
status: "updated" | "unchanged" | "failed";
|
|
9
|
+
catalogWritten: boolean;
|
|
10
|
+
cacheSynced: boolean;
|
|
11
|
+
codexRestarted: boolean;
|
|
12
|
+
modelCount?: number;
|
|
13
|
+
code?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function optionValue(args: string[], name: string): string | undefined {
|
|
17
|
+
const index = args.indexOf(name);
|
|
18
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function handleCatalogCommand(args: string[]): Promise<number> {
|
|
22
|
+
if (hasHelpFlag(args)) { printSubcommandUsage("catalog"); return 0; }
|
|
23
|
+
const json = args.includes("--json");
|
|
24
|
+
const restartCodex = args.includes("--restart-codex");
|
|
25
|
+
const authEnv = optionValue(args, "--auth-env");
|
|
26
|
+
const positionals = args.filter((arg, index) => {
|
|
27
|
+
if (arg === "--auth-env") return false;
|
|
28
|
+
if (index > 0 && args[index - 1] === "--auth-env") return false;
|
|
29
|
+
return !arg.startsWith("-");
|
|
30
|
+
});
|
|
31
|
+
const knownFlags = new Set(["--json", "--restart-codex", "--auth-env"]);
|
|
32
|
+
const unknown = args.find((arg, index) => arg.startsWith("-") && !knownFlags.has(arg) && args[index - 1] !== "--auth-env");
|
|
33
|
+
const validEnvName = authEnv === undefined || /^[A-Za-z_][A-Za-z0-9_]*$/.test(authEnv);
|
|
34
|
+
if (positionals[0] !== "pull" || positionals.length !== 2 || unknown || !validEnvName
|
|
35
|
+
|| args.includes("--auth-env") !== (authEnv !== undefined)) {
|
|
36
|
+
const envelope: CatalogPullEnvelope = {
|
|
37
|
+
schemaVersion: 1, ok: false, status: "failed", catalogWritten: false,
|
|
38
|
+
cacheSynced: false, codexRestarted: false, code: "usage",
|
|
39
|
+
};
|
|
40
|
+
if (json) console.log(JSON.stringify(envelope));
|
|
41
|
+
else console.error("Usage: ocx catalog pull <https-url> [--auth-env <NAME>] [--json] [--restart-codex]");
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
let token: string | undefined;
|
|
45
|
+
if (authEnv !== undefined) {
|
|
46
|
+
token = process.env[authEnv];
|
|
47
|
+
if (token === undefined) {
|
|
48
|
+
const envelope: CatalogPullEnvelope = {
|
|
49
|
+
schemaVersion: 1, ok: false, status: "failed", catalogWritten: false,
|
|
50
|
+
cacheSynced: false, codexRestarted: false, code: "auth_env_missing",
|
|
51
|
+
};
|
|
52
|
+
if (json) console.log(JSON.stringify(envelope));
|
|
53
|
+
else console.error(`Catalog authentication environment variable ${authEnv} is not set.`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const result = await pullRemoteCatalog(positionals[1]!, { token });
|
|
59
|
+
let codexRestarted = false;
|
|
60
|
+
let restartIncomplete = false;
|
|
61
|
+
if (result.catalogWritten) {
|
|
62
|
+
const processLog = json
|
|
63
|
+
? { log: (...values: unknown[]) => console.error(...values), error: (...values: unknown[]) => console.error(...values) }
|
|
64
|
+
: console;
|
|
65
|
+
const processResult = afterCatalogWriteHandleAppServers({ restart: restartCodex, log: processLog });
|
|
66
|
+
const restart = processResult.restart;
|
|
67
|
+
if (restart) {
|
|
68
|
+
// A partial stop is not a restart. `restartCodexAppServers` reports failures and
|
|
69
|
+
// survivors without throwing, so counting `stopped` alone reported success while a
|
|
70
|
+
// stale app-server was still serving the previous catalog from memory.
|
|
71
|
+
codexRestarted = restart.failed.length === 0
|
|
72
|
+
&& restart.surviving.length === 0
|
|
73
|
+
&& restart.stopped.length === processResult.processes.length;
|
|
74
|
+
restartIncomplete = !codexRestarted;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (restartIncomplete) {
|
|
78
|
+
// The catalog and cache landed; only the restart did not finish. Saying the pull failed
|
|
79
|
+
// and wrote nothing would be a second false report, so the envelope keeps the real
|
|
80
|
+
// write state and `ok` carries the failure.
|
|
81
|
+
const envelope: CatalogPullEnvelope = {
|
|
82
|
+
schemaVersion: 1, ok: false, status: result.status,
|
|
83
|
+
catalogWritten: result.catalogWritten, cacheSynced: result.cacheSynced,
|
|
84
|
+
codexRestarted: false, modelCount: result.modelCount, code: "restart_incomplete",
|
|
85
|
+
};
|
|
86
|
+
if (json) console.log(JSON.stringify(envelope));
|
|
87
|
+
else console.error("Remote Codex catalog installed, but a Codex app-server is still running the previous catalog.");
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
const envelope: CatalogPullEnvelope = {
|
|
91
|
+
schemaVersion: 1, ok: true, status: result.status,
|
|
92
|
+
catalogWritten: result.catalogWritten, cacheSynced: result.cacheSynced,
|
|
93
|
+
codexRestarted, modelCount: result.modelCount,
|
|
94
|
+
};
|
|
95
|
+
if (json) console.log(JSON.stringify(envelope));
|
|
96
|
+
else if (result.status === "unchanged") console.log("Remote Codex catalog is unchanged; no files or processes were touched.");
|
|
97
|
+
else console.log(`Remote Codex catalog installed (${result.modelCount} models) and models_cache.json synchronized.`);
|
|
98
|
+
return 0;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const code = error instanceof RemoteCatalogError ? error.code : "write_failed";
|
|
101
|
+
const envelope: CatalogPullEnvelope = {
|
|
102
|
+
schemaVersion: 1, ok: false, status: "failed", catalogWritten: false,
|
|
103
|
+
cacheSynced: false, codexRestarted: false, code,
|
|
104
|
+
};
|
|
105
|
+
if (json) console.log(JSON.stringify(envelope));
|
|
106
|
+
else console.error(error instanceof RemoteCatalogError ? error.message : "Remote catalog installation failed");
|
|
107
|
+
return code === "lock_busy" ? 3 : 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -424,6 +424,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
424
424
|
// Explicit sync with the integration OFF still refreshes the catalog/cache
|
|
425
425
|
// for side profiles that consume the proxy without injection.
|
|
426
426
|
console.log(synced.message ?? "Codex integration is OFF; catalog refreshed, Codex config untouched.");
|
|
427
|
+
if (!synced.ok) code = 1;
|
|
427
428
|
} else if (!synced.ok) {
|
|
428
429
|
code = 1;
|
|
429
430
|
console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
|
|
@@ -481,10 +482,18 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
481
482
|
const { handleConnectCommand } = await import("./connect");
|
|
482
483
|
return await handleConnectCommand(deps.args.slice(1));
|
|
483
484
|
},
|
|
485
|
+
"remote-workspace": async deps => {
|
|
486
|
+
const { runRemoteWorkspaceCommand } = await import("./remote-workspace");
|
|
487
|
+
return await runRemoteWorkspaceCommand(deps.args.slice(1));
|
|
488
|
+
},
|
|
484
489
|
disconnect: async deps => {
|
|
485
490
|
const { handleDisconnectCommand } = await import("./connect");
|
|
486
491
|
return await handleDisconnectCommand(deps.args.slice(1));
|
|
487
492
|
},
|
|
493
|
+
catalog: async deps => {
|
|
494
|
+
const { handleCatalogCommand } = await import("./catalog");
|
|
495
|
+
return await handleCatalogCommand(deps.args.slice(1));
|
|
496
|
+
},
|
|
488
497
|
"sync-cache": async deps => {
|
|
489
498
|
const cacheArgs = deps.args.slice(1);
|
|
490
499
|
const restartCodex = cacheArgs.includes("--restart-codex");
|
package/src/cli/help.ts
CHANGED
|
@@ -41,10 +41,12 @@ Usage:
|
|
|
41
41
|
ocx tray <sub> Windows status tray (install|start|stop|status|uninstall)
|
|
42
42
|
ocx ensure Ensure the proxy is running and Codex config/cache are current
|
|
43
43
|
ocx connect <url> Connect this machine to a remote OpenCodex hub (credential via stdin)
|
|
44
|
+
ocx remote-workspace <sub> Pair/run an OCX-only remote execution computer
|
|
44
45
|
ocx disconnect Restore local state and clear the hub connection
|
|
45
46
|
ocx sync [--restart-codex] Fetch models from providers and inject into Codex config
|
|
46
47
|
ocx sync-cache [--restart-codex]
|
|
47
48
|
Refresh Codex's model cache from the active catalog
|
|
49
|
+
ocx catalog pull <https-url> Install a validated remote catalog and refresh the Codex cache
|
|
48
50
|
ocx status Check proxy server status (on a hub: one block with its ports and token source)
|
|
49
51
|
ocx doctor Diagnose environment/network issues (WSL, proxy, ChatGPT reachability)
|
|
50
52
|
ocx doctor --reclaim-response-temps
|
package/src/cli/index.ts
CHANGED
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
import { requestBoundSystemRestart } from "./system-restart-client";
|
|
64
64
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
65
65
|
import { dispatchCommand , decideStartWithLiveOwner } from "./dispatch";
|
|
66
|
-
import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
66
|
+
import { AuxiliaryListenerBindError, findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
67
67
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
68
68
|
import { createReadinessGate } from "../server/readiness";
|
|
69
69
|
import { isApiAuthRequired } from "../server/auth-cors";
|
|
@@ -391,7 +391,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
391
391
|
scheduleCatalogPrewarm();
|
|
392
392
|
break;
|
|
393
393
|
} catch (err) {
|
|
394
|
-
if (!isAddrInUse(err) || attempt >= 2) throw err;
|
|
394
|
+
if (err instanceof AuxiliaryListenerBindError || !isAddrInUse(err) || attempt >= 2) throw err;
|
|
395
395
|
if (requestedPort !== undefined) {
|
|
396
396
|
console.log(`⚠️ Port ${port} was taken while starting; waiting to retry the same port...`);
|
|
397
397
|
const hostname = loadConfig().hostname ?? "127.0.0.1";
|
package/src/cli/observe.ts
CHANGED
|
@@ -14,6 +14,10 @@ import { formatUsageReport } from "./usage-report";
|
|
|
14
14
|
import { USAGE_RANGES, USAGE_SURFACES, type UsageSummary } from "../usage/summary";
|
|
15
15
|
import { parseUsageTimeWindow, type UsageTimeWindow } from "../usage/time-range";
|
|
16
16
|
import { redactSecretString } from "../lib/redact";
|
|
17
|
+
import { readClientConnectionState, sameClientConnectionOwner } from "../client/state";
|
|
18
|
+
import { readServiceApiTokenState } from "../lib/service-secrets";
|
|
19
|
+
import { fetchHubUsage } from "../client/hub-client";
|
|
20
|
+
import type { HubUsageReport } from "../remote/hub-usage";
|
|
17
21
|
|
|
18
22
|
const USAGE = `Usage:
|
|
19
23
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
@@ -175,7 +179,30 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
175
179
|
throw new CliUsageError(`--surface must be one of ${USAGE_SURFACES.join(", ")}`, USAGE);
|
|
176
180
|
}
|
|
177
181
|
rejectArgs(args.map(redactSecretString), USAGE);
|
|
178
|
-
const
|
|
182
|
+
const suffix = query({ range, surface, provider, model, since: window?.since, until: window?.until });
|
|
183
|
+
const connection = readClientConnectionState();
|
|
184
|
+
let result: UsageSummary | HubUsageReport;
|
|
185
|
+
if (connection.kind === "invalid" || connection.kind === "mismatched") {
|
|
186
|
+
throw new Error(`Client usage unavailable: ${connection.reason}`);
|
|
187
|
+
}
|
|
188
|
+
if (connection.kind === "connected") {
|
|
189
|
+
const token = readServiceApiTokenState();
|
|
190
|
+
if (token.kind !== "present" || token.fingerprint !== connection.value.tokenFingerprint) {
|
|
191
|
+
throw new Error("Client usage unavailable: the enrolled data key is missing or changed; repair the client connection");
|
|
192
|
+
}
|
|
193
|
+
result = await fetchHubUsage(connection.value.serverUrl, token.token, new URLSearchParams(suffix), {
|
|
194
|
+
fetchImpl: deps.fetchImpl, timeoutMs: 60_000,
|
|
195
|
+
});
|
|
196
|
+
const current = readClientConnectionState();
|
|
197
|
+
const currentToken = readServiceApiTokenState();
|
|
198
|
+
if (current.kind !== "connected" || !sameClientConnectionOwner(current.value, connection.value)
|
|
199
|
+
|| current.value.tokenFingerprint !== token.fingerprint
|
|
200
|
+
|| currentToken.kind !== "present" || currentToken.fingerprint !== token.fingerprint) {
|
|
201
|
+
throw new Error("Client connection changed while reading usage; retry for the current connection");
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
result = await runtimeRequest<UsageSummary>(`/api/usage${suffix}`, {}, deps);
|
|
205
|
+
}
|
|
179
206
|
// Older daemons ignore custom bounds and return successful preset reports.
|
|
180
207
|
if (window && (result?.customWindow !== true || result.since !== window.since || result.until !== window.until)) {
|
|
181
208
|
throw new Error("The server did not confirm the requested custom usage window. Upgrade and restart the proxy, then retry.");
|
package/src/cli/opencode.ts
CHANGED
|
@@ -41,6 +41,9 @@ import type {
|
|
|
41
41
|
} from "../clients/config-export";
|
|
42
42
|
import { filterCatalogVisibleModels, visibleNativeSlugs } from "../codex/catalog";
|
|
43
43
|
import { commandInvocation } from "../lib/win-exec";
|
|
44
|
+
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
45
|
+
import { localManagementOrigin } from "../lib/local-destinations";
|
|
46
|
+
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
44
47
|
import { loadServiceTokenFromFile, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
45
48
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
46
49
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
@@ -306,17 +309,38 @@ function opencodeBlocks(
|
|
|
306
309
|
/** Default deadline for authenticated GET /api/models during `ocx opencode` launch. */
|
|
307
310
|
export const OPENCODE_PROXY_MODELS_TIMEOUT_MS = 8_000;
|
|
308
311
|
|
|
312
|
+
function opencodeManagementOrigin(live: LiveProxy, override?: string): string {
|
|
313
|
+
if (!override && (!Number.isInteger(live.port) || live.port < 1 || live.port > 65535)) {
|
|
314
|
+
throw new Error("The local management port is invalid.");
|
|
315
|
+
}
|
|
316
|
+
let url: URL;
|
|
317
|
+
try { url = new URL(override ?? `http://${probeHostname(live.hostname)}:${live.port}`); }
|
|
318
|
+
catch { throw new Error("The local management address is invalid."); }
|
|
319
|
+
if (url.protocol !== "http:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
320
|
+
throw new Error("The catalog requires a local HTTP management origin without credentials or a path.");
|
|
321
|
+
}
|
|
322
|
+
const host = url.hostname.toLowerCase();
|
|
323
|
+
if (["localhost", "localhost.", "127.0.0.1", "0.0.0.0", "[::]"].includes(host)) url.hostname = "127.0.0.1";
|
|
324
|
+
else if (host !== "[::1]") {
|
|
325
|
+
throw new Error("The catalog requires a loopback management listener. On a hub, enable hub.managementIngress.");
|
|
326
|
+
}
|
|
327
|
+
const port = Number(url.port || 80);
|
|
328
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("The local management port is invalid.");
|
|
329
|
+
return url.origin;
|
|
330
|
+
}
|
|
331
|
+
|
|
309
332
|
/** Fetch the live model catalog from a running proxy's management API. */
|
|
310
333
|
export async function fetchOpencodeProxyModels(
|
|
311
334
|
live: LiveProxy,
|
|
312
|
-
|
|
313
|
-
deps: { fetchImpl?: typeof fetch; timeoutMs?: number } = {},
|
|
335
|
+
managementToken: string,
|
|
336
|
+
deps: { fetchImpl?: typeof fetch; timeoutMs?: number; managementOrigin?: string } = {},
|
|
314
337
|
): Promise<OpencodeProxyModelRow[]> {
|
|
315
|
-
const baseUrl =
|
|
316
|
-
const fetchImpl = deps.fetchImpl ??
|
|
338
|
+
const baseUrl = opencodeManagementOrigin(live, deps.managementOrigin);
|
|
339
|
+
const fetchImpl = deps.fetchImpl ?? directLocalHttpFetch;
|
|
317
340
|
const headers = new Headers({ Accept: "application/json" });
|
|
318
|
-
const token =
|
|
319
|
-
if (token)
|
|
341
|
+
const token = managementToken.trim();
|
|
342
|
+
if (!token) throw new Error("No local admin token is available for the model catalog.");
|
|
343
|
+
headers.set("X-OpenCodex-API-Key", token);
|
|
320
344
|
const controller = new AbortController();
|
|
321
345
|
const timeout = setTimeout(() => controller.abort(), deps.timeoutMs ?? OPENCODE_PROXY_MODELS_TIMEOUT_MS);
|
|
322
346
|
const abortIfTimedOut = (): Promise<never> => new Promise((_, reject) => {
|
|
@@ -337,10 +361,16 @@ export async function fetchOpencodeProxyModels(
|
|
|
337
361
|
response = await Promise.race([
|
|
338
362
|
fetchImpl(`${baseUrl}/api/models`, {
|
|
339
363
|
headers,
|
|
364
|
+
redirect: "error",
|
|
365
|
+
cache: "no-store",
|
|
340
366
|
signal: controller.signal,
|
|
341
367
|
}),
|
|
342
368
|
abortIfTimedOut(),
|
|
343
369
|
]);
|
|
370
|
+
if (response.status >= 300 && response.status < 400) {
|
|
371
|
+
void response.body?.cancel().catch(() => {});
|
|
372
|
+
throw new Error("Management catalog redirects are refused.");
|
|
373
|
+
}
|
|
344
374
|
text = await Promise.race([response.text(), abortIfTimedOut()]);
|
|
345
375
|
} catch (error) {
|
|
346
376
|
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
@@ -584,7 +614,7 @@ export function buildOpencodeEnv(
|
|
|
584
614
|
const runtimeConfig = mergeOpencodeRuntimeConfig(base[OPENCODE_CONFIG_CONTENT_ENV], blocks);
|
|
585
615
|
if (isOpencodeRuntimeConfigError(runtimeConfig)) return runtimeConfig;
|
|
586
616
|
return {
|
|
587
|
-
...base,
|
|
617
|
+
...Object.fromEntries(Object.entries(base).filter(([name]) => name.toUpperCase() !== "OPENCODEX_ADMIN_AUTH_TOKEN")),
|
|
588
618
|
[OPENCODE_CONFIG_CONTENT_ENV]: serializeOpencodeRuntimeConfig(runtimeConfig),
|
|
589
619
|
[OPENCODE_API_KEY_ENV]: apiKey,
|
|
590
620
|
};
|
|
@@ -652,7 +682,11 @@ export async function cmdOpencode(args: string[]): Promise<number> {
|
|
|
652
682
|
const apiKey = opencodeApiKey(startupConfig);
|
|
653
683
|
let proxyModels: OpencodeProxyModelRow[];
|
|
654
684
|
try {
|
|
655
|
-
|
|
685
|
+
const managementToken = configuredAdminToken();
|
|
686
|
+
if (!managementToken) throw new Error("No local admin token is available; check the running proxy's home.");
|
|
687
|
+
proxyModels = await fetchOpencodeProxyModels(live, managementToken, {
|
|
688
|
+
managementOrigin: localManagementOrigin({ ...startupConfig, hostname: live.hostname }, live.port),
|
|
689
|
+
});
|
|
656
690
|
} catch (error) {
|
|
657
691
|
const reason = error instanceof Error ? error.message : String(error);
|
|
658
692
|
console.error(`❌ Could not fetch the model catalog from the proxy: ${reason}`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modelCapabilitiesConfigError } from "../config/provider-validation";
|
|
1
2
|
import {
|
|
2
3
|
CliUsageError,
|
|
3
4
|
csv,
|
|
@@ -39,7 +40,7 @@ const USAGE = `Usage:
|
|
|
39
40
|
[--auth-mode <key|forward|oauth|local|->] [--note <text|->]
|
|
40
41
|
[--api-key-transport <x-api-key|bearer|->]
|
|
41
42
|
[--headers <json>] [--enabled <on|off>] [--live-models <on|off>]
|
|
42
|
-
[--retain-models <id,id|->]
|
|
43
|
+
[--retain-models <id,id|->] [--model <id> --text-only]
|
|
43
44
|
[--xai-chat <on|off>]
|
|
44
45
|
[--allow-private-network <on|off>] [--json]
|
|
45
46
|
ocx provider test <name> [--json]
|
|
@@ -72,7 +73,16 @@ async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
72
73
|
const liveModels = takeBooleanOption(args, "--live-models");
|
|
73
74
|
const allowPrivateNetwork = takeBooleanOption(args, "--allow-private-network");
|
|
74
75
|
const xaiChat = takeBooleanOption(args, "--xai-chat");
|
|
76
|
+
const textOnly = takeFlag(args, "--text-only");
|
|
77
|
+
const capabilityModel = takeOption(args, "--model");
|
|
75
78
|
rejectArgs(args, USAGE);
|
|
79
|
+
if (textOnly || capabilityModel !== undefined) {
|
|
80
|
+
if (!textOnly || capabilityModel === undefined) throw new CliUsageError("--text-only and --model must be supplied together", USAGE);
|
|
81
|
+
const declaration = { [capabilityModel]: { inputModalities: ["text"] } };
|
|
82
|
+
const error = modelCapabilitiesConfigError(declaration);
|
|
83
|
+
if (error) throw new CliUsageError(error, USAGE);
|
|
84
|
+
patch.modelCapabilities = declaration;
|
|
85
|
+
}
|
|
76
86
|
if (xaiChat !== undefined) {
|
|
77
87
|
if (name !== "xai") throw new CliUsageError("--xai-chat is valid only for provider xai", USAGE);
|
|
78
88
|
patch.xaiResponsesOptIn = !xaiChat;
|
package/src/cli/provider.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
11
|
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig } from "../config";
|
|
12
|
-
import { apiKeyTransportConfigError } from "../config/provider-validation";
|
|
12
|
+
import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation";
|
|
13
13
|
import { hasHelpFlag } from "./help";
|
|
14
14
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
15
15
|
import { providerConfigSeed } from "../providers/derive";
|
|
@@ -139,7 +139,7 @@ function handleList(args: string[]): void {
|
|
|
139
139
|
// provider add
|
|
140
140
|
// ---------------------------------------------------------------------------
|
|
141
141
|
|
|
142
|
-
const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--api-key-transport <x-api-key|bearer>] [--default-model <model>] [--allow-private-network] [--set-default] [--force] [--json] [--sync]";
|
|
142
|
+
const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--api-key-transport <x-api-key|bearer>] [--default-model <model>] [--model <id> --text-only] [--allow-private-network] [--set-default] [--force] [--json] [--sync]";
|
|
143
143
|
|
|
144
144
|
async function handleAdd(args: string[]): Promise<void> {
|
|
145
145
|
const name = args[0];
|
|
@@ -164,7 +164,13 @@ async function handleAdd(args: string[]): Promise<void> {
|
|
|
164
164
|
const adapter = consumeFlagValue(restArgs, "--adapter");
|
|
165
165
|
const baseUrl = consumeFlagValue(restArgs, "--base-url");
|
|
166
166
|
const defaultModel = consumeFlagValue(restArgs, "--default-model");
|
|
167
|
+
const textOnly = consumeFlag(restArgs, "--text-only");
|
|
168
|
+
const capabilityModel = consumeFlagValue(restArgs, "--model");
|
|
167
169
|
rejectUnknownArgs(restArgs, ADD_USAGE);
|
|
170
|
+
if (capabilityModel !== undefined && !textOnly) {
|
|
171
|
+
console.error("Error: --model requires --text-only for provider add.");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
168
174
|
|
|
169
175
|
const config = loadConfig();
|
|
170
176
|
|
|
@@ -224,6 +230,20 @@ async function handleAdd(args: string[]): Promise<void> {
|
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
const existingProvider = config.providers[name];
|
|
233
|
+
if (existingProvider?.modelCapabilities !== undefined && provConfig.modelCapabilities === undefined) {
|
|
234
|
+
provConfig.modelCapabilities = structuredClone(existingProvider.modelCapabilities);
|
|
235
|
+
}
|
|
236
|
+
if (textOnly) {
|
|
237
|
+
const modelId = capabilityModel ?? defaultModel ?? provConfig.defaultModel;
|
|
238
|
+
if (!modelId) {
|
|
239
|
+
console.error("Error: --text-only requires --model or a default model.");
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const declaration = { [modelId]: { inputModalities: ["text"] } };
|
|
243
|
+
const error = modelCapabilitiesConfigError(declaration);
|
|
244
|
+
if (error) { console.error(`Error: ${error}.`); process.exit(1); }
|
|
245
|
+
provConfig.modelCapabilities = mergeModelCapabilities(provConfig.modelCapabilities, declaration);
|
|
246
|
+
}
|
|
227
247
|
const { initializeProviderModelSelection } = await import("../providers/initial-model-selection");
|
|
228
248
|
initializeProviderModelSelection(name, provConfig, existingProvider, config);
|
|
229
249
|
config.providers[name] = provConfig;
|
package/src/cli/registry.ts
CHANGED
|
@@ -107,6 +107,17 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
107
107
|
"Credentials are accepted only through stdin; argv and environment credential forms are not supported.",
|
|
108
108
|
],
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: "remote-workspace",
|
|
112
|
+
usage: "ocx remote-workspace <pair|agent|status>",
|
|
113
|
+
summary: "Pair this OCX-only computer with a hub and run its remote workspace executor.",
|
|
114
|
+
details: [
|
|
115
|
+
"Pair: ocx remote-workspace pair <hub-url> --pairing-code-stdin --root <absolute-path> [--toolchain-root <absolute-directory>] [--executor-helper <absolute-file>] [--name <device-name>]",
|
|
116
|
+
"Agent: ocx remote-workspace agent",
|
|
117
|
+
"Status: ocx remote-workspace status [--json]",
|
|
118
|
+
"Codex, Claude Code, Pi, provider logins, and API keys remain on the hub; only workspace tools execute here.",
|
|
119
|
+
],
|
|
120
|
+
},
|
|
110
121
|
{
|
|
111
122
|
name: "disconnect",
|
|
112
123
|
usage: "ocx disconnect [--keep-catalog] [--json]",
|
|
@@ -132,6 +143,16 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
132
143
|
"--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.",
|
|
133
144
|
],
|
|
134
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "catalog",
|
|
148
|
+
usage: "ocx catalog pull <https-url> [--auth-env <NAME>] [--json] [--restart-codex]",
|
|
149
|
+
summary: "Install a validated remote /v1/catalog snapshot into Codex.",
|
|
150
|
+
details: [
|
|
151
|
+
"Authentication is read only from the named environment variable and sent as a Bearer token.",
|
|
152
|
+
"HTTPS is required except for loopback HTTP; redirects are refused.",
|
|
153
|
+
"The catalog and models_cache.json are coordinated under the Codex catalog write lock.",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
135
156
|
{ name: "status", usage: "ocx status", summary: "Check proxy server status." },
|
|
136
157
|
{
|
|
137
158
|
name: "doctor",
|