@bitkyc08/opencodex 2.7.41 → 2.7.42
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 +4 -0
- package/gui/dist/assets/index-Bl_VBGoI.js +65 -0
- package/gui/dist/assets/index-DfVGuN88.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/base.ts +6 -0
- package/src/adapters/kiro-constants.ts +6 -2
- package/src/adapters/kiro-retry.ts +175 -10
- package/src/adapters/kiro.ts +172 -85
- package/src/adapters/mimo-free.ts +1 -0
- package/src/adapters/openai-chat.ts +30 -4
- package/src/adapters/openai-responses.ts +90 -12
- package/src/bridge.ts +91 -43
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +29 -2
- package/src/cli/access.ts +108 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account.ts +9 -1
- package/src/cli/agent.ts +184 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +20 -8
- package/src/cli/doctor.ts +45 -8
- package/src/cli/help.ts +65 -13
- package/src/cli/index.ts +108 -7
- package/src/cli/integrations.ts +142 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +9 -10
- package/src/cli/observe.ts +117 -0
- package/src/cli/provider-runtime.ts +152 -0
- package/src/cli/provider.ts +23 -1
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +3 -3
- package/src/cli/status.ts +17 -0
- package/src/cli/system-command.ts +112 -0
- package/src/codex/auth-api.ts +3 -2
- package/src/codex/catalog/aggregation.ts +113 -18
- package/src/codex/catalog/provider-fetch.ts +24 -13
- package/src/codex/catalog/sync.ts +20 -8
- package/src/codex/catalog.ts +2 -1
- package/src/codex/refresh.ts +10 -3
- package/src/codex/routing.ts +21 -32
- package/src/codex/sync.ts +17 -0
- package/src/config.ts +48 -0
- package/src/generated/jawcode-model-metadata.ts +2 -1
- package/src/grok/inject.ts +184 -4
- package/src/grok/status.ts +33 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/windows-elevation.ts +627 -0
- package/src/providers/openai-sidecar.ts +46 -2
- package/src/providers/registry.ts +52 -0
- package/src/server/auth-cors.ts +6 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/claude-messages.ts +20 -1
- package/src/server/images.ts +14 -7
- package/src/server/management/agent-settings-routes.ts +10 -4
- package/src/server/management/combo-routes.ts +0 -1
- package/src/server/management/config-routes.ts +0 -1
- package/src/server/management/logs-usage-routes.ts +94 -0
- package/src/server/management/model-routes.ts +0 -1
- package/src/server/management/oauth-account-routes.ts +0 -1
- package/src/server/management/provider-routes.ts +0 -1
- package/src/server/management/shared.ts +0 -1
- package/src/server/management/system-routes.ts +27 -15
- package/src/server/management-api.ts +0 -1
- package/src/server/memory-watchdog.ts +54 -10
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +122 -2
- package/src/server/responses/core.ts +76 -13
- package/src/server/responses/passthrough-error.ts +38 -13
- package/src/server/startup-action-control.ts +266 -15
- package/src/service.ts +512 -3
- package/src/storage/cleanup.ts +1538 -0
- package/src/storage/scanner.ts +4 -1
- package/src/types.ts +16 -0
- package/src/update/job.ts +229 -25
- package/src/usage/log.ts +39 -0
- package/src/web-search/loop.ts +8 -1
- package/gui/dist/assets/index-B2J4t3te.css +0 -1
- package/gui/dist/assets/index-BmvM6wRb.js +0 -65
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
csv,
|
|
4
|
+
printData,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
summaryLines,
|
|
9
|
+
takeBooleanOption,
|
|
10
|
+
takeFlag,
|
|
11
|
+
takeOption,
|
|
12
|
+
type RuntimeApiDeps,
|
|
13
|
+
} from "./runtime-api";
|
|
14
|
+
|
|
15
|
+
const CLAUDE_USAGE = `Usage:
|
|
16
|
+
ocx claude config [status] [--json]
|
|
17
|
+
ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>]
|
|
18
|
+
[--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>]
|
|
19
|
+
[--compact-window <tokens|default>] [--inject-agents <on|off>]
|
|
20
|
+
[--small-fast-model <id|->] [--model-map <from=to,from=to|->]
|
|
21
|
+
[--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|->]
|
|
22
|
+
[--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]`;
|
|
23
|
+
|
|
24
|
+
const GROK_USAGE = `Usage:
|
|
25
|
+
ocx grok [status] [--json]
|
|
26
|
+
ocx grok <exclude|include|set> <model,model...> [--json]
|
|
27
|
+
ocx grok clear [--json]
|
|
28
|
+
ocx grok apply [--json]`;
|
|
29
|
+
|
|
30
|
+
function parseMap(raw: string): Record<string, string> {
|
|
31
|
+
if (raw === "-") return {};
|
|
32
|
+
const map: Record<string, string> = {};
|
|
33
|
+
for (const pair of raw.split(",")) {
|
|
34
|
+
const index = pair.indexOf("=");
|
|
35
|
+
if (index <= 0 || index === pair.length - 1) throw new CliUsageError(`invalid model map entry "${pair}"; use from=to`, CLAUDE_USAGE);
|
|
36
|
+
map[pair.slice(0, index).trim()] = pair.slice(index + 1).trim();
|
|
37
|
+
}
|
|
38
|
+
return map;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function handleClaudeConfigCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
42
|
+
return runCliAction(async () => {
|
|
43
|
+
const args = [...argv];
|
|
44
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
45
|
+
const wantsJson = takeFlag(args, "--json");
|
|
46
|
+
if (action === "status" || action === "show") {
|
|
47
|
+
rejectArgs(args, CLAUDE_USAGE);
|
|
48
|
+
const result = await runtimeRequest("/api/claude-code", {}, deps);
|
|
49
|
+
printData(result, wantsJson, summaryLines(result));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (action !== "set") throw new CliUsageError(`unknown Claude config command ${action}`, CLAUDE_USAGE);
|
|
53
|
+
const body: Record<string, unknown> = {};
|
|
54
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
55
|
+
const authMode = takeOption(args, "--auth-mode");
|
|
56
|
+
const systemEnv = takeBooleanOption(args, "--system-env");
|
|
57
|
+
const fastMode = takeBooleanOption(args, "--fast-mode");
|
|
58
|
+
const autoContext = takeBooleanOption(args, "--auto-context");
|
|
59
|
+
const compact = takeOption(args, "--compact-window");
|
|
60
|
+
const injectAgents = takeBooleanOption(args, "--inject-agents");
|
|
61
|
+
const smallFastModel = takeOption(args, "--small-fast-model");
|
|
62
|
+
const modelMap = takeOption(args, "--model-map");
|
|
63
|
+
const blockedSkills = takeOption(args, "--blocked-skills");
|
|
64
|
+
const webModel = takeOption(args, "--web-model");
|
|
65
|
+
const webBackend = takeOption(args, "--web-backend");
|
|
66
|
+
const visionModel = takeOption(args, "--vision-model");
|
|
67
|
+
const visionBackend = takeOption(args, "--vision-backend");
|
|
68
|
+
rejectArgs(args, CLAUDE_USAGE);
|
|
69
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
70
|
+
if (authMode !== undefined) body.authMode = authMode;
|
|
71
|
+
if (systemEnv !== undefined) body.systemEnv = systemEnv;
|
|
72
|
+
if (fastMode !== undefined) body.fastMode = fastMode;
|
|
73
|
+
if (autoContext !== undefined) body.autoContext = autoContext;
|
|
74
|
+
if (compact !== undefined) {
|
|
75
|
+
if (compact === "default" || compact === "-") body.autoCompactWindow = null;
|
|
76
|
+
else {
|
|
77
|
+
const value = Number(compact.replace(/[_,]/g, ""));
|
|
78
|
+
if (!Number.isInteger(value) || value <= 0) throw new CliUsageError("--compact-window must be a positive integer or default", CLAUDE_USAGE);
|
|
79
|
+
body.autoCompactWindow = value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (injectAgents !== undefined) body.injectAgents = injectAgents;
|
|
83
|
+
if (smallFastModel !== undefined) body.smallFastModel = smallFastModel === "-" ? "" : smallFastModel;
|
|
84
|
+
if (modelMap !== undefined) body.modelMap = parseMap(modelMap);
|
|
85
|
+
if (blockedSkills !== undefined) body.blockedSkills = blockedSkills === "-" ? null : csv(blockedSkills);
|
|
86
|
+
const sidecar = (model: string | undefined, backend: string | undefined): Record<string, unknown> | undefined => {
|
|
87
|
+
if (model === undefined && backend === undefined) return undefined;
|
|
88
|
+
const result: Record<string, unknown> = {};
|
|
89
|
+
if (model !== undefined) result.model = model === "-" ? "" : model;
|
|
90
|
+
if (backend !== undefined) result.backend = backend === "-" ? null : backend;
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
const web = sidecar(webModel, webBackend);
|
|
94
|
+
const vision = sidecar(visionModel, visionBackend);
|
|
95
|
+
if (web) body.webSearchSidecar = web;
|
|
96
|
+
if (vision) body.visionSidecar = vision;
|
|
97
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("at least one Claude setting is required", CLAUDE_USAGE);
|
|
98
|
+
const result = await runtimeRequest("/api/claude-code", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
99
|
+
printData(result, wantsJson, ["Claude Code settings updated."]);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type GrokState = Record<string, unknown> & { excluded?: string[] };
|
|
104
|
+
|
|
105
|
+
export async function handleGrokCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
106
|
+
return runCliAction(async () => {
|
|
107
|
+
const args = [...argv];
|
|
108
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
109
|
+
const wantsJson = takeFlag(args, "--json");
|
|
110
|
+
if (action === "status" || action === "show") {
|
|
111
|
+
rejectArgs(args, GROK_USAGE);
|
|
112
|
+
const result = await runtimeRequest("/api/grok", {}, deps);
|
|
113
|
+
printData(result, wantsJson, summaryLines(result));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (action === "apply") {
|
|
117
|
+
rejectArgs(args, GROK_USAGE);
|
|
118
|
+
const result = await runtimeRequest("/api/grok/apply", { method: "POST" }, deps);
|
|
119
|
+
printData(result, wantsJson, [String((result as Record<string, unknown>).message ?? "Grok configuration applied.")]);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let excluded: string[];
|
|
123
|
+
if (action === "clear") excluded = [];
|
|
124
|
+
else if (["exclude", "include", "set"].includes(action)) {
|
|
125
|
+
const raw = args.shift();
|
|
126
|
+
if (!raw) throw new CliUsageError("comma-separated models are required", GROK_USAGE);
|
|
127
|
+
const requested = csv(raw) ?? [];
|
|
128
|
+
if (action === "set") excluded = requested;
|
|
129
|
+
else {
|
|
130
|
+
const state = await runtimeRequest<GrokState>("/api/grok", {}, deps);
|
|
131
|
+
const current = new Set(state.excluded ?? []);
|
|
132
|
+
for (const model of requested) action === "exclude" ? current.add(model) : current.delete(model);
|
|
133
|
+
excluded = [...current].sort();
|
|
134
|
+
}
|
|
135
|
+
} else throw new CliUsageError(`unknown Grok command ${action}`, GROK_USAGE);
|
|
136
|
+
rejectArgs(args, GROK_USAGE);
|
|
137
|
+
const result = await runtimeRequest("/api/grok/selection", { method: "PUT", body: JSON.stringify({ excluded }) }, deps);
|
|
138
|
+
printData(result, wantsJson, [`Grok exclusions: ${excluded.join(", ") || "none"}`]);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const INTEGRATION_USAGE = { claude: CLAUDE_USAGE, grok: GROK_USAGE };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
csv,
|
|
4
|
+
printData,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
summaryLines,
|
|
9
|
+
takeBooleanOption,
|
|
10
|
+
takeFlag,
|
|
11
|
+
takeIntegerOption,
|
|
12
|
+
takeOption,
|
|
13
|
+
type RuntimeApiDeps,
|
|
14
|
+
} from "./runtime-api";
|
|
15
|
+
|
|
16
|
+
const USAGE = `Usage:
|
|
17
|
+
ocx models live [--provider <name>] [--json]
|
|
18
|
+
ocx models edit <custom-id> [--model-id <id>] [--display-name <name|->]
|
|
19
|
+
[--context-window <tokens|0>] [--modalities <text,image,audio|->] [--json]
|
|
20
|
+
ocx models <enable|disable> <provider/model|native-model> [--native] [--json]
|
|
21
|
+
ocx models provider <name> <on|off> [--json]
|
|
22
|
+
ocx models selected <provider> [--set <id,id...>|--clear] [--json]
|
|
23
|
+
ocx models context <status|value <tokens>|provider <name> <on|off>|all <on|off>> [--json]
|
|
24
|
+
ocx models shadow <status|set> [model|-] [--enabled <on|off>] [--json]`;
|
|
25
|
+
|
|
26
|
+
type ModelRow = {
|
|
27
|
+
provider?: string;
|
|
28
|
+
id?: string;
|
|
29
|
+
namespaced?: string;
|
|
30
|
+
native?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
custom?: boolean;
|
|
33
|
+
customId?: string;
|
|
34
|
+
displayName?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function live(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
38
|
+
const args = [...argv];
|
|
39
|
+
const wantsJson = takeFlag(args, "--json");
|
|
40
|
+
const provider = takeOption(args, "--provider");
|
|
41
|
+
rejectArgs(args, USAGE);
|
|
42
|
+
const rows = await runtimeRequest<ModelRow[]>("/api/models", {}, deps);
|
|
43
|
+
const filtered = provider ? rows.filter(row => row.provider === provider) : rows;
|
|
44
|
+
printData(filtered, wantsJson, filtered.map(row => {
|
|
45
|
+
const flags = [row.native ? "native" : "routed", row.custom ? "custom" : "", row.disabled ? "disabled" : "enabled"].filter(Boolean);
|
|
46
|
+
return `${row.namespaced ?? `${row.provider}/${row.id}`} [${flags.join(", ")}]`;
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
51
|
+
const args = [...argv];
|
|
52
|
+
const id = args.shift()?.trim();
|
|
53
|
+
const wantsJson = takeFlag(args, "--json");
|
|
54
|
+
if (!id) throw new CliUsageError("custom model id is required", USAGE);
|
|
55
|
+
const patch: Record<string, unknown> = {};
|
|
56
|
+
const modelId = takeOption(args, "--model-id");
|
|
57
|
+
const displayName = takeOption(args, "--display-name");
|
|
58
|
+
const contextRaw = takeOption(args, "--context-window");
|
|
59
|
+
const modalitiesRaw = takeOption(args, "--modalities");
|
|
60
|
+
rejectArgs(args, USAGE);
|
|
61
|
+
if (modelId !== undefined) patch.modelId = modelId;
|
|
62
|
+
if (displayName !== undefined) patch.displayName = displayName === "-" ? "" : displayName;
|
|
63
|
+
if (contextRaw !== undefined) {
|
|
64
|
+
const value = Number(contextRaw.replace(/[_,]/g, ""));
|
|
65
|
+
if (!Number.isInteger(value) || value < 0) throw new CliUsageError("--context-window must be an integer >= 0", USAGE);
|
|
66
|
+
patch.contextWindow = value === 0 ? null : value;
|
|
67
|
+
}
|
|
68
|
+
if (modalitiesRaw !== undefined) patch.inputModalities = modalitiesRaw === "-" ? [] : csv(modalitiesRaw);
|
|
69
|
+
if (Object.keys(patch).length === 0) throw new CliUsageError("at least one edit option is required", USAGE);
|
|
70
|
+
const result = await runtimeRequest(`/api/custom-models/${encodeURIComponent(id)}`, {
|
|
71
|
+
method: "PUT",
|
|
72
|
+
body: JSON.stringify(patch),
|
|
73
|
+
}, deps);
|
|
74
|
+
printData(result, wantsJson, [`Updated custom model ${id}.`]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseSelector(selector: string, forceNative: boolean): { provider: string; id: string; native: boolean } {
|
|
78
|
+
if (forceNative || !selector.includes("/")) return { provider: "openai", id: selector, native: true };
|
|
79
|
+
const slash = selector.indexOf("/");
|
|
80
|
+
const provider = selector.slice(0, slash);
|
|
81
|
+
const id = selector.slice(slash + 1);
|
|
82
|
+
if (!provider || !id) throw new CliUsageError("model selector must be provider/model or a native model id", USAGE);
|
|
83
|
+
return { provider, id, native: false };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function visibility(enabled: boolean, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
87
|
+
const args = [...argv];
|
|
88
|
+
const selector = args.shift()?.trim();
|
|
89
|
+
const wantsJson = takeFlag(args, "--json");
|
|
90
|
+
const native = takeFlag(args, "--native");
|
|
91
|
+
if (!selector) throw new CliUsageError("model selector is required", USAGE);
|
|
92
|
+
rejectArgs(args, USAGE);
|
|
93
|
+
const target = parseSelector(selector, native);
|
|
94
|
+
const result = await runtimeRequest("/api/model-visibility", {
|
|
95
|
+
method: "PUT",
|
|
96
|
+
body: JSON.stringify({ scope: "models", provider: target.provider, enabled, targets: [{ id: target.id, native: target.native }] }),
|
|
97
|
+
}, deps);
|
|
98
|
+
printData(result, wantsJson, [`${enabled ? "Enabled" : "Disabled"} ${selector}.`]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function providerVisibility(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
102
|
+
const args = [...argv];
|
|
103
|
+
const provider = args.shift()?.trim();
|
|
104
|
+
const state = args.shift()?.toLowerCase();
|
|
105
|
+
const wantsJson = takeFlag(args, "--json");
|
|
106
|
+
if (!provider || (state !== "on" && state !== "off")) throw new CliUsageError("provider and on|off are required", USAGE);
|
|
107
|
+
rejectArgs(args, USAGE);
|
|
108
|
+
const rows = await runtimeRequest<ModelRow[]>("/api/models", {}, deps);
|
|
109
|
+
const targets = rows.filter(row => row.provider === provider && typeof row.id === "string")
|
|
110
|
+
.map(row => ({ id: row.id!, native: row.native === true }));
|
|
111
|
+
if (targets.length === 0) throw new CliUsageError(`no models are available for provider ${provider}`);
|
|
112
|
+
const result = await runtimeRequest("/api/model-visibility", {
|
|
113
|
+
method: "PUT",
|
|
114
|
+
body: JSON.stringify({ scope: "provider", provider, enabled: state === "on", targets }),
|
|
115
|
+
}, deps);
|
|
116
|
+
printData(result, wantsJson, [`${provider}: ${state}`]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function selected(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
120
|
+
const args = [...argv];
|
|
121
|
+
const provider = args.shift()?.trim();
|
|
122
|
+
const wantsJson = takeFlag(args, "--json");
|
|
123
|
+
const selectedModels = csv(takeOption(args, "--set"));
|
|
124
|
+
const clear = takeFlag(args, "--clear");
|
|
125
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
126
|
+
if (selectedModels !== undefined && clear) throw new CliUsageError("--set and --clear cannot be combined", USAGE);
|
|
127
|
+
rejectArgs(args, USAGE);
|
|
128
|
+
if (selectedModels === undefined && !clear) {
|
|
129
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/selected-models", {}, deps);
|
|
130
|
+
const map = result.selected as Record<string, string[]> | undefined;
|
|
131
|
+
const available = result.available as Record<string, string[]> | undefined;
|
|
132
|
+
const view = { provider, selected: map?.[provider] ?? [], available: available?.[provider] ?? [] };
|
|
133
|
+
printData(view, wantsJson, [`${provider}: ${view.selected.join(", ") || "all models"}`]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const models = clear ? [] : selectedModels!;
|
|
137
|
+
const result = await runtimeRequest("/api/selected-models", {
|
|
138
|
+
method: "PUT",
|
|
139
|
+
body: JSON.stringify({ provider, models }),
|
|
140
|
+
}, deps);
|
|
141
|
+
printData(result, wantsJson, [`${provider}: ${models.length ? models.join(", ") : "all models"}`]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function context(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
145
|
+
const args = [...argv];
|
|
146
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
147
|
+
const wantsJson = takeFlag(args, "--json");
|
|
148
|
+
if (action === "status") {
|
|
149
|
+
rejectArgs(args, USAGE);
|
|
150
|
+
const result = await runtimeRequest("/api/provider-context-caps", {}, deps);
|
|
151
|
+
printData(result, wantsJson, summaryLines(result));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let body: Record<string, unknown>;
|
|
155
|
+
if (action === "value") {
|
|
156
|
+
const raw = args.shift();
|
|
157
|
+
if (!raw) throw new CliUsageError("context value is required", USAGE);
|
|
158
|
+
const value = Number(raw.replace(/[_,]/g, ""));
|
|
159
|
+
if (!Number.isInteger(value) || value <= 0) throw new CliUsageError("context value must be a positive integer", USAGE);
|
|
160
|
+
body = { value };
|
|
161
|
+
} else if (action === "provider") {
|
|
162
|
+
const provider = args.shift()?.trim();
|
|
163
|
+
const state = args.shift()?.toLowerCase();
|
|
164
|
+
if (!provider || (state !== "on" && state !== "off")) throw new CliUsageError("provider and on|off are required", USAGE);
|
|
165
|
+
body = { provider, enabled: state === "on" };
|
|
166
|
+
} else if (action === "all") {
|
|
167
|
+
const state = args.shift()?.toLowerCase();
|
|
168
|
+
if (state !== "on" && state !== "off") throw new CliUsageError("all requires on|off", USAGE);
|
|
169
|
+
body = { setAll: state === "on" };
|
|
170
|
+
} else throw new CliUsageError(`unknown context action ${action}`, USAGE);
|
|
171
|
+
rejectArgs(args, USAGE);
|
|
172
|
+
const result = await runtimeRequest("/api/provider-context-caps", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
173
|
+
printData(result, wantsJson, ["Context cap settings updated."]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function shadow(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
177
|
+
const args = [...argv];
|
|
178
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
179
|
+
const wantsJson = takeFlag(args, "--json");
|
|
180
|
+
if (action === "status") {
|
|
181
|
+
rejectArgs(args, USAGE);
|
|
182
|
+
const result = await runtimeRequest("/api/shadow-call-settings", {}, deps);
|
|
183
|
+
printData(result, wantsJson);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (action !== "set") throw new CliUsageError(`unknown shadow action ${action}`, USAGE);
|
|
187
|
+
const modelRaw = args[0] && !args[0].startsWith("--") ? args.shift() : undefined;
|
|
188
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
189
|
+
rejectArgs(args, USAGE);
|
|
190
|
+
const body: Record<string, unknown> = {};
|
|
191
|
+
if (modelRaw !== undefined) body.model = modelRaw === "-" ? "" : modelRaw;
|
|
192
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
193
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("model and/or --enabled is required", USAGE);
|
|
194
|
+
const result = await runtimeRequest("/api/shadow-call-settings", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
195
|
+
printData(result, wantsJson, ["Shadow-call settings updated."]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function handleModelsRuntimeCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
199
|
+
let action: (() => Promise<void>) | undefined;
|
|
200
|
+
if (sub === "live") action = () => live(argv, deps);
|
|
201
|
+
else if (sub === "edit") action = () => edit(argv, deps);
|
|
202
|
+
else if (sub === "enable") action = () => visibility(true, argv, deps);
|
|
203
|
+
else if (sub === "disable") action = () => visibility(false, argv, deps);
|
|
204
|
+
else if (sub === "provider") action = () => providerVisibility(argv, deps);
|
|
205
|
+
else if (sub === "selected") action = () => selected(argv, deps);
|
|
206
|
+
else if (sub === "context") action = () => context(argv, deps);
|
|
207
|
+
else if (sub === "shadow") action = () => shadow(argv, deps);
|
|
208
|
+
if (!action) return null;
|
|
209
|
+
return runCliAction(action);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const MODELS_RUNTIME_USAGE = USAGE;
|
package/src/cli/models.ts
CHANGED
|
@@ -312,26 +312,25 @@ function handleConfiguredModels(args: string[]): void {
|
|
|
312
312
|
console.log("Note: providers with liveModels may have additional models at runtime.");
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
function
|
|
316
|
-
command.catch(error => {
|
|
317
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
318
|
-
process.exitCode = 1;
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function handleModels(args: string[]): void {
|
|
315
|
+
export async function handleModels(args: string[]): Promise<void> {
|
|
323
316
|
const [subcommand, ...rest] = args;
|
|
324
317
|
if (subcommand === "add") {
|
|
325
|
-
|
|
318
|
+
await handleCustomAdd(rest);
|
|
326
319
|
return;
|
|
327
320
|
}
|
|
328
321
|
if (subcommand === "remove") {
|
|
329
|
-
|
|
322
|
+
await handleCustomRemove(rest);
|
|
330
323
|
return;
|
|
331
324
|
}
|
|
332
325
|
if (subcommand === "list-custom") {
|
|
333
326
|
handleCustomList(rest);
|
|
334
327
|
return;
|
|
335
328
|
}
|
|
329
|
+
if (["live", "edit", "enable", "disable", "provider", "selected", "context", "shadow"].includes(subcommand ?? "")) {
|
|
330
|
+
const { handleModelsRuntimeCommand } = await import("./models-runtime");
|
|
331
|
+
const code = await handleModelsRuntimeCommand(subcommand!, rest);
|
|
332
|
+
if (code !== null) process.exitCode = code;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
336
335
|
handleConfiguredModels(subcommand === "list" ? rest : args);
|
|
337
336
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
printData,
|
|
4
|
+
rejectArgs,
|
|
5
|
+
runCliAction,
|
|
6
|
+
runtimeRequest,
|
|
7
|
+
summaryLines,
|
|
8
|
+
takeFlag,
|
|
9
|
+
takeIntegerOption,
|
|
10
|
+
takeOption,
|
|
11
|
+
type RuntimeApiDeps,
|
|
12
|
+
} from "./runtime-api";
|
|
13
|
+
|
|
14
|
+
const USAGE = `Usage:
|
|
15
|
+
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
16
|
+
[--limit <n>] [--follow] [--json|--jsonl]
|
|
17
|
+
ocx observe usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>] [--json]
|
|
18
|
+
ocx observe storage [--json]
|
|
19
|
+
ocx observe memory [--json]
|
|
20
|
+
ocx observe debug [--json]
|
|
21
|
+
ocx observe claude-inbound [--limit <n>] [--json]
|
|
22
|
+
ocx observe injection [--limit <n>] [--json]`;
|
|
23
|
+
|
|
24
|
+
type LogEntry = Record<string, unknown> & { id?: string | number; timestamp?: string; provider?: string; model?: string; status?: number };
|
|
25
|
+
|
|
26
|
+
function query(params: Record<string, string | number | undefined>): string {
|
|
27
|
+
const search = new URLSearchParams();
|
|
28
|
+
for (const [key, value] of Object.entries(params)) if (value !== undefined) search.set(key, String(value));
|
|
29
|
+
const encoded = search.toString();
|
|
30
|
+
return encoded ? `?${encoded}` : "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function logRows(data: unknown): LogEntry[] {
|
|
34
|
+
if (Array.isArray(data)) return data as LogEntry[];
|
|
35
|
+
if (data && typeof data === "object") {
|
|
36
|
+
const record = data as Record<string, unknown>;
|
|
37
|
+
for (const key of ["logs", "entries", "requests"]) if (Array.isArray(record[key])) return record[key] as LogEntry[];
|
|
38
|
+
}
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatLog(row: LogEntry): string {
|
|
43
|
+
const time = String(row.timestamp ?? row.createdAt ?? "");
|
|
44
|
+
const route = [row.provider, row.model].filter(Boolean).join("/");
|
|
45
|
+
const status = row.status ?? row.statusCode ?? "?";
|
|
46
|
+
const duration = row.durationMs !== undefined ? `${String(row.durationMs)}ms` : "";
|
|
47
|
+
return [time, String(status), route, duration].filter(Boolean).join(" ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
51
|
+
const args = [...argv];
|
|
52
|
+
const wantsJson = takeFlag(args, "--json");
|
|
53
|
+
const wantsJsonl = takeFlag(args, "--jsonl");
|
|
54
|
+
const follow = takeFlag(args, "--follow") || takeFlag(args, "-f");
|
|
55
|
+
const provider = takeOption(args, "--provider");
|
|
56
|
+
const model = takeOption(args, "--model");
|
|
57
|
+
const status = takeOption(args, "--status");
|
|
58
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 200;
|
|
59
|
+
rejectArgs(args, USAGE);
|
|
60
|
+
if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", USAGE);
|
|
61
|
+
if (follow && wantsJson) throw new CliUsageError("--follow uses --jsonl, not --json", USAGE);
|
|
62
|
+
let seen = new Set<string>();
|
|
63
|
+
do {
|
|
64
|
+
const data = await runtimeRequest(`/api/logs${query({ provider, model, status, limit })}`, {}, deps);
|
|
65
|
+
const rows = logRows(data);
|
|
66
|
+
if (!follow && wantsJson) printData(data, true);
|
|
67
|
+
else {
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
const key = String(row.id ?? `${row.timestamp}:${row.provider}:${row.model}:${row.status}`);
|
|
70
|
+
if (follow && seen.has(key)) continue;
|
|
71
|
+
if (wantsJsonl) console.log(JSON.stringify(row));
|
|
72
|
+
else console.log(formatLog(row));
|
|
73
|
+
seen.add(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!follow) return;
|
|
77
|
+
if (seen.size > 5_000) seen = new Set([...seen].slice(-2_500));
|
|
78
|
+
await Bun.sleep(1_000);
|
|
79
|
+
} while (true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
83
|
+
const args = [...argv];
|
|
84
|
+
const wantsJson = takeFlag(args, "--json");
|
|
85
|
+
const range = takeOption(args, "--range") ?? "30d";
|
|
86
|
+
const surface = takeOption(args, "--surface") ?? "all";
|
|
87
|
+
if (!["7d", "30d", "all"].includes(range)) throw new CliUsageError("--range must be 7d, 30d, or all", USAGE);
|
|
88
|
+
if (!["all", "codex", "claude", "grok"].includes(surface)) throw new CliUsageError("--surface must be all, codex, claude, or grok", USAGE);
|
|
89
|
+
rejectArgs(args, USAGE);
|
|
90
|
+
const result = await runtimeRequest(`/api/usage${query({ range, surface })}`, {}, deps);
|
|
91
|
+
printData(result, wantsJson, summaryLines(result));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
95
|
+
const args = [...argv];
|
|
96
|
+
const wantsJson = takeFlag(args, "--json");
|
|
97
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 });
|
|
98
|
+
rejectArgs(args, USAGE);
|
|
99
|
+
const result = await runtimeRequest(`${path}${query({ limit })}`, {}, deps);
|
|
100
|
+
printData(result, wantsJson, summaryLines(result));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function handleObserveCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
104
|
+
return runCliAction(async () => {
|
|
105
|
+
const [sub = "logs", ...rest] = argv;
|
|
106
|
+
if (sub === "logs") await logs(rest, deps);
|
|
107
|
+
else if (sub === "usage") await usage(rest, deps);
|
|
108
|
+
else if (sub === "storage") await simple("/api/storage", rest, deps);
|
|
109
|
+
else if (sub === "memory") await simple("/api/system/memory", rest, deps);
|
|
110
|
+
else if (sub === "debug") await simple("/api/debug", rest, deps);
|
|
111
|
+
else if (sub === "claude-inbound") await simple("/api/claude/inbound-debug", rest, deps);
|
|
112
|
+
else if (sub === "injection") await simple("/api/debug/injection-logs", rest, deps);
|
|
113
|
+
else throw new CliUsageError(`unknown observe command ${sub}`, USAGE);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const OBSERVE_USAGE = USAGE;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
csv,
|
|
4
|
+
printData,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
summaryLines,
|
|
9
|
+
takeBooleanOption,
|
|
10
|
+
takeFlag,
|
|
11
|
+
takeOption,
|
|
12
|
+
type RuntimeApiDeps,
|
|
13
|
+
} from "./runtime-api";
|
|
14
|
+
|
|
15
|
+
const USAGE = `Usage:
|
|
16
|
+
ocx provider edit <name> [--adapter <id>] [--base-url <url>] [--default-model <id|->]
|
|
17
|
+
[--auth-mode <key|forward|oauth|local|->] [--note <text|->]
|
|
18
|
+
[--enabled <on|off>] [--live-models <on|off>] [--allow-private-network <on|off>] [--json]
|
|
19
|
+
ocx provider test <name> [--json]
|
|
20
|
+
ocx provider quota [--refresh] [--json]
|
|
21
|
+
ocx provider presets [--json]
|
|
22
|
+
ocx provider account-mode <pool|direct> [--json]
|
|
23
|
+
ocx provider selected <name> [--set <model,model...>] [--clear] [--json]`;
|
|
24
|
+
|
|
25
|
+
function cleared(value: string | undefined): string | undefined {
|
|
26
|
+
return value === "-" ? "" : value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
30
|
+
const args = [...argv];
|
|
31
|
+
const name = args.shift()?.trim();
|
|
32
|
+
if (!name) throw new CliUsageError("provider name is required", USAGE);
|
|
33
|
+
const wantsJson = takeFlag(args, "--json");
|
|
34
|
+
const patch: Record<string, unknown> = {};
|
|
35
|
+
const adapter = takeOption(args, "--adapter");
|
|
36
|
+
const baseUrl = takeOption(args, "--base-url");
|
|
37
|
+
const defaultModel = cleared(takeOption(args, "--default-model"));
|
|
38
|
+
const authMode = cleared(takeOption(args, "--auth-mode"));
|
|
39
|
+
const note = cleared(takeOption(args, "--note"));
|
|
40
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
41
|
+
const liveModels = takeBooleanOption(args, "--live-models");
|
|
42
|
+
const allowPrivateNetwork = takeBooleanOption(args, "--allow-private-network");
|
|
43
|
+
rejectArgs(args, USAGE);
|
|
44
|
+
if (adapter !== undefined) patch.adapter = adapter;
|
|
45
|
+
if (baseUrl !== undefined) patch.baseUrl = baseUrl;
|
|
46
|
+
if (defaultModel !== undefined) patch.defaultModel = defaultModel;
|
|
47
|
+
if (authMode !== undefined) patch.authMode = authMode;
|
|
48
|
+
if (note !== undefined) patch.note = note;
|
|
49
|
+
if (enabled !== undefined) patch.disabled = !enabled;
|
|
50
|
+
if (liveModels !== undefined) patch.liveModels = liveModels;
|
|
51
|
+
if (allowPrivateNetwork !== undefined) patch.allowPrivateNetwork = allowPrivateNetwork;
|
|
52
|
+
if (Object.keys(patch).length === 0) throw new CliUsageError("at least one edit option is required", USAGE);
|
|
53
|
+
const result = await runtimeRequest(`/api/providers?name=${encodeURIComponent(name)}`, {
|
|
54
|
+
method: "PATCH",
|
|
55
|
+
body: JSON.stringify(patch),
|
|
56
|
+
}, deps);
|
|
57
|
+
printData(result, wantsJson, [`Updated provider ${name}.`]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function testProvider(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
61
|
+
const args = [...argv];
|
|
62
|
+
const name = args.shift()?.trim();
|
|
63
|
+
const wantsJson = takeFlag(args, "--json");
|
|
64
|
+
if (!name) throw new CliUsageError("provider name is required", USAGE);
|
|
65
|
+
rejectArgs(args, USAGE);
|
|
66
|
+
const result = await runtimeRequest<Record<string, unknown>>(`/api/providers/test?name=${encodeURIComponent(name)}`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
}, deps);
|
|
69
|
+
const ok = result.ok === true;
|
|
70
|
+
printData(result, wantsJson, [
|
|
71
|
+
`${name}: ${ok ? "connected" : "failed"}`,
|
|
72
|
+
String(result.message ?? result.error ?? "No detail"),
|
|
73
|
+
`Latency: ${String(result.latencyMs ?? "?")} ms`,
|
|
74
|
+
]);
|
|
75
|
+
if (!ok) process.exitCode = 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function quota(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
79
|
+
const args = [...argv];
|
|
80
|
+
const wantsJson = takeFlag(args, "--json");
|
|
81
|
+
const refresh = takeFlag(args, "--refresh");
|
|
82
|
+
rejectArgs(args, USAGE);
|
|
83
|
+
const result = await runtimeRequest(`/api/provider-quotas${refresh ? "?refresh=1" : ""}`, {}, deps);
|
|
84
|
+
printData(result, wantsJson, summaryLines(result));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function presets(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
88
|
+
const args = [...argv];
|
|
89
|
+
const wantsJson = takeFlag(args, "--json");
|
|
90
|
+
rejectArgs(args, USAGE);
|
|
91
|
+
const result = await runtimeRequest<{ providers?: unknown[] } | unknown[]>("/api/provider-presets", {}, deps);
|
|
92
|
+
const rows = Array.isArray(result) ? result : result.providers ?? [];
|
|
93
|
+
printData(result, wantsJson, rows.map(row => {
|
|
94
|
+
const record = row as Record<string, unknown>;
|
|
95
|
+
return `${String(record.id ?? record.name ?? "?")} ${String(record.label ?? record.adapter ?? "")}`.trimEnd();
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function accountMode(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
100
|
+
const args = [...argv];
|
|
101
|
+
const mode = args.shift();
|
|
102
|
+
const wantsJson = takeFlag(args, "--json");
|
|
103
|
+
if (mode !== "pool" && mode !== "direct") throw new CliUsageError("mode must be pool or direct", USAGE);
|
|
104
|
+
rejectArgs(args, USAGE);
|
|
105
|
+
const result = await runtimeRequest("/api/providers?name=openai", {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
body: JSON.stringify({ codexAccountMode: mode }),
|
|
108
|
+
}, deps);
|
|
109
|
+
printData(result, wantsJson, [`OpenAI Codex account mode: ${mode}`]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function selected(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
113
|
+
const args = [...argv];
|
|
114
|
+
const name = args.shift()?.trim();
|
|
115
|
+
const wantsJson = takeFlag(args, "--json");
|
|
116
|
+
const set = csv(takeOption(args, "--set"));
|
|
117
|
+
const clear = takeFlag(args, "--clear");
|
|
118
|
+
if (!name) throw new CliUsageError("provider name is required", USAGE);
|
|
119
|
+
if (set !== undefined && clear) throw new CliUsageError("--set and --clear cannot be combined", USAGE);
|
|
120
|
+
rejectArgs(args, USAGE);
|
|
121
|
+
if (set === undefined && !clear) {
|
|
122
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/selected-models", {}, deps);
|
|
123
|
+
const selectedMap = (result.selected ?? {}) as Record<string, string[]>;
|
|
124
|
+
const availableMap = (result.available ?? {}) as Record<string, string[]>;
|
|
125
|
+
printData({ provider: name, selected: selectedMap[name] ?? [], available: availableMap[name] ?? [] }, wantsJson,
|
|
126
|
+
[`Selected models for ${name}: ${(selectedMap[name] ?? []).join(", ") || "all"}`]);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const models = clear ? [] : (set ?? []);
|
|
130
|
+
const result = await runtimeRequest("/api/selected-models", {
|
|
131
|
+
method: "PUT",
|
|
132
|
+
body: JSON.stringify({ provider: name, models }),
|
|
133
|
+
}, deps);
|
|
134
|
+
printData(result, wantsJson, [`${name}: ${models.length ? models.join(", ") : "all models"}`]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function handleProviderRuntimeCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
138
|
+
const handlers: Record<string, (args: string[], deps: RuntimeApiDeps) => Promise<void>> = {
|
|
139
|
+
edit,
|
|
140
|
+
update: edit,
|
|
141
|
+
test: testProvider,
|
|
142
|
+
quota,
|
|
143
|
+
presets,
|
|
144
|
+
"account-mode": accountMode,
|
|
145
|
+
selected,
|
|
146
|
+
};
|
|
147
|
+
const handler = handlers[sub];
|
|
148
|
+
if (!handler) return null;
|
|
149
|
+
return runCliAction(() => handler(argv, deps));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const PROVIDER_RUNTIME_USAGE = USAGE;
|