@bitkyc08/opencodex 2.76.0 → 2.77.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/App-COdEeqSj.css +1 -0
- package/gui/dist/assets/App-DEpCJEP_.js +54 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-gloIYNaW.js} +1 -1
- package/gui/dist/assets/index-Cd7ittMv.css +1 -0
- package/gui/dist/assets/index-DwJKFJm5.js +94 -0
- package/gui/dist/assets/quota-summary-DGcEJU3h.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/ollama-native.ts +29 -2
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/account-api.ts +3 -0
- package/src/cli/account.ts +3 -1
- package/src/cli/agent.ts +10 -0
- package/src/cli/capabilities.ts +9 -0
- package/src/cli/chatgpt-command.ts +10 -7
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-readiness.ts +89 -0
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +3 -3
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +84 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +52 -8
- package/src/cli/models-runtime.ts +2 -1
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider.ts +3 -29
- package/src/cli/registry.ts +29 -5
- package/src/cli/root.ts +30 -9
- package/src/cli/runtime-api.ts +1 -1
- package/src/cli/tray-proxy.ts +3 -2
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +46 -66
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/retained-sync.ts +2 -1
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/main-account.ts +144 -11
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +33 -1
- package/src/codex/quota.ts +56 -15
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/shim-path-resolution.ts +133 -0
- package/src/codex/shim.ts +39 -39
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/combos/failover.ts +82 -1
- package/src/combos/jev.ts +13 -3
- package/src/config/diagnostics.ts +4 -0
- package/src/config/load-degrade.ts +4 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +11 -0
- package/src/config/schema/leaf-validators.ts +5 -1
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +245 -153
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +4 -2
- package/src/integrations/state.ts +68 -6
- package/src/integrations/writer.ts +18 -4
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +3 -3
- package/src/oauth/anthropic.ts +7 -1
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +36 -27
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +18 -11
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +6 -129
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/integration-routes.ts +64 -6
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/route-registry.ts +4 -3
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +100 -1
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +57 -26
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +18 -1
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +18 -0
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsonUtf8Bytes } from "../../lib/json-byte-size";
|
|
2
|
+
|
|
3
|
+
export const PICKER_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
4
|
+
const MAX_FIELD_BYTES = 64 * 1024;
|
|
5
|
+
const MAX_ROW_BYTES = 256 * 1024;
|
|
6
|
+
const MAX_ADDED_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
const MAX_ADDED_ROWS = 4096;
|
|
8
|
+
|
|
9
|
+
/** Inspect references only: no deep copy or serialized string is allocated by preflight. */
|
|
10
|
+
export function pickerRowBytes(row: Record<string, unknown>): number {
|
|
11
|
+
for (const key of Object.keys(row)) {
|
|
12
|
+
jsonUtf8Bytes(key, MAX_FIELD_BYTES);
|
|
13
|
+
if (row[key] !== undefined) jsonUtf8Bytes(row[key], MAX_FIELD_BYTES);
|
|
14
|
+
}
|
|
15
|
+
return jsonUtf8Bytes(row, MAX_ROW_BYTES);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** One budget for the entire response, including duplicate surfaces and the CLI fallback. */
|
|
19
|
+
export class PickerRewriteBudget {
|
|
20
|
+
private outputBytes: number;
|
|
21
|
+
private addedBytes = 0;
|
|
22
|
+
private addedRows = 0;
|
|
23
|
+
|
|
24
|
+
constructor(body: unknown) {
|
|
25
|
+
this.outputBytes = jsonUtf8Bytes(body, PICKER_MAX_OUTPUT_BYTES);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
reserveBytes(bytes: number): void {
|
|
29
|
+
if (bytes > MAX_ADDED_BYTES - this.addedBytes || bytes > PICKER_MAX_OUTPUT_BYTES - this.outputBytes) {
|
|
30
|
+
throw new RangeError("Picker rewrite byte limit");
|
|
31
|
+
}
|
|
32
|
+
this.addedBytes += bytes;
|
|
33
|
+
this.outputBytes += bytes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
reserveRow(row: Record<string, unknown>): void {
|
|
37
|
+
if (++this.addedRows > MAX_ADDED_ROWS) throw new RangeError("Picker rewrite row limit");
|
|
38
|
+
// Include a separator even for an empty list, conservatively reserving one extra byte.
|
|
39
|
+
this.reserveBytes(pickerRowBytes(row) + 1);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/claude/outbound.ts
CHANGED
|
@@ -248,6 +248,7 @@ export function anthropicIncompleteOutcome(
|
|
|
248
248
|
*/
|
|
249
249
|
export function anthropicFailedStatus(error: Rec, message: string): number {
|
|
250
250
|
const code = typeof error.code === "string" ? error.code : undefined;
|
|
251
|
+
if (code === "context_length_exceeded") return 400;
|
|
251
252
|
return code === "translation_buffer_limit"
|
|
252
253
|
? 413
|
|
253
254
|
: typeof error.status === "number"
|
|
@@ -726,6 +727,7 @@ export function responsesSseToAnthropicSse(
|
|
|
726
727
|
status,
|
|
727
728
|
message,
|
|
728
729
|
true,
|
|
730
|
+
code === "context_length_exceeded" ? code : undefined,
|
|
729
731
|
);
|
|
730
732
|
break;
|
|
731
733
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { claudeConfigDir } from "./gateway-cache";
|
|
5
|
+
import { commandInvocation } from "../lib/win-exec";
|
|
6
|
+
|
|
7
|
+
export interface SubagentForceStatus {
|
|
8
|
+
targetValid: boolean;
|
|
9
|
+
version: string | null;
|
|
10
|
+
support: "supported" | "unsupported" | "unknown";
|
|
11
|
+
settingsOverride: boolean;
|
|
12
|
+
settingsReadable: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function subagentForceSupport(version: string | null): SubagentForceStatus["support"] {
|
|
16
|
+
const match = version?.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
17
|
+
if (!match) return "unknown";
|
|
18
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
19
|
+
return major! > 2 || (major === 2 && (minor! > 1 || (minor === 1 && patch! >= 257))) ? "supported" : "unsupported";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function installedVersion(): Promise<string | null> {
|
|
23
|
+
const invocation = commandInvocation("claude", ["--version"]);
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
execFile(invocation.file, invocation.args, { ...invocation.options, timeout: 1500, maxBuffer: 4096 }, (error, stdout) => {
|
|
26
|
+
resolve(error ? null : /^\s*(\d+\.\d+\.\d+)(?:\s|$)/.exec(String(stdout))?.[1] ?? null);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Server-local observation only: never returns settings values or modifies settings. */
|
|
32
|
+
export async function inspectSubagentForceStatus(
|
|
33
|
+
targetValid: boolean,
|
|
34
|
+
configDir = claudeConfigDir(),
|
|
35
|
+
versionProbe: () => Promise<string | null> = installedVersion,
|
|
36
|
+
): Promise<SubagentForceStatus> {
|
|
37
|
+
const version = await versionProbe().catch(() => null);
|
|
38
|
+
let settingsOverride = false;
|
|
39
|
+
let settingsReadable = true;
|
|
40
|
+
try {
|
|
41
|
+
const path = join(configDir, "settings.json");
|
|
42
|
+
const metadata = await stat(path);
|
|
43
|
+
if (!metadata.isFile() || metadata.size > 1_048_576) throw new Error("settings unavailable");
|
|
44
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
45
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid settings");
|
|
46
|
+
const env = parsed.env;
|
|
47
|
+
settingsOverride = !!env && typeof env === "object" && !Array.isArray(env)
|
|
48
|
+
&& (Object.hasOwn(env, "CLAUDE_CODE_SUBAGENT_MODEL") || Object.hasOwn(env, "CLAUDE_CODE_SUBAGENT_MODEL_FORCE"));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
settingsReadable = (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
51
|
+
}
|
|
52
|
+
return { targetValid, version, support: subagentForceSupport(version), settingsOverride, settingsReadable };
|
|
53
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
import { claudeCodeAlias, claudeCodeNativeAlias, resolveAlias } from "./alias";
|
|
3
|
+
import { AUTO_CONTEXT_OFF, shouldMarkOneMillion, stripOneMillionMarker, withOneMillionMarker } from "./context-windows";
|
|
4
|
+
import { hasOwnProvider } from "../config";
|
|
5
|
+
import { knownModelIdsForProvider } from "../router";
|
|
6
|
+
import { decodeRoutedModelIdOrThrow } from "../providers/slug-codec";
|
|
7
|
+
import { SAFE_AGENT_MODEL_ID } from "../config/subagent-models";
|
|
8
|
+
import { resolveDesktop3pAlias } from "./desktop-3p";
|
|
9
|
+
export { SAFE_AGENT_MODEL_ID } from "../config/subagent-models";
|
|
10
|
+
|
|
11
|
+
/** Roster entry -> alias + display parts. Entries are bare native slugs or "provider/id".
|
|
12
|
+
* Codex-facing encoded ids (`provider/vendor-model`) decode to the native slash id first
|
|
13
|
+
* so the alias joins the raw-native context-window map (context-windows.ts). */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generated subagent defs cannot rely on the parent's auto-context compaction
|
|
17
|
+
* pairing, so their [1m] marker follows the AUTHORITATIVE window only: mark when
|
|
18
|
+
* the effective window (exact selector, then the canonical [1m] form, then bare)
|
|
19
|
+
* is genuinely >= 1M; strip an inherited unsafe marker back to the bare selector;
|
|
20
|
+
* with no window information, keep the selector as it was. Genuine routed [1m]
|
|
21
|
+
* ids are preserved through the canonical-exact lookup. (#854)
|
|
22
|
+
*/
|
|
23
|
+
export function withSubagentContextMarker(selector: string, windows: Record<string, number>): string {
|
|
24
|
+
const bare = stripOneMillionMarker(selector);
|
|
25
|
+
const wasMarked = selector !== bare;
|
|
26
|
+
const canonicalExact = wasMarked ? `${bare}[1m]` : selector;
|
|
27
|
+
const authoritativeWindow = windows[selector] ?? windows[canonicalExact] ?? windows[bare];
|
|
28
|
+
if (typeof authoritativeWindow === "number" && authoritativeWindow > 0) {
|
|
29
|
+
return shouldMarkOneMillion(authoritativeWindow, AUTO_CONTEXT_OFF)
|
|
30
|
+
? (withOneMillionMarker(selector, windows) ?? selector)
|
|
31
|
+
: bare;
|
|
32
|
+
}
|
|
33
|
+
return wasMarked ? selector : bare;
|
|
34
|
+
}
|
|
35
|
+
export function entryParts(entry: string, config: OcxConfig): { alias: string; id: string; provider: string } {
|
|
36
|
+
const slash = entry.indexOf("/");
|
|
37
|
+
if (slash > 0) {
|
|
38
|
+
const provider = entry.slice(0, slash);
|
|
39
|
+
const prov = hasOwnProvider(config.providers, provider) ? config.providers[provider] : undefined;
|
|
40
|
+
const id = prov
|
|
41
|
+
? decodeRoutedModelIdOrThrow(entry.slice(slash + 1), knownModelIdsForProvider(provider, prov, config))
|
|
42
|
+
: entry.slice(slash + 1);
|
|
43
|
+
return { alias: claudeCodeAlias(provider, id), id, provider };
|
|
44
|
+
}
|
|
45
|
+
return { alias: claudeCodeNativeAlias(entry), id: entry, provider: "native" };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type SubagentForceExposure = { entries: readonly string[] } | { selectors: readonly string[] };
|
|
49
|
+
|
|
50
|
+
function fullSelectorIdentity(selector: string): string {
|
|
51
|
+
const route = resolveAlias(selector) ?? resolveDesktop3pAlias(selector) ?? selector;
|
|
52
|
+
return (route.startsWith("native/") ? route.slice("native/".length) : route).replace(/\[1m\]$/i, "[1m]");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function selectorIdentity(selector: string): string {
|
|
56
|
+
return fullSelectorIdentity(stripOneMillionMarker(selector));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolve only currently exposed entries; retained roster rows are not exposure proof. */
|
|
60
|
+
export function resolveSubagentForceModel(config: OcxConfig, windows: Record<string, number>, available: SubagentForceExposure): string | null {
|
|
61
|
+
const entry = config.claudeCode?.subagentModelForce;
|
|
62
|
+
if (typeof entry !== "string" || !SAFE_AGENT_MODEL_ID.test(entry)) return null;
|
|
63
|
+
try {
|
|
64
|
+
const parts = entryParts(entry, config);
|
|
65
|
+
if ((config.disabledModels ?? []).some(disabled => {
|
|
66
|
+
try { return selectorIdentity(entryParts(disabled, config).alias) === selectorIdentity(parts.alias); } catch { return false; }
|
|
67
|
+
})) return null;
|
|
68
|
+
const selectors = "selectors" in available ? available.selectors : available.entries.flatMap(candidate => {
|
|
69
|
+
try { return [entryParts(candidate, config).alias]; } catch { return []; }
|
|
70
|
+
});
|
|
71
|
+
const exposed = selectors.some(candidate => selectorIdentity(candidate) === selectorIdentity(parts.alias));
|
|
72
|
+
if (!exposed) return null;
|
|
73
|
+
const bare = stripOneMillionMarker(parts.alias);
|
|
74
|
+
const requestedMarker = bare !== parts.alias;
|
|
75
|
+
const exactMarkedExposure = requestedMarker && selectors.some(candidate =>
|
|
76
|
+
fullSelectorIdentity(candidate) === fullSelectorIdentity(parts.alias));
|
|
77
|
+
const window = windows[parts.alias] ?? windows[`${bare}[1m]`] ?? windows[bare];
|
|
78
|
+
// Identity admission must not let an appended context promise ride on an
|
|
79
|
+
// ordinary exposed model. Exact catalog ids such as kimi/k3[1m] remain literal.
|
|
80
|
+
if (requestedMarker && !exactMarkedExposure && !(typeof window === "number" && Number.isFinite(window) && window >= 1_000_000)) return null;
|
|
81
|
+
const marked = exactMarkedExposure ? parts.alias : withSubagentContextMarker(parts.alias, windows);
|
|
82
|
+
// Bare Anthropic names are indistinguishable from the old CLI's fallback.
|
|
83
|
+
// Encode only the force selector; handlers restore native identity before
|
|
84
|
+
// their existing credential/model-map checks. Roster generation is unchanged.
|
|
85
|
+
if (parts.alias.startsWith("claude-") && !resolveAlias(parts.alias) && !resolveDesktop3pAlias(parts.alias)) {
|
|
86
|
+
const native = claudeCodeNativeAlias(stripOneMillionMarker(marked));
|
|
87
|
+
return marked === stripOneMillionMarker(marked) ? native : `${native}[1m]`;
|
|
88
|
+
}
|
|
89
|
+
return marked;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/cli/account-api.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface AccountRow {
|
|
|
27
27
|
masked?: string;
|
|
28
28
|
active: boolean;
|
|
29
29
|
needsReauth?: boolean;
|
|
30
|
+
needsReauthReason?: "verify_account";
|
|
30
31
|
autoSelectable?: boolean;
|
|
31
32
|
skipReason?: "paused" | "needs_reauth" | "suspended" | "cooldown" | "quota_exhausted";
|
|
32
33
|
selectionExcludedReason?: "plan_excluded";
|
|
@@ -348,6 +349,7 @@ interface OAuthAccountDto {
|
|
|
348
349
|
/** Present only for providers that support operator pause (generic OAuth pools). */
|
|
349
350
|
paused?: boolean;
|
|
350
351
|
autoSwitchThresholdOverride?: number | null;
|
|
352
|
+
needsReauthReason?: "verify_account";
|
|
351
353
|
autoSelectable?: boolean;
|
|
352
354
|
skipReason?: unknown;
|
|
353
355
|
/** Always sent by the management route; explicitly `null` when the tier is unknown. */
|
|
@@ -390,6 +392,7 @@ async function fetchOAuthRows(
|
|
|
390
392
|
needsReauth: a.needsReauth,
|
|
391
393
|
...(a.paused === true ? { paused: true } : {}),
|
|
392
394
|
...(name === "anthropic" && Object.hasOwn(a, "autoSwitchThresholdOverride") ? { autoSwitchThresholdOverride: a.autoSwitchThresholdOverride } : {}),
|
|
395
|
+
...(a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
393
396
|
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
394
397
|
? { autoSelectable: a.autoSelectable } : {}),
|
|
395
398
|
...(name === "kiro" && a.autoSelectable === false && isKiroSkipReason(a.skipReason)
|
package/src/cli/account.ts
CHANGED
|
@@ -110,7 +110,9 @@ function statusText(row: AccountRow): string {
|
|
|
110
110
|
// held out -- so printing only one of the two would hide exactly the confusing case (#2703).
|
|
111
111
|
if (row.paused) parts.push("paused");
|
|
112
112
|
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
113
|
-
if (row.needsReauth && !(row.provider === "kiro" && row.skipReason === "needs_reauth"))
|
|
113
|
+
if (row.needsReauth && !(row.provider === "kiro" && row.skipReason === "needs_reauth")) {
|
|
114
|
+
parts.push(row.needsReauthReason === "verify_account" ? "needs-reauth(verify)" : "needs-reauth");
|
|
115
|
+
}
|
|
114
116
|
// A paused Kiro row already says "paused"; repeating it as a skip reason adds nothing.
|
|
115
117
|
if (row.provider === "kiro" && row.autoSelectable === false && !(row.paused && row.skipReason === "paused"))
|
|
116
118
|
parts.push(row.skipReason ? `not-auto-selected(${row.skipReason})` : "not-auto-selected");
|
package/src/cli/agent.ts
CHANGED
|
@@ -28,6 +28,7 @@ const USAGE = `Usage:
|
|
|
28
28
|
ocx agent injection suggest <work description> [--model <id>] [--apply] [--json]
|
|
29
29
|
ocx agent effort <status|set> [--main <level|->] [--subagent <level|->] [--json]
|
|
30
30
|
ocx agent subagents <status|set|clear> [model,model...] [--json]
|
|
31
|
+
ocx agent subagents force <model|-> [--json]
|
|
31
32
|
ocx agent fallback <status|set|clear> [model,model...] [--poll-ms <5000-600000>] [--json]
|
|
32
33
|
ocx agent roles [status|set <role> <model>|suggest [--model <id>] [--apply]] [--json]
|
|
33
34
|
ocx agent sidecar <status|web|vision> [--list] [--model <id|->]
|
|
@@ -169,6 +170,15 @@ async function subagents(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
169
170
|
printData(result, wantsJson, summaryLines(result));
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
173
|
+
if (action === "force") {
|
|
174
|
+
const model = args.shift();
|
|
175
|
+
if (!model?.trim()) throw new CliUsageError("a force model or - is required", USAGE);
|
|
176
|
+
rejectArgs(args, USAGE);
|
|
177
|
+
const force = clearable(model);
|
|
178
|
+
const result = await runtimeRequest("/api/subagent-models", { method: "PUT", body: JSON.stringify({ force }) }, deps);
|
|
179
|
+
printData(result, wantsJson, [force ? `Claude subagent force: ${force}. Applies on the next routed ocx claude launch.` : "Claude subagent force cleared."]);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
172
182
|
let models: string[];
|
|
173
183
|
if (action === "clear") models = [];
|
|
174
184
|
else if (action === "set") {
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -1063,6 +1063,15 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
1063
1063
|
json: "none",
|
|
1064
1064
|
details: ["The Aside refresh uses the live server; other catalog synchronization also performs local work."],
|
|
1065
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
command: ["agent", "subagents", "force"],
|
|
1068
|
+
summary: "Force Claude Code subagents onto one exposed model at the next routed launch; - clears.",
|
|
1069
|
+
routes: [{ method: "GET", path: "/api/subagent-models" }, { method: "PUT", path: "/api/subagent-models" }],
|
|
1070
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit the saved force setting." }],
|
|
1071
|
+
mutates: true,
|
|
1072
|
+
json: "payload",
|
|
1073
|
+
details: ["Requires Claude Code 2.1.257+. Plain claude, forks, inherit skills, the main model and small-fast sidecars are unaffected."],
|
|
1074
|
+
},
|
|
1066
1075
|
{
|
|
1067
1076
|
command: ["agent", "request-user-input"],
|
|
1068
1077
|
summary: "Show or set whether default mode may ask the operator a question mid-task.",
|
|
@@ -101,23 +101,26 @@ CODEX_CLI_PATH launcher: ${app.shim ? "yes" : "no"}`);
|
|
|
101
101
|
console.error("ChatGPT (com.openai.codex) was not found; install or open it once, then retry.");
|
|
102
102
|
return 1;
|
|
103
103
|
}
|
|
104
|
+
let binary: string | undefined;
|
|
104
105
|
if (sub === "launch") {
|
|
105
106
|
if (config.chatgptDesktop?.appServerShim !== true) {
|
|
106
107
|
console.error('Experimental shim disabled; set chatgptDesktop.appServerShim: true before launching.');
|
|
107
108
|
return 1;
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
binary = resolveChatgptCodexBinary(install.root) ?? undefined;
|
|
110
111
|
if (!binary) {
|
|
111
112
|
console.error(`No bundled app-server binary was found in ${install.root}; the shim cannot launch this build.`);
|
|
112
113
|
return 1;
|
|
113
114
|
}
|
|
114
|
-
const untrusted = untrustedChatgptBundleReason(install.root, binary);
|
|
115
|
-
if (untrusted) {
|
|
116
|
-
console.error(`Refusing to launch the shim: ${untrusted}.`);
|
|
117
|
-
return 1;
|
|
118
|
-
}
|
|
119
|
-
writeChatgptShimLauncher(undefined, binary);
|
|
120
115
|
}
|
|
116
|
+
// Both relaunch paths execute the discovered bundle. Restore validates the app
|
|
117
|
+
// shell without requiring an app-server binary or the experimental opt-in flag.
|
|
118
|
+
const untrusted = untrustedChatgptBundleReason(install.root, binary);
|
|
119
|
+
if (untrusted) {
|
|
120
|
+
console.error(`Refusing to ${sub === "launch" ? "launch the shim" : "restore ChatGPT"}: ${untrusted}.`);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
if (binary) writeChatgptShimLauncher(undefined, binary);
|
|
121
124
|
if (!(await quitApp(install, launcher))) {
|
|
122
125
|
console.error("ChatGPT did not quit; quit it manually and retry.");
|
|
123
126
|
return 1;
|
package/src/cli/claude.ts
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* `claude` CLI with stdio inherited. User-exported env wins except when a stale
|
|
8
8
|
* loopback opencodex base URL points at a different proxy port.
|
|
9
9
|
*/
|
|
10
|
+
import { resolveSubagentForceModel } from "../claude/subagent-model";
|
|
10
11
|
import { spawn } from "node:child_process";
|
|
11
12
|
import { loadConfig } from "../config";
|
|
12
13
|
import { injectClaudeAgentDefs } from "../claude/agents-inject";
|
|
13
14
|
import { CLAUDE_ALIAS_PREFIX_CURRENT, CLAUDE_ALIAS_PREFIX_CURRENT_V2, CLAUDE_ALIAS_PREFIX_V1, CLAUDE_ALIAS_PREFIX_V2 } from "../claude/alias";
|
|
14
15
|
import { claudeToolSearchEnv, effectiveModelEnv, resolveAutoContext } from "../claude/context-windows";
|
|
15
|
-
import { claudeConfigDir,
|
|
16
|
+
import { claudeConfigDir, fetchGatewayModels, writeGatewayModelCache } from "../claude/gateway-cache";
|
|
16
17
|
import { commandInvocation } from "../lib/win-exec";
|
|
17
18
|
import { isProxyAdmissionSecret } from "../server/auth-cors";
|
|
18
19
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
@@ -50,6 +51,8 @@ export interface ClaudeRoutingTarget {
|
|
|
50
51
|
* launch base so detection and the spawned process can never disagree (audit R3-3).
|
|
51
52
|
*/
|
|
52
53
|
export type ClaudeEnvDeps = {
|
|
54
|
+
forceAvailable?: readonly string[];
|
|
55
|
+
forceAvailableSelectors?: readonly string[];
|
|
53
56
|
authDetect?: Omit<Partial<AuthDetectDeps>, "env" | "ownTokens">;
|
|
54
57
|
/** Test seam; production uses the authenticated Node-launcher context. */
|
|
55
58
|
preBunAnthropicSlots?: readonly AnthropicParentEnvSlot[] | null;
|
|
@@ -213,6 +216,15 @@ export function buildClaudeEnv(
|
|
|
213
216
|
if (env[name] !== undefined && env[name] !== "") return; // user wins
|
|
214
217
|
env[name] = value;
|
|
215
218
|
};
|
|
219
|
+
if (config.claudeCode?.subagentModelForce !== undefined) {
|
|
220
|
+
const forced = resolveSubagentForceModel(config, contextWindows, deps.forceAvailableSelectors !== undefined ? { selectors: deps.forceAvailableSelectors } : { entries: deps.forceAvailable ?? [] });
|
|
221
|
+
if (forced) {
|
|
222
|
+
setDefault("CLAUDE_CODE_SUBAGENT_MODEL", forced);
|
|
223
|
+
setDefault("CLAUDE_CODE_SUBAGENT_MODEL_FORCE", "1");
|
|
224
|
+
} else {
|
|
225
|
+
(deps.warn ?? console.warn)("⚠ Claude subagent force target is invalid or unavailable; launch override skipped.");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
216
228
|
if (deps.allowRootSkipPermissions === true) {
|
|
217
229
|
setDefault("IS_SANDBOX", "1");
|
|
218
230
|
}
|
|
@@ -420,6 +432,7 @@ export function buildClaudeEnv(
|
|
|
420
432
|
* decides to launch natively, so a wrong destination here silently downgrades every launch.
|
|
421
433
|
*/
|
|
422
434
|
export interface ClaudeCodeLiveState {
|
|
435
|
+
forceAvailable?: string[];
|
|
423
436
|
contextWindows: Record<string, number>;
|
|
424
437
|
enabled?: boolean;
|
|
425
438
|
}
|
|
@@ -434,9 +447,10 @@ export async function fetchClaudeCodeState(config: OcxConfig, port: number, time
|
|
|
434
447
|
signal: AbortSignal.timeout(timeoutMs),
|
|
435
448
|
});
|
|
436
449
|
if (!res.ok) return { contextWindows: {} };
|
|
437
|
-
const body = await res.json() as { contextWindows?: Record<string, number>; enabled?: boolean };
|
|
450
|
+
const body = await res.json() as { contextWindows?: Record<string, number>; enabled?: boolean; forceAvailable?: string[] };
|
|
438
451
|
return {
|
|
439
452
|
contextWindows: body.contextWindows && typeof body.contextWindows === "object" ? body.contextWindows : {},
|
|
453
|
+
...(Array.isArray(body.forceAvailable) ? { forceAvailable: body.forceAvailable.filter(value => typeof value === "string") } : {}),
|
|
440
454
|
...(typeof body.enabled === "boolean" ? { enabled: body.enabled } : {}),
|
|
441
455
|
};
|
|
442
456
|
} catch {
|
|
@@ -774,6 +788,7 @@ export async function cmdClaude(args: string[]): Promise<number> {
|
|
|
774
788
|
if (preflight.kind === "native") return launchNativeClaude(config, args, preflight.notice);
|
|
775
789
|
let route: number | ClaudeRoutingTarget;
|
|
776
790
|
let contextWindows: Record<string, number>;
|
|
791
|
+
let forceAvailable: readonly string[] | undefined;
|
|
777
792
|
/** The hub's featured roster on a connected client; undefined means "use local config". */
|
|
778
793
|
let hubRoster: readonly string[] | undefined;
|
|
779
794
|
if (clientState.kind === "connected") {
|
|
@@ -792,18 +807,19 @@ export async function cmdClaude(args: string[]): Promise<number> {
|
|
|
792
807
|
if (plan.kind === "native") return launchNativeClaude(config, args, plan.notice);
|
|
793
808
|
route = port;
|
|
794
809
|
contextWindows = liveState.contextWindows;
|
|
810
|
+
forceAvailable = liveState.forceAvailable ?? [];
|
|
795
811
|
}
|
|
812
|
+
const gateway = await fetchGatewayModels(route, { admissionConfig: config });
|
|
813
|
+
const forceAvailableSelectors = typeof route === "number" ? undefined : gateway?.models.map(model => model.id) ?? [];
|
|
796
814
|
const allowRootSkipPermissions = shouldAllowRootSkipPermissions(args);
|
|
797
|
-
const env = buildClaudeEnv(config, route, process.env, contextWindows, { allowRootSkipPermissions });
|
|
815
|
+
const env = buildClaudeEnv(config, route, process.env, contextWindows, { allowRootSkipPermissions, forceAvailable, forceAvailableSelectors });
|
|
798
816
|
if (allowRootSkipPermissions) {
|
|
799
817
|
console.error(rootSkipPermissionsNotice(env));
|
|
800
818
|
}
|
|
801
819
|
// Pre-write the CLI's gateway-model cache (devlog 030): without a token the CLI
|
|
802
820
|
// never refreshes it, so the picker would keep showing yesterday's aliases.
|
|
803
821
|
try {
|
|
804
|
-
const cachePath =
|
|
805
|
-
? await refreshGatewayModelCacheFromProxy(route, { admissionConfig: config })
|
|
806
|
-
: await refreshGatewayModelCacheFromProxy(route, { admissionConfig: config });
|
|
822
|
+
const cachePath = gateway ? writeGatewayModelCache(gateway.baseUrl, gateway.models) : null;
|
|
807
823
|
if (cachePath === null) {
|
|
808
824
|
console.error("⚠ Gateway model cache could not be refreshed; the model picker may be stale.");
|
|
809
825
|
}
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
getCodexRoutingKind,
|
|
4
4
|
type CodexRoutingKind,
|
|
5
5
|
} from "../codex/inject";
|
|
6
|
+
import { diagnoseCodexShim, type CodexShimDiagnostic } from "../codex/shim";
|
|
7
|
+
import { findFirstCodexOnPath, type CodexPathCandidate } from "../codex/shim-path-resolution";
|
|
6
8
|
import { loadConfig, resolveEnvValue } from "../config";
|
|
7
9
|
|
|
8
10
|
const PROXY_ENV_KEYS = [
|
|
@@ -21,6 +23,93 @@ export interface CodexShimReadinessInputs {
|
|
|
21
23
|
configuredProxyResolved: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
export type CodexConnectShimStatus = "ready" | "missing" | "unhealthy" | "unverified";
|
|
27
|
+
|
|
28
|
+
export interface CodexConnectShimReadiness {
|
|
29
|
+
status: CodexConnectShimStatus;
|
|
30
|
+
/** Secret-free, actionable text rendered by ocx connect. */
|
|
31
|
+
message: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CodexConnectShimInspectionDeps {
|
|
35
|
+
diagnose?: () => Pick<CodexShimDiagnostic, "installed" | "healthy" | "summary">;
|
|
36
|
+
findOnPath?: () => CodexPathCandidate | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CODEX_TOKEN_NOTE = "The connected Codex config uses OPENCODEX_API_AUTH_TOKEN; "
|
|
40
|
+
+ "without a working shim or when a PATH wrapper replaces it, Codex may fail with "
|
|
41
|
+
+ '"Missing environment variable".';
|
|
42
|
+
const CODEX_TOKEN_ACTION = `${CODEX_TOKEN_NOTE} Run 'ocx codex-shim install' to repair it.`;
|
|
43
|
+
|
|
44
|
+
export function codexConnectShimReadiness(inputs: {
|
|
45
|
+
diagnosis: Pick<CodexShimDiagnostic, "installed" | "healthy" | "summary">;
|
|
46
|
+
commandPath: string | null;
|
|
47
|
+
commandIsShim?: boolean;
|
|
48
|
+
}): CodexConnectShimReadiness {
|
|
49
|
+
if (inputs.diagnosis.installed && !inputs.diagnosis.healthy) {
|
|
50
|
+
return {
|
|
51
|
+
status: "unhealthy",
|
|
52
|
+
message: `installed but unhealthy: ${inputs.diagnosis.summary}. ${CODEX_TOKEN_ACTION}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (inputs.diagnosis.healthy && inputs.commandPath && !inputs.commandIsShim) {
|
|
56
|
+
// The shim is installed and healthy, so reinstalling it cannot fix this: only PATH
|
|
57
|
+
// order (or the wrapper in front of it) can.
|
|
58
|
+
return {
|
|
59
|
+
status: "missing",
|
|
60
|
+
message: `not active; PATH resolves 'codex' to ${inputs.commandPath}, not an OpenCodex shim. `
|
|
61
|
+
+ `Shim state: ${inputs.diagnosis.summary}. Put the directory of the tracked shim ahead of that command on PATH, `
|
|
62
|
+
+ `or remove that wrapper; re-running 'ocx codex-shim install' will not change PATH order. ${CODEX_TOKEN_NOTE}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (inputs.diagnosis.installed && inputs.diagnosis.healthy) {
|
|
66
|
+
if (!inputs.commandPath) {
|
|
67
|
+
return {
|
|
68
|
+
status: "missing",
|
|
69
|
+
message: `installed but not active; no 'codex' executable was found on PATH. `
|
|
70
|
+
+ `Shim state: ${inputs.diagnosis.summary}. Add the directory of the tracked shim to PATH. ${CODEX_TOKEN_NOTE}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { status: "ready", message: "installed and healthy" };
|
|
74
|
+
}
|
|
75
|
+
if (inputs.commandPath) {
|
|
76
|
+
return {
|
|
77
|
+
status: "missing",
|
|
78
|
+
message: `not active; PATH resolves 'codex' to ${inputs.commandPath}, not an OpenCodex shim. ${CODEX_TOKEN_ACTION}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
status: "missing",
|
|
83
|
+
message: `not installed and no 'codex' executable was found on PATH. ${CODEX_TOKEN_ACTION}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function inspectCodexShimForConnect(
|
|
88
|
+
deps: CodexConnectShimInspectionDeps = {},
|
|
89
|
+
): CodexConnectShimReadiness {
|
|
90
|
+
let diagnosis: Pick<CodexShimDiagnostic, "installed" | "healthy" | "summary">;
|
|
91
|
+
try {
|
|
92
|
+
diagnosis = (deps.diagnose ?? diagnoseCodexShim)();
|
|
93
|
+
} catch {
|
|
94
|
+
diagnosis = { installed: true, healthy: false, summary: "diagnostic state could not be read" };
|
|
95
|
+
}
|
|
96
|
+
let command: CodexPathCandidate | null = null;
|
|
97
|
+
try {
|
|
98
|
+
command = (deps.findOnPath ?? findFirstCodexOnPath)();
|
|
99
|
+
} catch {
|
|
100
|
+
return {
|
|
101
|
+
status: "unverified",
|
|
102
|
+
message: `PATH activation could not be verified. Shim state: ${diagnosis.summary}. `
|
|
103
|
+
+ `Check PATH and retry 'ocx connect'. ${CODEX_TOKEN_NOTE}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return codexConnectShimReadiness({
|
|
107
|
+
diagnosis,
|
|
108
|
+
commandPath: command?.path ?? null,
|
|
109
|
+
commandIsShim: command?.isShim ?? false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
24
113
|
function externalProviderLabel(provider: string | null): string {
|
|
25
114
|
return provider
|
|
26
115
|
? `external model_provider ${JSON.stringify(provider)}`
|
package/src/cli/connect.ts
CHANGED
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
terminalSafeText,
|
|
41
41
|
type RuntimeApiDeps,
|
|
42
42
|
} from "./runtime-api";
|
|
43
|
+
import {
|
|
44
|
+
inspectCodexShimForConnect,
|
|
45
|
+
type CodexConnectShimReadiness,
|
|
46
|
+
} from "./codex-shim-readiness";
|
|
43
47
|
|
|
44
48
|
export interface ClientCommandDeps extends RuntimeApiDeps {
|
|
45
49
|
lifecycleLockDeps?: ClientLifecycleLockDeps;
|
|
@@ -277,6 +281,19 @@ export type ConnectCompletionReport = {
|
|
|
277
281
|
readonly failure: string | null;
|
|
278
282
|
};
|
|
279
283
|
|
|
284
|
+
export function codexShimReadinessLine(
|
|
285
|
+
selectedClients: readonly OcxConnectedClientId[],
|
|
286
|
+
inspect: () => CodexConnectShimReadiness = inspectCodexShimForConnect,
|
|
287
|
+
): string | null {
|
|
288
|
+
if (!selectedClients.includes("codex")) return null;
|
|
289
|
+
return `Codex autostart shim: ${terminalSafeText(inspect().message)}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function printCodexShimReadiness(selectedClients: readonly OcxConnectedClientId[]): void {
|
|
293
|
+
const line = codexShimReadinessLine(selectedClients);
|
|
294
|
+
if (line) console.log(line);
|
|
295
|
+
}
|
|
296
|
+
|
|
280
297
|
/**
|
|
281
298
|
* What `ocx connect` says once the hub and the credential are settled, and whether the command
|
|
282
299
|
* still fails (#4207).
|
|
@@ -414,6 +431,7 @@ async function runHubConnect(argv: string[], deps: ClientCommandDeps): Promise<v
|
|
|
414
431
|
const readiness = inspectInstalledCatalogReadiness(installedCatalogFileState(), deps.catalogProbeDeps ?? {});
|
|
415
432
|
const report = connectCompletionReport(connection, clients, readiness);
|
|
416
433
|
for (const line of report.lines) console.log(line);
|
|
434
|
+
printCodexShimReadiness(clients);
|
|
417
435
|
if (report.failure) throw new Error(report.failure);
|
|
418
436
|
}
|
|
419
437
|
|
|
@@ -463,6 +481,7 @@ async function runLinkConnect(argv: string[], deps: ClientCommandDeps): Promise<
|
|
|
463
481
|
const readiness = inspectInstalledCatalogReadiness(installedCatalogFileState(), deps.catalogProbeDeps ?? {});
|
|
464
482
|
const report = connectCompletionReport(connection, clients, readiness);
|
|
465
483
|
for (const line of report.lines) console.log(line);
|
|
484
|
+
printCodexShimReadiness(clients);
|
|
466
485
|
if (report.failure) throw new Error(report.failure);
|
|
467
486
|
}
|
|
468
487
|
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { LivenessIo, LiveProxy } from "../server/proxy-liveness";
|
|
|
17
17
|
import type { OcxConfig } from "../types";
|
|
18
18
|
import type { OwnedIntegrationRefreshOutcome } from "../integrations/owned-refresh";
|
|
19
19
|
import { hasHelpFlag, printSubcommandUsage, printUsage } from "./help";
|
|
20
|
+
import { printUnknownCommand } from "./help-recovery";
|
|
20
21
|
import {
|
|
21
22
|
HUB_GATED_SKIP_MESSAGE,
|
|
22
23
|
localClientSkipMessage,
|
|
@@ -514,7 +515,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
514
515
|
},
|
|
515
516
|
config,
|
|
516
517
|
port: live.port,
|
|
517
|
-
}, ["mcode", "pi", "raycast", "omo", "cline", "droid"]));
|
|
518
|
+
}, ["mcode", "pi", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
|
|
518
519
|
} catch (error) {
|
|
519
520
|
console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
520
521
|
}
|
|
@@ -1167,8 +1168,7 @@ export async function dispatchCommand(head: CliHead, deps: CliDispatchDeps): Pro
|
|
|
1167
1168
|
}
|
|
1168
1169
|
const runner = commandRunners[resolveDispatchCommand(command) ?? ""];
|
|
1169
1170
|
if (!runner) {
|
|
1170
|
-
|
|
1171
|
-
printUsage();
|
|
1171
|
+
printUnknownCommand(command);
|
|
1172
1172
|
return 1;
|
|
1173
1173
|
}
|
|
1174
1174
|
return await runner(deps);
|
|
@@ -66,12 +66,13 @@ export interface ExportCommandDeps extends RuntimeApiDeps {
|
|
|
66
66
|
* `opencodeCatalogFromProxyRows` owns the visibility rules (drop `disabled`, drop dupes,
|
|
67
67
|
* drop native under Codex Direct) — the export core does none of that, so a row filtered
|
|
68
68
|
* here is the only thing keeping a disabled model out of a client's picker. It also carries
|
|
69
|
-
* the effort ladder
|
|
70
|
-
*
|
|
71
|
-
* duplicate donate its ladder to
|
|
69
|
+
* the effort ladder and the effective custom-row projection, so every per-model field a
|
|
70
|
+
* client receives comes from the same filtered, deduped row as the model itself: a second
|
|
71
|
+
* lookup over the raw rows would let a hidden or disabled duplicate donate its ladder to
|
|
72
|
+
* the visible entry.
|
|
72
73
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
74
|
+
* The empty-vs-undefined ladder distinction survives the hop: a row declaring `[]` exports
|
|
75
|
+
* `[]` (explicit "no rungs"), a row declaring nothing exports nothing (unknown).
|
|
75
76
|
*/
|
|
76
77
|
export function exportModelsFromProxyRows(
|
|
77
78
|
rows: readonly OpencodeProxyModelRow[],
|
|
@@ -87,13 +88,21 @@ export function exportModelsFromProxyRows(
|
|
|
87
88
|
if (entry.fastRowAvailable !== undefined) model.fastRowAvailable = entry.fastRowAvailable;
|
|
88
89
|
if (entry.displayName) model.displayName = entry.displayName;
|
|
89
90
|
if (entry.contextWindow !== undefined) model.contextWindow = entry.contextWindow;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// The catalog row's own output limit; dropping it here is what once made every CLI
|
|
92
|
+
// export fall back to the generated-metadata guess the management path never used.
|
|
93
|
+
if (entry.maxTokens !== undefined) model.maxTokens = entry.maxTokens;
|
|
94
|
+
if (entry.maxInputTokens !== undefined) model.maxInputTokens = entry.maxInputTokens;
|
|
95
|
+
if (entry.reasoningEfforts !== undefined) model.reasoningEfforts = [...entry.reasoningEfforts];
|
|
93
96
|
if (entry.defaultReasoningEffort) model.defaultReasoningEffort = entry.defaultReasoningEffort;
|
|
94
97
|
if (entry.inputModalities && entry.inputModalities.length > 0) {
|
|
95
98
|
model.inputModalities = [...entry.inputModalities];
|
|
96
99
|
}
|
|
100
|
+
// Explicit booleans survive, false included; nothing infers a negative from absence.
|
|
101
|
+
if (entry.supportsTools !== undefined) model.supportsTools = entry.supportsTools;
|
|
102
|
+
if (entry.supportsReasoning !== undefined) model.supportsReasoning = entry.supportsReasoning;
|
|
103
|
+
if (entry.supportsReasoningSummaries !== undefined) {
|
|
104
|
+
model.supportsReasoningSummaries = entry.supportsReasoningSummaries;
|
|
105
|
+
}
|
|
97
106
|
return model;
|
|
98
107
|
});
|
|
99
108
|
}
|
|
@@ -194,7 +203,9 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
|
|
|
194
203
|
// `--out` is the path that writes the selected client's native format.
|
|
195
204
|
// Format metadata rides in the human lines below.
|
|
196
205
|
printData(clientConfig, wantsJson, [
|
|
197
|
-
|
|
206
|
+
// `lines` entries print one console line each and are control-escaped, so
|
|
207
|
+
// the document goes in as individual lines rather than one multi-line blob.
|
|
208
|
+
...text.trimEnd().split("\n"),
|
|
198
209
|
"",
|
|
199
210
|
...(out !== undefined ? [`Wrote ${out}`] : []),
|
|
200
211
|
`Destination: ${spec.destination(process.env)}`,
|