@bitkyc08/opencodex 2.79.0 → 2.81.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/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +10 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/autostart-health.ts +3 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +347 -199
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +16 -16
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-scope.ts +4 -1
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +21 -18
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/desktop-startup.ts +100 -3
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -111,6 +111,22 @@ export const INTEGRATION_CAPABILITIES: readonly Capability[] = [
|
|
|
111
111
|
json: "payload",
|
|
112
112
|
details: ["With --client, restore requires --profile; only Aside profiles accept that selector. No automatic drift confirmation or retry.","Preview preserves exact opId/profile/confirm-drift intent. Generic restore derives the client on the server; the returned plan does not embed opId or complete command input.","A refused or no-op preview is completed inspection, not applied work. A stale bound mutation exits5 with a fixed re-preview hint; no replacement token is automatically adopted."],
|
|
113
113
|
},
|
|
114
|
+
{
|
|
115
|
+
command: ["commandcode", "restore"],
|
|
116
|
+
usage: "ocx commandcode restore --op <opId> [--confirm-drift] [--preview | --plan-fingerprint <token>] [--json]",
|
|
117
|
+
summary: "Restore a Command Code rollback operation, retaining client ownership and drift checks.",
|
|
118
|
+
routes: [{ method: "POST", path: "/api/client-integrations/commandcode/restore" }, { method: "POST", path: "/api/client-integrations/commandcode/restore/preview" }],
|
|
119
|
+
flags: [
|
|
120
|
+
{ name: "--op", value: "string", summary: "Command Code operation ID; --op-id is an alias.", required: true },
|
|
121
|
+
{ name: "--confirm-drift", value: "boolean", summary: "Explicitly allow replacing edits made after the snapshot." },
|
|
122
|
+
{ name: "--preview", value: "boolean", summary: "Inspect restoration without mutating; exclusive with a commit fingerprint." },
|
|
123
|
+
{ name: "--plan-fingerprint", value: "string", summary: "Optional versioned token from a matching preview; stale intent is refused without retry." },
|
|
124
|
+
{ name: "--json", value: "boolean", summary: "Emit the result as JSON." },
|
|
125
|
+
],
|
|
126
|
+
mutates: true,
|
|
127
|
+
json: "payload",
|
|
128
|
+
details: ["cmd restore is an alias. --client and --profile are rejected; the dedicated route binds the operation to Command Code.", "Older proxies without the dedicated route fail without a generic restore fallback. Redirects are refused.", "Preview preserves the operation and drift intent. A stale bound mutation requires a new explicit preview."],
|
|
129
|
+
},
|
|
114
130
|
{
|
|
115
131
|
command: ["claude", "config", "status"],
|
|
116
132
|
usage: "ocx claude config status [--json]",
|
|
@@ -15,11 +15,14 @@ const DEFAULT_DEPS: CodexShimAutoRestoreCliDeps = {
|
|
|
15
15
|
readConfig: readConfigDiagnostics,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/** Identify command namespaces whose execution must not trigger automatic shim repair. */
|
|
18
19
|
export function skipsCodexShimAutoRestore(command: string | undefined, args: string[]): boolean {
|
|
19
20
|
if (command === "uninstall" || command === "remove") return true;
|
|
20
21
|
if (command === "status" || command === "doctor") return true;
|
|
21
22
|
// `lab` is read-only inspection; it must not trigger shim side effects.
|
|
22
23
|
if (command === "lab") return true;
|
|
24
|
+
// Local messaging owns its helpers; even malformed usage must not repair/start anything.
|
|
25
|
+
if (command === "message") return true;
|
|
23
26
|
// `resolve` is read-only inspection for embedding shells: a lookup made to populate
|
|
24
27
|
// a consent surface must not trigger a shim repair side effect first.
|
|
25
28
|
if (command === "resolve") return true;
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -145,6 +145,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
145
145
|
if (!deps.head.resolveArgs) return 64;
|
|
146
146
|
return await deps.handleResolve(deps.head.resolveArgs);
|
|
147
147
|
},
|
|
148
|
+
message: async deps => (await import("./message-command")).runMessageCommand(deps.args.slice(1)),
|
|
148
149
|
restore: async deps => {
|
|
149
150
|
const restoreArgs = deps.args.slice(1);
|
|
150
151
|
const restoreJson = takeFlag(restoreArgs, "--json");
|
|
@@ -465,7 +466,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
465
466
|
},
|
|
466
467
|
config,
|
|
467
468
|
port: live.port,
|
|
468
|
-
}, ["mcode", "pi", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
|
|
469
|
+
}, ["mcode", "pi", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"]));
|
|
469
470
|
} catch (error) {
|
|
470
471
|
console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
471
472
|
}
|
|
@@ -961,6 +962,14 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
961
962
|
const { handleZcodeCommand } = await import("./integrations");
|
|
962
963
|
return await handleZcodeCommand(deps.args.slice(1));
|
|
963
964
|
},
|
|
965
|
+
commandcode: async deps => {
|
|
966
|
+
const { handleCommandcodeCommand } = await import("./integrations");
|
|
967
|
+
return await handleCommandcodeCommand(deps.args.slice(1));
|
|
968
|
+
},
|
|
969
|
+
cmd: async deps => {
|
|
970
|
+
const { handleCommandcodeCommand } = await import("./integrations");
|
|
971
|
+
return await handleCommandcodeCommand(deps.args.slice(1));
|
|
972
|
+
},
|
|
964
973
|
help: async () => {
|
|
965
974
|
printUsage();
|
|
966
975
|
return 0;
|
package/src/cli/help.ts
CHANGED
|
@@ -79,6 +79,7 @@ Usage:
|
|
|
79
79
|
ocx alias <sub> Short names for providers and models (list, set, rm, defaults)
|
|
80
80
|
ocx combo <sub> Combo routing strategies and failover
|
|
81
81
|
ocx agent <sub> Subagents, injection, effort caps, and sidecars
|
|
82
|
+
ocx message <sub> Loaded local Codex sessions and queued peer messages (sessions|send)
|
|
82
83
|
ocx effort [sub] Inspect and configure reasoning effort caps and defaults
|
|
83
84
|
ocx observe <sub> Logs, usage, storage, memory, and debug data
|
|
84
85
|
ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
|
|
@@ -91,7 +92,7 @@ Usage:
|
|
|
91
92
|
ocx api-key <sub> Alias of ocx access key
|
|
92
93
|
ocx access <sub> External API keys and endpoint information
|
|
93
94
|
ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
|
|
94
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
95
|
+
ocx export --client <id> Print a client config wired to the running proxy (18 clients)
|
|
95
96
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
96
97
|
ocx grok <sub> Grok Build model selection and apply
|
|
97
98
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
@@ -105,6 +106,8 @@ Usage:
|
|
|
105
106
|
ocx mcode [args...] Launch MiniMax Code through its managed provider
|
|
106
107
|
ocx mmx text <sub> [args] Launch MiniMax CLI text through the proxy
|
|
107
108
|
ocx zcode [sub] Connect ZCode to the proxy (managed provider)
|
|
109
|
+
ocx commandcode [sub] Connect Command Code CLI to the proxy (managed provider)
|
|
110
|
+
ocx cmd [sub] Alias of ocx commandcode
|
|
108
111
|
ocx help [command] Show help
|
|
109
112
|
ocx --version | -v Print version
|
|
110
113
|
|
package/src/cli/index.ts
CHANGED
|
@@ -210,11 +210,17 @@ initializeNodeLauncherContext();
|
|
|
210
210
|
// The compiled executable is also the capture-only MCP server's launcher.
|
|
211
211
|
// Handle this private entrypoint before CLI preflight or command dispatch.
|
|
212
212
|
if (process.argv[2] === "__keyring-load-check") { console.log(JSON.stringify((await import("../lib/keyring-native")).inspectKeyringBinding())); process.exit(0); }
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
|
|
214
|
+
// The compiled executable also launches the isolated MCP servers.
|
|
215
|
+
// Handle these private entrypoints before CLI preflight or command dispatch.
|
|
216
|
+
if (process.argv[2] === "__codebuddy-mcp" || process.argv[2] === "__qoder-mcp") {
|
|
217
|
+
if (process.argv[2] === "__codebuddy-mcp") {
|
|
218
|
+
const { runCodeBuddyMcpServer } = await import("../adapters/codebuddy/mcp-server");
|
|
219
|
+
await runCodeBuddyMcpServer(process.argv[3] ?? "");
|
|
220
|
+
} else {
|
|
221
|
+
const { runCodingAgentMcpServer } = await import("../adapters/coding-agent/mcp-server");
|
|
222
|
+
await runCodingAgentMcpServer(process.argv[3] ?? "");
|
|
223
|
+
}
|
|
218
224
|
await new Promise<never>(() => {});
|
|
219
225
|
}
|
|
220
226
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Preview and explicitly bound mutations use the existing management authority. */
|
|
2
2
|
import type { IntegrationMutationPlan, IntegrationPlanOperation } from "../integrations/mutation-plan";
|
|
3
|
+
import type { IntegrationClientId } from "../integrations/registry";
|
|
3
4
|
import { redactSecretString, redactUserPath } from "../lib/redact";
|
|
4
5
|
import {
|
|
5
6
|
CliUsageError, RuntimeApiError, printData, runtimeRequest, terminalSafeText,
|
|
@@ -186,9 +187,15 @@ function reportError(error: unknown): number {
|
|
|
186
187
|
return 1;
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
190
|
+
export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}, expectedClientId?: IntegrationClientId): Promise<number> {
|
|
190
191
|
try {
|
|
191
192
|
const intent = parseIntent(argv);
|
|
193
|
+
if (expectedClientId !== undefined && intent.operation === "restore") {
|
|
194
|
+
if (intent.client !== undefined || intent.profile !== undefined) throw new CliUsageError("client-specific restore does not accept --client or --profile", USAGE);
|
|
195
|
+
intent.client = expectedClientId;
|
|
196
|
+
intent.path = `${clientIntegrationPath(expectedClientId)}/restore${intent.preview ? "/preview" : ""}`;
|
|
197
|
+
intent.body.expectedClientId = expectedClientId;
|
|
198
|
+
}
|
|
192
199
|
const result = await runtimeRequest(intent.path, {
|
|
193
200
|
method: intent.preview || intent.operation === "restore" ? "POST" : "PUT",
|
|
194
201
|
redirect: "error", body: JSON.stringify(intent.body),
|
package/src/cli/integrations.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type RuntimeApiDeps,
|
|
14
14
|
} from "./runtime-api";
|
|
15
15
|
import { clientIntegrationPath, validateAsideProfile } from "./integration-input";
|
|
16
|
+
import type { IntegrationClientId } from "../integrations/registry";
|
|
16
17
|
|
|
17
18
|
const CLAUDE_USAGE = `Usage:
|
|
18
19
|
ocx claude config [status] [--json]
|
|
@@ -215,7 +216,11 @@ function singleClientStatusLines(result: unknown): string[] {
|
|
|
215
216
|
export async function handleClientIntegrationCommand(
|
|
216
217
|
argv: string[],
|
|
217
218
|
deps: RuntimeApiDeps = {},
|
|
219
|
+
expectedClientId?: IntegrationClientId,
|
|
218
220
|
): Promise<number> {
|
|
221
|
+
if (expectedClientId !== undefined && argv.some(arg => ["--client", "--profile"].some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
|
|
222
|
+
return runCliAction(async () => { throw new CliUsageError("client-specific restore does not accept --client or --profile", CLIENT_USAGE); });
|
|
223
|
+
}
|
|
219
224
|
if ((argv[0] === "history" || argv[0] === "journal") && argv[1] === "remove") {
|
|
220
225
|
const { handleIntegrationJournalRemove } = await import("./integration-journal");
|
|
221
226
|
return handleIntegrationJournalRemove(argv.slice(2), deps);
|
|
@@ -227,7 +232,7 @@ export async function handleClientIntegrationCommand(
|
|
|
227
232
|
if (argv[0] === "preview" || argv.some(arg => ["--preview", "--plan-fingerprint", "--reasoning-default", "--clear-reasoning-defaults"]
|
|
228
233
|
.some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
|
|
229
234
|
const { handleIntegrationPreviewCommand } = await import("./integration-preview");
|
|
230
|
-
return handleIntegrationPreviewCommand(argv, deps);
|
|
235
|
+
return handleIntegrationPreviewCommand(argv, deps, expectedClientId);
|
|
231
236
|
}
|
|
232
237
|
return runCliAction(async () => {
|
|
233
238
|
const args = [...argv];
|
|
@@ -289,9 +294,12 @@ export async function handleClientIntegrationCommand(
|
|
|
289
294
|
if (client !== undefined && profile === undefined) throw new CliUsageError("restore --client requires --profile", CLIENT_USAGE);
|
|
290
295
|
rejectArgs(args, CLIENT_USAGE);
|
|
291
296
|
if (!opId) throw new CliUsageError("--op <opId> is required", CLIENT_USAGE);
|
|
292
|
-
const
|
|
297
|
+
const restorePath = expectedClientId !== undefined ? `${clientIntegrationPath(expectedClientId)}/restore`
|
|
298
|
+
: profile === undefined ? "/api/client-integrations/restore" : `${clientIntegrationPath("aside", profile)}/restore`;
|
|
299
|
+
const result = await runtimeRequest(restorePath, {
|
|
293
300
|
method: "POST",
|
|
294
|
-
|
|
301
|
+
...(expectedClientId === undefined ? {} : { redirect: "error" as const }),
|
|
302
|
+
body: JSON.stringify({ opId, confirmDrift, ...(expectedClientId === undefined ? {} : { expectedClientId }) }),
|
|
295
303
|
}, deps);
|
|
296
304
|
printData(result, wantsJson, [String((result as Record<string, unknown>).message ?? "Restored.")]);
|
|
297
305
|
return;
|
|
@@ -387,3 +395,24 @@ export async function handleClaudeInterceptCommand(argv: string[], deps: Runtime
|
|
|
387
395
|
printData(result, wantsJson, summaryLines(result));
|
|
388
396
|
});
|
|
389
397
|
}
|
|
398
|
+
|
|
399
|
+
const COMMANDCODE_USAGE = "Usage: ocx commandcode [status|show|list|enable|disable|history|journal|restore] [--json]";
|
|
400
|
+
|
|
401
|
+
export async function handleCommandcodeCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
402
|
+
const args = [...argv];
|
|
403
|
+
const verbIndex = args.findIndex(arg => !arg.startsWith("-"));
|
|
404
|
+
const action = (verbIndex === -1 ? "status" : args[verbIndex]).toLowerCase();
|
|
405
|
+
const known = ["status", "show", "list", "enable", "disable", "history", "journal", "restore"];
|
|
406
|
+
if (!known.includes(action)) {
|
|
407
|
+
console.error(`unknown commandcode command ${action}`);
|
|
408
|
+
console.error(COMMANDCODE_USAGE);
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
const rest = verbIndex === -1 ? args : [...args.slice(0, verbIndex), ...args.slice(verbIndex + 1)];
|
|
412
|
+
const forwarded = action === "restore" ? [action, ...rest] : [action, ...rest, "--client", "commandcode"];
|
|
413
|
+
const code = await handleClientIntegrationCommand(forwarded, deps, action === "restore" ? "commandcode" : undefined);
|
|
414
|
+
if (code === 0 && (action === "enable" || action === "disable")) {
|
|
415
|
+
console.error("Command Code reads providers on startup.");
|
|
416
|
+
}
|
|
417
|
+
return code;
|
|
418
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MessageKind } from "../messaging/envelope";
|
|
2
|
+
import { isThreadId } from "../messaging/types";
|
|
3
|
+
|
|
4
|
+
export type MessageArgs = { action: "sessions"; json: boolean }
|
|
5
|
+
| { action: "send"; json: boolean; kind: MessageKind; thread?: string; name?: string; inReplyTo?: string };
|
|
6
|
+
|
|
7
|
+
/** Pure pre-parse: unknown/duplicate options and unsupported remote surfaces fail closed. */
|
|
8
|
+
export function parseMessageArgs(argv: readonly string[]): MessageArgs | null {
|
|
9
|
+
const action = argv[0];
|
|
10
|
+
if (action !== "sessions" && action !== "send") return null;
|
|
11
|
+
const seen = new Set<string>();
|
|
12
|
+
const values: Record<string, string> = {};
|
|
13
|
+
for (let index = 1; index < argv.length; index++) {
|
|
14
|
+
const flag = argv[index]!;
|
|
15
|
+
if (seen.has(flag)) return null;
|
|
16
|
+
seen.add(flag);
|
|
17
|
+
if (flag === "--json" || (action === "send" && flag === "--stdin")) continue;
|
|
18
|
+
if (action !== "send" || !["--thread", "--name", "--kind", "--in-reply-to"].includes(flag)) return null;
|
|
19
|
+
const value = argv[++index];
|
|
20
|
+
if (!value || value.startsWith("--")) return null;
|
|
21
|
+
values[flag] = value;
|
|
22
|
+
}
|
|
23
|
+
const json = seen.has("--json");
|
|
24
|
+
if (action === "sessions") return { action, json };
|
|
25
|
+
const thread = values["--thread"], name = values["--name"], inReplyTo = values["--in-reply-to"];
|
|
26
|
+
const kind = values["--kind"] ?? "request";
|
|
27
|
+
if (!seen.has("--stdin") || (thread !== undefined) === (name !== undefined)
|
|
28
|
+
|| (thread !== undefined && !isThreadId(thread))
|
|
29
|
+
|| (name !== undefined && (!name.trim() || name.length > 4096 || /[\x00-\x1f\x7f-\x9f]/.test(name)))
|
|
30
|
+
|| !["request", "response", "notification"].includes(kind)
|
|
31
|
+
|| (kind === "response" ? !isThreadId(inReplyTo) : inReplyTo !== undefined)) return null;
|
|
32
|
+
return { action, json, kind: kind as MessageKind, ...(thread ? { thread } : {}),
|
|
33
|
+
...(name ? { name } : {}), ...(inReplyTo ? { inReplyTo } : {}) };
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MessageBudget } from "../messaging/budget";
|
|
2
|
+
import { readMessageInput } from "../messaging/input";
|
|
3
|
+
import { localSessions, messageFailure, sendLocalMessage } from "../messaging/send";
|
|
4
|
+
import { parseMessageArgs } from "./message-args";
|
|
5
|
+
import { terminalSafeText } from "./runtime-api";
|
|
6
|
+
|
|
7
|
+
/** Run one local messaging operation after pure syntax validation, then release owned resources. */
|
|
8
|
+
export async function runMessageCommand(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {
|
|
9
|
+
const args = parseMessageArgs(argv);
|
|
10
|
+
if (!args) {
|
|
11
|
+
console.error("Usage: ocx message sessions [--json] | send (--thread <uuid> | --name <exact-name>) --stdin [--kind request|response|notification] [--in-reply-to <uuid>] [--json]");
|
|
12
|
+
return 64;
|
|
13
|
+
}
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const cancel = () => controller.abort();
|
|
16
|
+
process.once("SIGINT", cancel); process.once("SIGTERM", cancel);
|
|
17
|
+
const budget = new MessageBudget(30_000, controller.signal);
|
|
18
|
+
try {
|
|
19
|
+
// Keep home selection on this command path and after syntax validation.
|
|
20
|
+
const { messageCodexHome } = await import("./message-runtime");
|
|
21
|
+
const home = messageCodexHome(env);
|
|
22
|
+
if (args.action === "sessions") {
|
|
23
|
+
const sessions = await localSessions(home, budget);
|
|
24
|
+
if (args.json) console.log(JSON.stringify({ schema: "ocx-message-sessions/1", sessions }));
|
|
25
|
+
else for (const session of sessions) console.log(terminalSafeText(`${session.id}\t${session.status}\t${session.name ?? "(unnamed)"}`));
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
const body = await readMessageInput(Bun.stdin.stream(), budget);
|
|
29
|
+
const receipt = await sendLocalMessage({ ...args, body },
|
|
30
|
+
{ home, senderId: env.CODEX_THREAD_ID }, budget);
|
|
31
|
+
if (args.json) console.log(JSON.stringify(receipt));
|
|
32
|
+
else console.log(terminalSafeText(`${receipt.status}: ${receipt.messageId}${receipt.error ? ` — ${receipt.error.message}` : " (submitted, not proof of processing)"}`));
|
|
33
|
+
return receipt.status === "queued" ? 0 : receipt.status === "unknown" ? 3 : 1;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const failure = { schema: "ocx-message-error/1", ...(args.action === "send" ? { status: "not_sent" } : {}), error: messageFailure(error) };
|
|
36
|
+
if (args.json) console.log(JSON.stringify(failure));
|
|
37
|
+
else console.error(failure.error.message);
|
|
38
|
+
return 1;
|
|
39
|
+
} finally {
|
|
40
|
+
budget.dispose();
|
|
41
|
+
process.removeListener("SIGINT", cancel); process.removeListener("SIGTERM", cancel);
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli/observe.ts
CHANGED
|
@@ -129,7 +129,9 @@ async function explain(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
129
129
|
rejectArgs(args, USAGE);
|
|
130
130
|
const encoded = encodeURIComponent(requestId);
|
|
131
131
|
const result = await runtimeRequest(`/api/request-history/${encoded}/route-decision`, {}, deps);
|
|
132
|
-
|
|
132
|
+
// One entry per line: printData escapes control characters per entry, so a single
|
|
133
|
+
// pretty-printed string would print its line breaks as literal `\x0a`.
|
|
134
|
+
printData(result, wantsJson, wantsJson ? undefined : JSON.stringify(result, null, 2).split("\n"));
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
async function rebuildIndex(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
package/src/cli/provider.ts
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
* show <name> Show provider config details (secrets masked)
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
|
-
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate } from "../config";
|
|
11
|
+
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config";
|
|
12
12
|
import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation";
|
|
13
13
|
import { hasHelpFlag, printSubcommandUsage } from "./help";
|
|
14
14
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
15
15
|
import { providerConfigSeed } from "../providers/derive";
|
|
16
|
+
import { assertAnthropicInstanceLoginConfig } from "../oauth/store-anthropic-instance";
|
|
16
17
|
import { dropProviderCustomModels } from "../providers/provider-id-rewrite";
|
|
17
18
|
import type { OcxProviderConfig } from "../types";
|
|
18
19
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
@@ -249,6 +250,17 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
249
250
|
|
|
250
251
|
if (responsesPath !== undefined) provConfig.responsesPath = responsesPath;
|
|
251
252
|
if (authMode !== undefined) provConfig.authMode = authMode as OcxProviderConfig["authMode"];
|
|
253
|
+
if (name === "anthropic2") {
|
|
254
|
+
if (provConfig.adapter !== "anthropic" || provConfig.authMode !== "oauth") delete provConfig.anthropicOAuthInstance;
|
|
255
|
+
if (provConfig.anthropicOAuthInstance) {
|
|
256
|
+
try { assertAnthropicInstanceLoginConfig(config, name); }
|
|
257
|
+
catch {
|
|
258
|
+
console.error("Error: cannot add Pool 2 over an existing custom or unreadable provider configuration; resolve it first.");
|
|
259
|
+
process.exitCode = 2;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
252
264
|
if (name === "openai" && (authMode !== undefined || responsesPath !== undefined)
|
|
253
265
|
&& !isCanonicalOpenAiForwardProvider(provConfig)) {
|
|
254
266
|
console.error("Error: Canonical OpenAI must keep its built-in forward destination and authentication. Use a separate provider name for a custom endpoint.");
|
|
@@ -315,11 +327,22 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
315
327
|
return;
|
|
316
328
|
}
|
|
317
329
|
const { initializeProviderModelSelection } = await import("../providers/initial-model-selection");
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
330
|
+
let markerCollision = false;
|
|
331
|
+
withConfigMutationLockSync(() => {
|
|
332
|
+
if (provConfig.anthropicOAuthInstance) {
|
|
333
|
+
try { assertAnthropicInstanceLoginConfig(config, name); }
|
|
334
|
+
catch { markerCollision = true; return; }
|
|
335
|
+
}
|
|
336
|
+
initializeProviderModelSelection(name, provConfig, existingProvider, config);
|
|
337
|
+
config.providers[name] = provConfig;
|
|
338
|
+
if (setDefault) config.defaultProvider = name;
|
|
339
|
+
validateAndSave(config);
|
|
340
|
+
});
|
|
341
|
+
if (markerCollision) {
|
|
342
|
+
console.error("Error: provider configuration changed while adding Pool 2; resolve the ownership collision first.");
|
|
343
|
+
process.exitCode = 2;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
323
346
|
|
|
324
347
|
let sync: LocalSyncResult | undefined;
|
|
325
348
|
if (wantsSync) {
|
package/src/cli/registry.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface CliCommandEntry {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
11
|
+
{
|
|
12
|
+
name: "message",
|
|
13
|
+
usage: "ocx message <sessions|send> [options]",
|
|
14
|
+
summary: "Discover loaded local Codex sessions and submit one queued peer message.",
|
|
15
|
+
details: ["Local Unix transport only; starts no daemon. queued is submission, not processing; never replay an unknown send."],
|
|
16
|
+
},
|
|
11
17
|
{
|
|
12
18
|
name: "chatgpt",
|
|
13
19
|
usage: "ocx chatgpt <launch|restore|status>",
|
|
@@ -467,8 +473,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
467
473
|
},
|
|
468
474
|
{
|
|
469
475
|
name: "export",
|
|
470
|
-
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
|
|
471
|
-
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
|
|
476
|
+
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
|
|
477
|
+
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
|
|
472
478
|
details: [
|
|
473
479
|
"--json prints the generated document as JSON on stdout; use --out for the client's native format.",
|
|
474
480
|
"--out <path> writes the native config there and refuses to replace an existing file without --force.",
|
|
@@ -598,6 +604,22 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
598
604
|
"Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
|
|
599
605
|
],
|
|
600
606
|
},
|
|
607
|
+
{
|
|
608
|
+
name: "commandcode",
|
|
609
|
+
usage: "ocx commandcode [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
|
|
610
|
+
summary: "Connect Command Code CLI to the proxy via its managed provider.",
|
|
611
|
+
details: [
|
|
612
|
+
"Alias of ocx integration client <sub> --client commandcode.",
|
|
613
|
+
"enable manages the opencodex block under the existing provider or providers root in ~/.commandcode/providers.json; disable removes only that block.",
|
|
614
|
+
"Command Code reads providers on startup.",
|
|
615
|
+
"`restore --op <opId>` replays the exact previous managed block; take the id from `ocx commandcode history`.",
|
|
616
|
+
],
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "cmd",
|
|
620
|
+
usage: "ocx cmd [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
|
|
621
|
+
summary: "Alias of ocx commandcode.",
|
|
622
|
+
},
|
|
601
623
|
{
|
|
602
624
|
name: "restart",
|
|
603
625
|
usage: "ocx restart",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Command Code config export.
|
|
2
|
+
import type { ExportContext, ManagedContribution } from "./contracts";
|
|
3
|
+
import { normalizeExportModels, authoritativeContextWindow, singleFragment } from "./model-metadata";
|
|
4
|
+
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
|
|
5
|
+
import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
6
|
+
|
|
7
|
+
export interface CommandCodeModelEntry {
|
|
8
|
+
reasoningEfforts?: string[];
|
|
9
|
+
contextWindow?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandCodeProviderBlock {
|
|
13
|
+
name: "OpenCodex";
|
|
14
|
+
api: "openai-completions";
|
|
15
|
+
baseURL: string;
|
|
16
|
+
apiKey: string | boolean;
|
|
17
|
+
models: Record<string, CommandCodeModelEntry>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CommandCodeGeneratedConfig {
|
|
21
|
+
provider: Record<string, CommandCodeProviderBlock>;
|
|
22
|
+
/** The root a pre-existing target already uses; see {@link commandCodeProviderRoot}. */
|
|
23
|
+
providers?: Record<string, CommandCodeProviderBlock>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* LOSSY spelling-collision key for a routed selector — not model identity.
|
|
28
|
+
*
|
|
29
|
+
* One provider model reaches this exporter under interchangeable spellings: the raw
|
|
30
|
+
* selector keeps inner slashes (`command-code/deepseek/deepseek-v4.1-flash`, what
|
|
31
|
+
* `/v1/models` publishes) while the Codex-facing form encodes them as dashes
|
|
32
|
+
* (`command-code/deepseek-deepseek-v4.1-flash`, what `~/.codex/config.toml` stores and
|
|
33
|
+
* what an operator typing `--model` copies). Command Code addresses models by exact key,
|
|
34
|
+
* so a catalog that carries both spellings can resolve the active model against one and
|
|
35
|
+
* miss it in the other.
|
|
36
|
+
*
|
|
37
|
+
* The slash is meaningful: it separates the provider from the model id, and a provider id
|
|
38
|
+
* never contains one. But the encoding is NOT bijective — `p/a/b` and `p/a-b` are
|
|
39
|
+
* different model ids that encode to the same string. That is the same lossy relation
|
|
40
|
+
* `slugEquivalenceKey` documents in src/providers/slug-codec.ts and the same ambiguity
|
|
41
|
+
* `resolveSlugAliasCollisions` guards in the catalog. Callers must therefore never drop
|
|
42
|
+
* a row on this key alone; buildCommandCodeClientConfig additionally compares provider
|
|
43
|
+
* and id before folding two rows into one.
|
|
44
|
+
*/
|
|
45
|
+
function canonicalSpellingOf(namespaced: string): string {
|
|
46
|
+
const slash = namespaced.indexOf("/");
|
|
47
|
+
if (slash <= 0) return namespaced;
|
|
48
|
+
return namespaced.slice(0, slash) + "/" + namespaced.slice(slash + 1).replaceAll("/", "-");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildCommandCodeClientConfig(ctx: ExportContext): CommandCodeGeneratedConfig {
|
|
52
|
+
const models: Record<string, CommandCodeModelEntry> = {};
|
|
53
|
+
// Fold interchangeable spellings of one model onto a single key. The first
|
|
54
|
+
// occurrence wins per provider+id identity; distinct model ids that happen to
|
|
55
|
+
// share a lossy spelling collision must both remain addressable.
|
|
56
|
+
const emittedIdentitiesByCollision = new Map<string, Set<string>>();
|
|
57
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
58
|
+
const emittedKey = model.namespaced;
|
|
59
|
+
const collisionKey = canonicalSpellingOf(model.namespaced);
|
|
60
|
+
const identity = `${model.provider}\u0000${model.id}`;
|
|
61
|
+
const identities = emittedIdentitiesByCollision.get(collisionKey) ?? new Set<string>();
|
|
62
|
+
if (identities.has(identity)) continue;
|
|
63
|
+
identities.add(identity);
|
|
64
|
+
emittedIdentitiesByCollision.set(collisionKey, identities);
|
|
65
|
+
const entry: CommandCodeModelEntry = {};
|
|
66
|
+
const context = authoritativeContextWindow(model.contextWindow);
|
|
67
|
+
if (context !== undefined) {
|
|
68
|
+
entry.contextWindow = context;
|
|
69
|
+
}
|
|
70
|
+
const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
|
|
71
|
+
?.filter(effort => ["low", "medium", "high", "xhigh", "max"].includes(effort));
|
|
72
|
+
if (efforts && efforts.length > 0) {
|
|
73
|
+
entry.reasoningEfforts = efforts;
|
|
74
|
+
}
|
|
75
|
+
models[emittedKey] = entry;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
provider: {
|
|
79
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
80
|
+
name: "OpenCodex",
|
|
81
|
+
api: "openai-completions",
|
|
82
|
+
baseURL: ctx.baseUrl.replace(/\/v1\/?$/, "") + "/v1",
|
|
83
|
+
apiKey: false,
|
|
84
|
+
models,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The root key Command Code actually reads: `provider`, else `providers`.
|
|
92
|
+
*
|
|
93
|
+
* Verified against the published client (`command-code@1.66.0`, `dist/cli.mjs`):
|
|
94
|
+
* `const o = e.provider ?? e.providers;` — singular wins, and the plural branch is
|
|
95
|
+
* only reached when the singular root is nullish (`null` or `undefined`). Adding a
|
|
96
|
+
* singular root to a document that already carries a plural one therefore does not
|
|
97
|
+
* merge the two: the old entries stay on disk, byte for byte, and become invisible
|
|
98
|
+
* to the consumer.
|
|
99
|
+
*
|
|
100
|
+
* Evaluates nullish precedence before container validation so that non-nullish
|
|
101
|
+
* invalid singular values (such as strings or arrays) remain selected and are refused
|
|
102
|
+
* by the container guard rather than falling through to write an unreachable plural block.
|
|
103
|
+
*/
|
|
104
|
+
export function commandCodeProviderRoot(document: unknown): "provider" | "providers" {
|
|
105
|
+
const doc = document as { provider?: unknown; providers?: unknown } | null | undefined;
|
|
106
|
+
if (doc?.provider !== undefined && doc?.provider !== null) return "provider";
|
|
107
|
+
if (doc?.providers !== undefined && doc?.providers !== null) return "providers";
|
|
108
|
+
// Nothing established yet: the singular root is the one this exporter writes.
|
|
109
|
+
return "provider";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function summarizeCommandCode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
|
|
113
|
+
const doc = document as CommandCodeGeneratedConfig | undefined;
|
|
114
|
+
const root = commandCodeProviderRoot(document);
|
|
115
|
+
const models = Object.values(doc?.[root]?.[OPENCODE_PROVIDER_ID]?.models ?? {}) as CommandCodeModelEntry[];
|
|
116
|
+
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The provider block, written under whichever root the target document already uses.
|
|
121
|
+
*
|
|
122
|
+
* `ExportContext` carries the parsed target document so the root can be chosen from
|
|
123
|
+
* what is on disk rather than assumed. A fresh file gets the singular root this
|
|
124
|
+
* exporter has always written; a document that already carries a plural root keeps
|
|
125
|
+
* it, so enabling OpenCodex never makes the user's other providers unreadable.
|
|
126
|
+
*/
|
|
127
|
+
export function buildCommandCodeContribution(ctx: ExportContext): ManagedContribution {
|
|
128
|
+
const doc = buildCommandCodeClientConfig(ctx);
|
|
129
|
+
const root = commandCodeProviderRoot(ctx.document);
|
|
130
|
+
return singleFragment("commandcode", [root, OPENCODE_PROVIDER_ID], doc.provider[OPENCODE_PROVIDER_ID]);
|
|
131
|
+
}
|
|
@@ -185,6 +185,17 @@ export interface ExportContext {
|
|
|
185
185
|
*/
|
|
186
186
|
config?: OcxConfig;
|
|
187
187
|
droidReasoningDefaults?: DroidReasoningDefaults;
|
|
188
|
+
/**
|
|
189
|
+
* The parsed target document, when the caller has one in hand.
|
|
190
|
+
*
|
|
191
|
+
* A client whose reader picks between two roots needs the bytes on disk to
|
|
192
|
+
* choose: Command Code resolves `document.provider ?? document.providers`, so
|
|
193
|
+
* writing the singular root into a document that already carries the plural one
|
|
194
|
+
* leaves the user's providers unreadable rather than merging with them. Absent
|
|
195
|
+
* for `ocx export`, which has no target file — the builder then writes its
|
|
196
|
+
* default root, exactly as before.
|
|
197
|
+
*/
|
|
198
|
+
document?: unknown;
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
/** Namespaced model selector to one of that model's declared reasoning efforts. */
|
|
@@ -206,6 +217,7 @@ export type ExportClientId =
|
|
|
206
217
|
| "raycast"
|
|
207
218
|
| "omo"
|
|
208
219
|
| "cline"
|
|
220
|
+
| "commandcode"
|
|
209
221
|
| "kilo"
|
|
210
222
|
| "droid";
|
|
211
223
|
|