@danypops/jittor 0.5.1 → 0.7.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 +62 -7
- package/docs/BENCHMARK_SOURCES.md +32 -0
- package/docs/OUTPUT_CHANNELS.md +31 -0
- package/docs/PROVIDER_RESEARCH.md +57 -0
- package/extension/src/benchmark-tui.ts +105 -0
- package/extension/src/footer.ts +68 -12
- package/extension/src/index.ts +263 -36
- package/extension/src/tui.ts +61 -9
- package/extension/src/usage.ts +165 -47
- package/package.json +5 -1
- package/src/adapters/metric-benchmark-store.ts +99 -0
- package/src/adapters/openrouter-benchmark-index-source.ts +94 -0
- package/src/adapters/openrouter-benchmark-source.ts +109 -0
- package/src/adapters/sqlite-metric-store.ts +43 -2
- package/src/cli.ts +660 -9
- package/src/client.ts +12 -44
- package/src/constants.ts +69 -5
- package/src/daemon.ts +65 -43
- package/src/db.ts +13 -30
- package/src/domain/benchmark.ts +264 -0
- package/src/domain/context-telemetry.ts +31 -0
- package/src/domain/metric.ts +46 -5
- package/src/domain/model-observation.ts +203 -0
- package/src/domain/model-ranking-service.ts +41 -0
- package/src/domain/model-ranking.ts +232 -0
- package/src/domain/task-cost.ts +70 -0
- package/src/domain/task-focus.ts +65 -0
- package/src/domain/usage.ts +134 -33
- package/src/log.ts +28 -0
- package/src/ports/benchmark-controller.ts +11 -0
- package/src/ports/benchmark-source.ts +7 -0
- package/src/ports/benchmark-store.ts +7 -0
- package/src/ports/metric-store.ts +30 -0
- package/src/ports/router-controller.ts +1 -0
- package/src/providers/anthropic-contracts.ts +127 -0
- package/src/providers/google-adc-auth.ts +63 -0
- package/src/providers/google-vertex-budget-contracts.ts +181 -0
- package/src/providers/google-vertex-budget.ts +127 -0
- package/src/providers/google-vertex-contracts.ts +116 -0
- package/src/providers/telemetry-sources.ts +35 -0
- package/src/router.ts +12 -0
- package/src/service.ts +132 -17
- package/src/state.ts +31 -57
- package/src/version.ts +2 -14
package/src/cli.ts
CHANGED
|
@@ -4,16 +4,42 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
BENCHMARK_MAX_QUERY_LIMIT,
|
|
9
|
+
CLI_AVAILABLE_ROUTES_MAX,
|
|
10
|
+
CLI_METRICS_HUMAN_MAX_ROWS,
|
|
11
|
+
HUMAN_TEXT_FIELD_MAX_CHARACTERS,
|
|
12
|
+
MAX_QUERY_LIMIT,
|
|
13
|
+
MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
|
|
14
|
+
MODEL_RANKING_DEFAULT_COST_WEIGHT,
|
|
15
|
+
MODEL_RANKING_DEFAULT_LATENCY_WEIGHT,
|
|
16
|
+
MODEL_RANKING_DEFAULT_QUALITY_WEIGHT,
|
|
17
|
+
MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
|
|
18
|
+
MODEL_RANKING_MAX_SOURCES,
|
|
19
|
+
SYSTEMD_UNIT_NAME,
|
|
20
|
+
MAX_USAGE_BUCKETS,
|
|
21
|
+
USAGE_MAX_DISTINCT_SCOPES,
|
|
22
|
+
} from "./constants.ts";
|
|
23
|
+
import { connectJittorClient, type JittorClient } from "./client.ts";
|
|
9
24
|
import { serveMain } from "./daemon.ts";
|
|
25
|
+
import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "./domain/benchmark.ts";
|
|
26
|
+
import type { ModelRecommendationInput } from "./domain/model-ranking-service.ts";
|
|
27
|
+
import type { ModelCandidate, ModelRankingResult, ScopeAuthority, UtilityWeights } from "./domain/model-ranking.ts";
|
|
28
|
+
import { TASK_DOMAINS, TASK_TYPES, type ModelTaskDomain, type ModelTaskType } from "./domain/model-observation.ts";
|
|
10
29
|
import type { ContextAssessment } from "./domain/context-telemetry.ts";
|
|
30
|
+
import { METRIC_UNITS, type MetricObservation, type MetricQuery, type MetricUnit, type StoredMetricObservation } from "./domain/metric.ts";
|
|
31
|
+
import type { CompactionDurationEstimate } from "./domain/context-telemetry.ts";
|
|
32
|
+
import type { TaskCostSummary } from "./domain/task-cost.ts";
|
|
33
|
+
import type { PolicyDecision, Route } from "./policy.ts";
|
|
34
|
+
import type { RouteOverride, RouterStatus, TelemetryPollResult } from "./ports/router-controller.ts";
|
|
35
|
+
import { EXPECTED_OPERATION_NAMES, type OperationInputs, type OperationName, type OperationOutputs } from "./service.ts";
|
|
11
36
|
import { resolveJittorPaths } from "./state.ts";
|
|
12
37
|
|
|
13
38
|
export interface SystemdUnitOptions {
|
|
14
39
|
bunBin: string;
|
|
15
40
|
cliPath: string;
|
|
16
41
|
codexAuthFile?: string;
|
|
42
|
+
openRouterBenchmarks?: boolean;
|
|
17
43
|
}
|
|
18
44
|
|
|
19
45
|
export function renderSystemdUnit(options: SystemdUnitOptions): string {
|
|
@@ -25,7 +51,7 @@ Wants=network-online.target
|
|
|
25
51
|
[Service]
|
|
26
52
|
Type=simple
|
|
27
53
|
ExecStart=${options.bunBin} ${options.cliPath} serve
|
|
28
|
-
${options.codexAuthFile ? `Environment="JITTOR_CODEX_AUTH_FILE=${options.codexAuthFile.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"\n` : ""}Restart=always
|
|
54
|
+
${options.codexAuthFile ? `Environment="JITTOR_CODEX_AUTH_FILE=${options.codexAuthFile.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"\n` : ""}${options.openRouterBenchmarks ? "Environment=JITTOR_OPENROUTER_BENCHMARKS=1\n" : ""}Restart=always
|
|
29
55
|
RestartSec=2
|
|
30
56
|
NoNewPrivileges=true
|
|
31
57
|
PrivateTmp=true
|
|
@@ -35,12 +61,8 @@ WantedBy=default.target
|
|
|
35
61
|
`;
|
|
36
62
|
}
|
|
37
63
|
|
|
38
|
-
interface ContextClient {
|
|
39
|
-
call(operation: "context.assess", input: { since?: number; until?: number }): Promise<ContextAssessment>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
64
|
export interface CliDependencies {
|
|
43
|
-
client:
|
|
65
|
+
client: Pick<JittorClient, "call">;
|
|
44
66
|
stdout(line: string): void;
|
|
45
67
|
stderr(line: string): void;
|
|
46
68
|
systemctl(...args: string[]): void;
|
|
@@ -60,6 +82,7 @@ function installService(): void {
|
|
|
60
82
|
bunBin: process.execPath,
|
|
61
83
|
cliPath: fileURLToPath(import.meta.url),
|
|
62
84
|
...(existsSync(codexAuthFile) ? { codexAuthFile } : {}),
|
|
85
|
+
openRouterBenchmarks: process.env["JITTOR_OPENROUTER_BENCHMARKS"] === "1",
|
|
63
86
|
}));
|
|
64
87
|
systemctl("daemon-reload");
|
|
65
88
|
systemctl("enable", SYSTEMD_UNIT_NAME);
|
|
@@ -76,7 +99,26 @@ const DEFAULT_DEPENDENCIES: CliDependencies = {
|
|
|
76
99
|
};
|
|
77
100
|
|
|
78
101
|
function usage(stderr: (line: string) => void): number {
|
|
79
|
-
stderr(
|
|
102
|
+
stderr([
|
|
103
|
+
"Usage: jittor <command> [options]",
|
|
104
|
+
" serve",
|
|
105
|
+
" service <install|start|stop|restart|status|checkpoint>",
|
|
106
|
+
" context [--since <ms>] [--until <ms>] [--json]",
|
|
107
|
+
" benchmarks <status|refresh|list|rank> [options] [--json]",
|
|
108
|
+
" metrics record --source <s> --scope <s> --metric <s> --value <number|null> --unit <unit> [--observed-at <ms>] [--attributes <json>] [--json]",
|
|
109
|
+
" metrics query [--source <s>] [--scope <s>] [--metric <s>] [--since <ms>] [--until <ms>] [--limit <n>] [--order asc|desc] [--json]",
|
|
110
|
+
" metrics prune --before <ms> [--force] [--json] (force required if before is newer than 24h ago)",
|
|
111
|
+
` metrics distinct-scopes --source <s> --since <ms> --until <ms> [--limit 1..${USAGE_MAX_DISTINCT_SCOPES}] [--json]`,
|
|
112
|
+
` metrics usage-series --source <s> --since <ms> --until <ms> --bucket-size-ms <ms> --bucket-count 1..${MAX_USAGE_BUCKETS} [--scope-limit 1..${USAGE_MAX_DISTINCT_SCOPES}] [--json]`,
|
|
113
|
+
" metrics cost-by-task --since <ms> --until <ms> [--json]",
|
|
114
|
+
" telemetry poll [--json]",
|
|
115
|
+
" compaction estimate [--json]",
|
|
116
|
+
" router <status|decide|pause|resume|clear-override> [--json]",
|
|
117
|
+
" router override --route <provider/model@thinking> [--expires-at <ms>] [--json]",
|
|
118
|
+
" router current-route --route <provider/model@thinking> [--json]",
|
|
119
|
+
" router available-routes [--route <provider/model@thinking> ...] [--json]",
|
|
120
|
+
" op <operation> [--input <json>]",
|
|
121
|
+
].join("\n"));
|
|
80
122
|
return 2;
|
|
81
123
|
}
|
|
82
124
|
|
|
@@ -97,10 +139,396 @@ function parseContextArgs(args: string[]): { input: { since?: number; until?: nu
|
|
|
97
139
|
return { input, json };
|
|
98
140
|
}
|
|
99
141
|
|
|
142
|
+
function parseRoute(raw: string | undefined): Route | null {
|
|
143
|
+
if (raw === undefined) return null;
|
|
144
|
+
return parseCandidate(raw);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface MetricsRecordArgs { input: MetricObservation; json: boolean }
|
|
148
|
+
|
|
149
|
+
function parseMetricsRecordArgs(args: string[]): MetricsRecordArgs | null {
|
|
150
|
+
let json = false;
|
|
151
|
+
let source: string | undefined;
|
|
152
|
+
let scope: string | undefined;
|
|
153
|
+
let metric: string | undefined;
|
|
154
|
+
let value: number | null | undefined;
|
|
155
|
+
let unit: MetricUnit | undefined;
|
|
156
|
+
let observedAt: number | undefined;
|
|
157
|
+
let attributes: Record<string, unknown> | undefined;
|
|
158
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
159
|
+
const argument = args[index];
|
|
160
|
+
if (argument === "--json") { json = true; continue; }
|
|
161
|
+
if (!["--source", "--scope", "--metric", "--value", "--unit", "--observed-at", "--attributes"].includes(argument ?? "")) return null;
|
|
162
|
+
const raw = args[++index];
|
|
163
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
164
|
+
if (argument === "--source") source = raw;
|
|
165
|
+
else if (argument === "--scope") scope = raw;
|
|
166
|
+
else if (argument === "--metric") metric = raw;
|
|
167
|
+
else if (argument === "--value") {
|
|
168
|
+
if (raw.toLowerCase() === "null") value = null;
|
|
169
|
+
else {
|
|
170
|
+
const parsed = Number(raw);
|
|
171
|
+
if (!Number.isFinite(parsed)) return null;
|
|
172
|
+
value = parsed;
|
|
173
|
+
}
|
|
174
|
+
} else if (argument === "--unit") {
|
|
175
|
+
if (!METRIC_UNITS.includes(raw as MetricUnit)) return null;
|
|
176
|
+
unit = raw as MetricUnit;
|
|
177
|
+
} else if (argument === "--observed-at") {
|
|
178
|
+
const parsed = Number(raw);
|
|
179
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
180
|
+
observedAt = parsed;
|
|
181
|
+
} else {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(raw);
|
|
184
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
185
|
+
attributes = parsed as Record<string, unknown>;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (source === undefined || scope === undefined || metric === undefined || value === undefined || unit === undefined) return null;
|
|
192
|
+
return {
|
|
193
|
+
json,
|
|
194
|
+
input: {
|
|
195
|
+
source, scope, metric, value, unit,
|
|
196
|
+
observedAt: observedAt ?? Date.now(),
|
|
197
|
+
...(attributes ? { attributes } : {}),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface MetricsQueryArgs { input: MetricQuery; json: boolean }
|
|
203
|
+
|
|
204
|
+
function parseMetricsQueryArgs(args: string[]): MetricsQueryArgs | null {
|
|
205
|
+
let json = false;
|
|
206
|
+
const input: MetricQuery = {};
|
|
207
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
208
|
+
const argument = args[index];
|
|
209
|
+
if (argument === "--json") { json = true; continue; }
|
|
210
|
+
if (!["--source", "--scope", "--metric", "--since", "--until", "--limit", "--order"].includes(argument ?? "")) return null;
|
|
211
|
+
const raw = args[++index];
|
|
212
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
213
|
+
if (argument === "--source") input.source = raw;
|
|
214
|
+
else if (argument === "--scope") input.scope = raw;
|
|
215
|
+
else if (argument === "--metric") input.metric = raw;
|
|
216
|
+
else if (argument === "--order") {
|
|
217
|
+
if (raw !== "asc" && raw !== "desc") return null;
|
|
218
|
+
input.order = raw;
|
|
219
|
+
} else {
|
|
220
|
+
const parsed = Number(raw);
|
|
221
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
222
|
+
if (argument === "--since") input.since = parsed;
|
|
223
|
+
else if (argument === "--until") input.until = parsed;
|
|
224
|
+
else {
|
|
225
|
+
if (parsed < 1 || parsed > MAX_QUERY_LIMIT) return null;
|
|
226
|
+
input.limit = parsed;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (input.since !== undefined && input.until !== undefined && input.until < input.since) return null;
|
|
231
|
+
return { input, json };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface MetricsDistinctScopesArgs { input: { source: string; since: number; until: number; limit?: number }; json: boolean }
|
|
235
|
+
|
|
236
|
+
function parseMetricsDistinctScopesArgs(args: string[]): MetricsDistinctScopesArgs | null {
|
|
237
|
+
let json = false;
|
|
238
|
+
let source: string | undefined;
|
|
239
|
+
let since: number | undefined;
|
|
240
|
+
let until: number | undefined;
|
|
241
|
+
let limit: number | undefined;
|
|
242
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
243
|
+
const argument = args[index];
|
|
244
|
+
if (argument === "--json") { json = true; continue; }
|
|
245
|
+
if (!["--source", "--since", "--until", "--limit"].includes(argument ?? "")) return null;
|
|
246
|
+
const raw = args[++index];
|
|
247
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
248
|
+
if (argument === "--source") { source = raw; continue; }
|
|
249
|
+
const parsed = Number(raw);
|
|
250
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
251
|
+
if (argument === "--since") since = parsed;
|
|
252
|
+
else if (argument === "--until") until = parsed;
|
|
253
|
+
else {
|
|
254
|
+
if (parsed < 1 || parsed > USAGE_MAX_DISTINCT_SCOPES) return null;
|
|
255
|
+
limit = parsed;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (source === undefined || since === undefined || until === undefined || until < since) return null;
|
|
259
|
+
return { input: { source, since, until, ...(limit === undefined ? {} : { limit }) }, json };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
interface MetricsUsageSeriesArgs { input: { source: string; since: number; until: number; bucketSizeMs: number; bucketCount: number; scopeLimit?: number }; json: boolean }
|
|
263
|
+
|
|
264
|
+
function parseMetricsUsageSeriesArgs(args: string[]): MetricsUsageSeriesArgs | null {
|
|
265
|
+
let json = false;
|
|
266
|
+
let source: string | undefined;
|
|
267
|
+
let since: number | undefined;
|
|
268
|
+
let until: number | undefined;
|
|
269
|
+
let bucketSizeMs: number | undefined;
|
|
270
|
+
let bucketCount: number | undefined;
|
|
271
|
+
let scopeLimit: number | undefined;
|
|
272
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
273
|
+
const argument = args[index];
|
|
274
|
+
if (argument === "--json") { json = true; continue; }
|
|
275
|
+
if (!(["--source", "--since", "--until", "--bucket-size-ms", "--bucket-count", "--scope-limit"].includes(argument ?? ""))) return null;
|
|
276
|
+
const raw = args[++index];
|
|
277
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
278
|
+
if (argument === "--source") { source = raw; continue; }
|
|
279
|
+
const parsed = Number(raw);
|
|
280
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
281
|
+
if (argument === "--since") since = parsed;
|
|
282
|
+
else if (argument === "--until") until = parsed;
|
|
283
|
+
else if (argument === "--bucket-size-ms") { if (parsed < 1) return null; bucketSizeMs = parsed; }
|
|
284
|
+
else if (argument === "--bucket-count") { if (parsed < 1 || parsed > MAX_USAGE_BUCKETS) return null; bucketCount = parsed; }
|
|
285
|
+
else { if (parsed < 1 || parsed > USAGE_MAX_DISTINCT_SCOPES) return null; scopeLimit = parsed; }
|
|
286
|
+
}
|
|
287
|
+
if (source === undefined || since === undefined || until === undefined || until < since || bucketSizeMs === undefined || bucketCount === undefined) return null;
|
|
288
|
+
return { input: { source, since, until, bucketSizeMs, bucketCount, ...(scopeLimit === undefined ? {} : { scopeLimit }) }, json };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface CostByTaskArgs { input: { since: number; until: number }; json: boolean }
|
|
292
|
+
|
|
293
|
+
function parseCostByTaskArgs(args: string[]): CostByTaskArgs | null {
|
|
294
|
+
let json = false;
|
|
295
|
+
let since: number | undefined;
|
|
296
|
+
let until: number | undefined;
|
|
297
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
298
|
+
const argument = args[index];
|
|
299
|
+
if (argument === "--json") { json = true; continue; }
|
|
300
|
+
if (!["--since", "--until"].includes(argument ?? "")) return null;
|
|
301
|
+
const raw = args[++index];
|
|
302
|
+
const parsed = Number(raw);
|
|
303
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
304
|
+
if (argument === "--since") since = parsed;
|
|
305
|
+
else until = parsed;
|
|
306
|
+
}
|
|
307
|
+
if (since === undefined || until === undefined || until < since) return null;
|
|
308
|
+
return { input: { since, until }, json };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface MetricsPruneArgs { input: { before: number; force?: boolean }; json: boolean }
|
|
312
|
+
|
|
313
|
+
function parseMetricsPruneArgs(args: string[]): MetricsPruneArgs | null {
|
|
314
|
+
let json = false;
|
|
315
|
+
let force = false;
|
|
316
|
+
let before: number | undefined;
|
|
317
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
318
|
+
const argument = args[index];
|
|
319
|
+
if (argument === "--json") { json = true; continue; }
|
|
320
|
+
if (argument === "--force") { force = true; continue; }
|
|
321
|
+
if (argument !== "--before") return null;
|
|
322
|
+
const raw = args[++index];
|
|
323
|
+
const parsed = Number(raw);
|
|
324
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
325
|
+
before = parsed;
|
|
326
|
+
}
|
|
327
|
+
if (before === undefined) return null;
|
|
328
|
+
return { input: { before, ...(force ? { force } : {}) }, json };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
interface RouterOverrideArgs { input: RouteOverride; json: boolean }
|
|
332
|
+
|
|
333
|
+
function parseRouterOverrideArgs(args: string[]): RouterOverrideArgs | null {
|
|
334
|
+
let json = false;
|
|
335
|
+
let route: Route | null = null;
|
|
336
|
+
let expiresAt: number | null = null;
|
|
337
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
338
|
+
const argument = args[index];
|
|
339
|
+
if (argument === "--json") { json = true; continue; }
|
|
340
|
+
if (!["--route", "--expires-at"].includes(argument ?? "")) return null;
|
|
341
|
+
const raw = args[++index];
|
|
342
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
343
|
+
if (argument === "--route") {
|
|
344
|
+
route = parseRoute(raw);
|
|
345
|
+
if (!route) return null;
|
|
346
|
+
} else {
|
|
347
|
+
const parsed = Number(raw);
|
|
348
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
349
|
+
expiresAt = parsed;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!route) return null;
|
|
353
|
+
return { input: { route, expiresAt }, json };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
interface RouterRouteArgs { input: Route; json: boolean }
|
|
357
|
+
|
|
358
|
+
function parseRouterRouteArgs(args: string[]): RouterRouteArgs | null {
|
|
359
|
+
let json = false;
|
|
360
|
+
let route: Route | null = null;
|
|
361
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
362
|
+
const argument = args[index];
|
|
363
|
+
if (argument === "--json") { json = true; continue; }
|
|
364
|
+
if (argument !== "--route") return null;
|
|
365
|
+
const raw = args[++index];
|
|
366
|
+
if (raw === undefined) return null;
|
|
367
|
+
route = parseRoute(raw);
|
|
368
|
+
if (!route) return null;
|
|
369
|
+
}
|
|
370
|
+
if (!route) return null;
|
|
371
|
+
return { input: route, json };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
interface RouterAvailableRoutesArgs { input: { routes: Route[] }; json: boolean }
|
|
375
|
+
|
|
376
|
+
function parseRouterAvailableRoutesArgs(args: string[]): RouterAvailableRoutesArgs | null {
|
|
377
|
+
let json = false;
|
|
378
|
+
const routes: Route[] = [];
|
|
379
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
380
|
+
const argument = args[index];
|
|
381
|
+
if (argument === "--json") { json = true; continue; }
|
|
382
|
+
if (argument !== "--route") return null;
|
|
383
|
+
const raw = args[++index];
|
|
384
|
+
if (raw === undefined) return null;
|
|
385
|
+
const route = parseRoute(raw);
|
|
386
|
+
if (!route) return null;
|
|
387
|
+
if (routes.length >= CLI_AVAILABLE_ROUTES_MAX) return null;
|
|
388
|
+
routes.push(route);
|
|
389
|
+
}
|
|
390
|
+
return { input: { routes }, json };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function parseJsonOnlyArgs(args: string[]): { json: boolean } | null {
|
|
394
|
+
let json = false;
|
|
395
|
+
for (const argument of args) {
|
|
396
|
+
if (argument !== "--json") return null;
|
|
397
|
+
json = true;
|
|
398
|
+
}
|
|
399
|
+
return { json };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function parseOpArgs(args: string[]): { operation: OperationName; input: Record<string, unknown> } | null {
|
|
403
|
+
const [operation, ...rest] = args;
|
|
404
|
+
if (operation === undefined || !EXPECTED_OPERATION_NAMES.includes(operation as OperationName)) return null;
|
|
405
|
+
let input: Record<string, unknown> = {};
|
|
406
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
407
|
+
if (rest[index] !== "--input") return null;
|
|
408
|
+
const raw = rest[++index];
|
|
409
|
+
if (raw === undefined) return null;
|
|
410
|
+
try {
|
|
411
|
+
const parsed = JSON.parse(raw);
|
|
412
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
413
|
+
input = parsed as Record<string, unknown>;
|
|
414
|
+
} catch {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { operation: operation as OperationName, input };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
interface BenchmarkArgs {
|
|
422
|
+
action: "status" | "refresh" | "list" | "rank";
|
|
423
|
+
json: boolean;
|
|
424
|
+
force: boolean;
|
|
425
|
+
query?: BenchmarkQuery;
|
|
426
|
+
recommendation?: ModelRecommendationInput;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function parseCandidate(raw: string): ModelCandidate | null {
|
|
430
|
+
const separator = raw.indexOf("/");
|
|
431
|
+
const thinkingSeparator = raw.lastIndexOf("@");
|
|
432
|
+
if (separator <= 0 || thinkingSeparator <= separator + 1 || thinkingSeparator === raw.length - 1) return null;
|
|
433
|
+
return { provider: raw.slice(0, separator), model: raw.slice(separator + 1, thinkingSeparator), thinking: raw.slice(thinkingSeparator + 1) };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function parseBenchmarkArgs(action: string | undefined, args: string[]): BenchmarkArgs | null {
|
|
437
|
+
if (action !== "status" && action !== "refresh" && action !== "list" && action !== "rank") return null;
|
|
438
|
+
let json = false;
|
|
439
|
+
let force = false;
|
|
440
|
+
const query: Partial<BenchmarkQuery> = {};
|
|
441
|
+
const candidates: ModelCandidate[] = [];
|
|
442
|
+
const sourceIds: string[] = [];
|
|
443
|
+
let scopeAuthority: ScopeAuthority = "available-models";
|
|
444
|
+
let domain: ModelTaskDomain = "general";
|
|
445
|
+
let type: ModelTaskType = "general";
|
|
446
|
+
let budgetPressure = 0;
|
|
447
|
+
const weights: UtilityWeights = {
|
|
448
|
+
quality: MODEL_RANKING_DEFAULT_QUALITY_WEIGHT, cost: MODEL_RANKING_DEFAULT_COST_WEIGHT,
|
|
449
|
+
latency: MODEL_RANKING_DEFAULT_LATENCY_WEIGHT, context: MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
|
|
450
|
+
reliability: MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
|
|
451
|
+
};
|
|
452
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
453
|
+
const argument = args[index];
|
|
454
|
+
if (argument === "--json") { json = true; continue; }
|
|
455
|
+
if (argument === "--force" && action === "refresh") { force = true; continue; }
|
|
456
|
+
const allowed = action === "list" ? ["--source", "--model", "--dimension", "--limit"]
|
|
457
|
+
: action === "rank" ? ["--candidate", "--source", "--domain", "--type", "--scope", "--budget", "--weight-quality", "--weight-cost", "--weight-latency", "--weight-context", "--weight-reliability"] : [];
|
|
458
|
+
if (!allowed.includes(argument ?? "")) return null;
|
|
459
|
+
const raw = args[++index];
|
|
460
|
+
if (raw === undefined || raw.length === 0) return null;
|
|
461
|
+
if (action === "list") {
|
|
462
|
+
if (argument === "--limit") {
|
|
463
|
+
const limit = Number(raw);
|
|
464
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > BENCHMARK_MAX_QUERY_LIMIT) return null;
|
|
465
|
+
query.limit = limit;
|
|
466
|
+
} else if (argument === "--source") query.sourceId = raw;
|
|
467
|
+
else if (argument === "--model") query.model = raw;
|
|
468
|
+
else query.dimension = raw;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (argument === "--candidate") {
|
|
472
|
+
const candidate = parseCandidate(raw);
|
|
473
|
+
if (!candidate) return null;
|
|
474
|
+
candidates.push(candidate);
|
|
475
|
+
} else if (argument === "--source") sourceIds.push(raw);
|
|
476
|
+
else if (argument === "--domain") {
|
|
477
|
+
if (!TASK_DOMAINS.includes(raw as ModelTaskDomain)) return null;
|
|
478
|
+
domain = raw as ModelTaskDomain;
|
|
479
|
+
} else if (argument === "--type") {
|
|
480
|
+
if (!TASK_TYPES.includes(raw as ModelTaskType)) return null;
|
|
481
|
+
type = raw as ModelTaskType;
|
|
482
|
+
} else if (argument === "--scope") {
|
|
483
|
+
if (raw !== "exact-session" && raw !== "available-models") return null;
|
|
484
|
+
scopeAuthority = raw;
|
|
485
|
+
} else if (argument === "--budget") budgetPressure = Number(raw);
|
|
486
|
+
else {
|
|
487
|
+
const weight = Number(raw);
|
|
488
|
+
if (!Number.isFinite(weight) || weight < 0 || weight > 10) return null;
|
|
489
|
+
weights[argument!.slice("--weight-".length) as keyof UtilityWeights] = weight;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (action === "list" && query.sourceId === undefined) return null;
|
|
493
|
+
if (action === "rank" && (candidates.length === 0 || sourceIds.length > MODEL_RANKING_MAX_SOURCES || !Number.isFinite(budgetPressure) || budgetPressure < 0 || budgetPressure > 2)) return null;
|
|
494
|
+
return {
|
|
495
|
+
action, json, force,
|
|
496
|
+
...(action === "list" ? { query: query as BenchmarkQuery } : {}),
|
|
497
|
+
...(action === "rank" ? { recommendation: { candidates, sourceIds: [...new Set(sourceIds)], scopeAuthority, domain, type, budgetPressure, weights } } : {}),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
100
501
|
function value(value: number | null, suffix = ""): string {
|
|
101
502
|
return value === null ? "unknown" : `${Math.round(value).toLocaleString()}${suffix}`;
|
|
102
503
|
}
|
|
103
504
|
|
|
505
|
+
export function formatBenchmarkStatus(result: BenchmarkRefreshResult): string {
|
|
506
|
+
if (result.sources.length === 0) return "Benchmark sources: none configured";
|
|
507
|
+
return ["Benchmark sources:", ...result.sources.map((source) => {
|
|
508
|
+
const state = source.ok === null ? "not refreshed" : source.ok ? "ready" : "refresh failed";
|
|
509
|
+
return `- ${source.id}: ${state} · ${source.observations.toLocaleString()} observations · ${source.hasEvidence ? "evidence retained" : "no evidence"}`;
|
|
510
|
+
})].join("\n");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function humanField(value: string): string {
|
|
514
|
+
return value.length <= HUMAN_TEXT_FIELD_MAX_CHARACTERS ? value : `${value.slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS - 1)}…`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function formatBenchmarkQuery(result: BenchmarkQueryResult): string {
|
|
518
|
+
return [
|
|
519
|
+
`Benchmark evidence: ${humanField(result.sourceId)} · ${result.completeness} · ${result.freshness} · ${result.observations.length.toLocaleString()} observations`,
|
|
520
|
+
...result.observations.map((observation) => `- ${humanField(observation.model.canonical)} · ${humanField(observation.dimension)} ${observation.value.toLocaleString()} ${observation.unit} · ${humanField(observation.provenance.publisher)} · confidence ${(observation.provenance.confidence * 100).toFixed(0)}%`),
|
|
521
|
+
].join("\n");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function formatModelRanking(result: ModelRankingResult): string {
|
|
525
|
+
return [
|
|
526
|
+
`Model ranking: ${result.completeness} · scope ${result.scopeAuthority}${result.scopeWarning ? " · advisory only" : ""}`,
|
|
527
|
+
...result.ranked.map((item, index) => `${index + 1}. ${humanField(item.identity)} · utility ${item.utility === null ? "unknown" : item.utility.toFixed(3)} · confidence ${(item.confidence * 100).toFixed(0)}%`),
|
|
528
|
+
...(result.scopeWarning ? [result.scopeWarning] : []),
|
|
529
|
+
].join("\n");
|
|
530
|
+
}
|
|
531
|
+
|
|
104
532
|
export function formatContextAssessment(summary: ContextAssessment): string {
|
|
105
533
|
return [
|
|
106
534
|
`Context assessment: ${summary.completeness}`,
|
|
@@ -112,9 +540,227 @@ export function formatContextAssessment(summary: ContextAssessment): string {
|
|
|
112
540
|
].join("\n");
|
|
113
541
|
}
|
|
114
542
|
|
|
543
|
+
function formatRoute(route: Route): string {
|
|
544
|
+
return `${humanField(route.provider)}/${humanField(route.model)} · ${humanField(route.thinking)}`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export function formatMetricsQuery(rows: StoredMetricObservation[]): string {
|
|
548
|
+
if (rows.length === 0) return "Metrics: no observations matched";
|
|
549
|
+
const shown = rows.slice(0, CLI_METRICS_HUMAN_MAX_ROWS);
|
|
550
|
+
const lines = [
|
|
551
|
+
`Metrics: ${rows.length.toLocaleString()} observation(s)${rows.length > shown.length ? ` (showing first ${shown.length})` : ""}`,
|
|
552
|
+
...shown.map((row) => `- ${humanField(row.source)}/${humanField(row.scope)}/${humanField(row.metric)} = ${row.value === null ? "null" : row.value} ${row.unit} @ ${new Date(row.observedAt).toISOString()}`),
|
|
553
|
+
];
|
|
554
|
+
return lines.join("\n");
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function formatMetricsDistinctScopes(scopes: string[]): string {
|
|
558
|
+
if (scopes.length === 0) return "Scopes: none matched";
|
|
559
|
+
return [`Scopes: ${scopes.length.toLocaleString()}`, ...scopes.map((scope) => `- ${humanField(scope)}`)].join("\n");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export function formatMetricsUsageSeries(result: { rows: Array<{ scope: string; metric: string; bucketIndex: number; sum: number }>; truncated: boolean }): string {
|
|
563
|
+
if (result.rows.length === 0) return `Usage series: no data${result.truncated ? " (scope limit reached)" : ""}`;
|
|
564
|
+
const lines = [`Usage series: ${result.rows.length.toLocaleString()} bucket(s)${result.truncated ? " (scope limit reached)" : ""}`];
|
|
565
|
+
for (const row of result.rows) lines.push(`- ${humanField(row.scope)}/${humanField(row.metric)} bucket ${row.bucketIndex}: ${row.sum.toLocaleString()}`);
|
|
566
|
+
return lines.join("\n");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function formatUsdAmount(amount: number): string {
|
|
570
|
+
return `$${amount.toFixed(Math.abs(amount) < 0.01 && amount !== 0 ? 4 : 2)}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function formatCostByTask(summary: TaskCostSummary): string {
|
|
574
|
+
const lines = [
|
|
575
|
+
`Cost by task: ${summary.entries.length.toLocaleString()} task(s)${summary.truncated ? " (query limit reached; totals are a lower bound)" : ""}`,
|
|
576
|
+
...summary.entries.map((entry) => `- ${humanField(entry.taskId)}: ${formatUsdAmount(entry.costUsd)} · ↑${entry.inputTokens.toLocaleString()} ↓${entry.outputTokens.toLocaleString()} R${entry.cacheReadTokens.toLocaleString()} W${entry.cacheWriteTokens.toLocaleString()}`),
|
|
577
|
+
`Unattributed spend (no task was focused): ${formatUsdAmount(summary.unattributedCostUsd)}`,
|
|
578
|
+
];
|
|
579
|
+
return lines.join("\n");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function formatTelemetryPoll(result: TelemetryPollResult): string {
|
|
583
|
+
if (result.sources.length === 0) return "Telemetry: no sources configured";
|
|
584
|
+
return ["Telemetry:", ...result.sources.map((source) => {
|
|
585
|
+
const freshness = !source.ok ? `failed${source.error ? ` (${humanField(source.error)})` : ""}` : "ok";
|
|
586
|
+
return `- ${humanField(source.id)} (${humanField(source.provider)}): ${freshness} · ${source.metrics} metric(s)`;
|
|
587
|
+
})].join("\n");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function formatRouterStatus(status: RouterStatus): string {
|
|
591
|
+
const lines = [
|
|
592
|
+
`Router: ${status.ready ? "ready" : "not ready"}${status.paused ? " · paused" : ""}`,
|
|
593
|
+
`Current route: ${status.currentRoute ? formatRoute(status.currentRoute) : "none"}`,
|
|
594
|
+
`Available routes: ${status.availableRoutes.length.toLocaleString()}`,
|
|
595
|
+
`Override: ${status.override ? `${formatRoute(status.override.route)}${status.override.expiresAt === null ? "" : ` (expires ${new Date(status.override.expiresAt).toISOString()})`}` : "none"}`,
|
|
596
|
+
];
|
|
597
|
+
if (status.lastDecision) lines.push(`Last decision: ${status.lastDecision.action} · pressure ${Number.isFinite(status.lastDecision.pressure) ? status.lastDecision.pressure.toFixed(3) : "∞"} · ${humanField(status.lastDecision.reason)}`);
|
|
598
|
+
lines.push(formatTelemetryPoll({ sources: status.sources, observedAt: Date.now() }));
|
|
599
|
+
return lines.join("\n");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function formatCompactionEstimate(estimate: CompactionDurationEstimate): string {
|
|
603
|
+
if (estimate.confidence === "cold-start" || estimate.ms === null) {
|
|
604
|
+
return `Compaction duration: cold-start (${estimate.sampleSize.toLocaleString()} sample(s), not enough evidence yet)`;
|
|
605
|
+
}
|
|
606
|
+
return `Compaction duration: ~${estimate.ms.toLocaleString()}ms learned from ${estimate.sampleSize.toLocaleString()} sample(s)`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function formatPolicyDecision(decision: PolicyDecision): string {
|
|
610
|
+
const lines = [`Decision: ${decision.action} · pressure ${Number.isFinite(decision.pressure) ? decision.pressure.toFixed(3) : "∞"} · ${humanField(decision.reason)}`];
|
|
611
|
+
if (decision.route) lines.push(`Route: ${formatRoute(decision.route)}`);
|
|
612
|
+
if (decision.delayMs !== undefined) lines.push(`Delay: ${decision.delayMs}ms`);
|
|
613
|
+
return lines.join("\n");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function callAndPrint<Name extends OperationName>(
|
|
617
|
+
deps: CliDependencies,
|
|
618
|
+
operation: Name,
|
|
619
|
+
input: OperationInputs[Name],
|
|
620
|
+
json: boolean,
|
|
621
|
+
formatHuman: (result: OperationOutputs[Name]) => string,
|
|
622
|
+
): Promise<number> {
|
|
623
|
+
try {
|
|
624
|
+
const result = await deps.client.call(operation, input);
|
|
625
|
+
deps.stdout(json ? JSON.stringify(result) : formatHuman(result));
|
|
626
|
+
return 0;
|
|
627
|
+
} catch (error) {
|
|
628
|
+
deps.stderr(error instanceof Error ? error.message : String(error));
|
|
629
|
+
return 1;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
115
633
|
export async function runCli(args: string[], deps: CliDependencies = DEFAULT_DEPENDENCIES): Promise<number> {
|
|
116
634
|
const [command, action, ...rest] = args;
|
|
117
635
|
if (command === "serve") { deps.serve(); return 0; }
|
|
636
|
+
if (command === "metrics") {
|
|
637
|
+
if (action === "record") {
|
|
638
|
+
const parsed = parseMetricsRecordArgs(rest);
|
|
639
|
+
if (!parsed) return usage(deps.stderr);
|
|
640
|
+
return callAndPrint(deps, "metrics.record", parsed.input, parsed.json, (row) => formatMetricsQuery([row]));
|
|
641
|
+
}
|
|
642
|
+
if (action === "query") {
|
|
643
|
+
const parsed = parseMetricsQueryArgs(rest);
|
|
644
|
+
if (!parsed) return usage(deps.stderr);
|
|
645
|
+
return callAndPrint(deps, "metrics.query", parsed.input, parsed.json, formatMetricsQuery);
|
|
646
|
+
}
|
|
647
|
+
if (action === "prune") {
|
|
648
|
+
const parsed = parseMetricsPruneArgs(rest);
|
|
649
|
+
if (!parsed) return usage(deps.stderr);
|
|
650
|
+
return callAndPrint(deps, "metrics.prune", parsed.input, parsed.json, (result) => `Pruned ${result.deleted.toLocaleString()} observation(s)`);
|
|
651
|
+
}
|
|
652
|
+
if (action === "distinct-scopes") {
|
|
653
|
+
const parsed = parseMetricsDistinctScopesArgs(rest);
|
|
654
|
+
if (!parsed) return usage(deps.stderr);
|
|
655
|
+
return callAndPrint(deps, "metrics.distinct_scopes", parsed.input, parsed.json, formatMetricsDistinctScopes);
|
|
656
|
+
}
|
|
657
|
+
if (action === "usage-series") {
|
|
658
|
+
const parsed = parseMetricsUsageSeriesArgs(rest);
|
|
659
|
+
if (!parsed) return usage(deps.stderr);
|
|
660
|
+
return callAndPrint(deps, "metrics.usage_series", parsed.input, parsed.json, formatMetricsUsageSeries);
|
|
661
|
+
}
|
|
662
|
+
if (action === "cost-by-task") {
|
|
663
|
+
const parsed = parseCostByTaskArgs(rest);
|
|
664
|
+
if (!parsed) return usage(deps.stderr);
|
|
665
|
+
return callAndPrint(deps, "metrics.cost_by_task", parsed.input, parsed.json, formatCostByTask);
|
|
666
|
+
}
|
|
667
|
+
return usage(deps.stderr);
|
|
668
|
+
}
|
|
669
|
+
if (command === "telemetry") {
|
|
670
|
+
if (action !== "poll") return usage(deps.stderr);
|
|
671
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
672
|
+
if (!parsed) return usage(deps.stderr);
|
|
673
|
+
return callAndPrint(deps, "telemetry.poll", {}, parsed.json, formatTelemetryPoll);
|
|
674
|
+
}
|
|
675
|
+
if (command === "compaction") {
|
|
676
|
+
if (action !== "estimate") return usage(deps.stderr);
|
|
677
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
678
|
+
if (!parsed) return usage(deps.stderr);
|
|
679
|
+
return callAndPrint(deps, "compaction.estimate", {}, parsed.json, formatCompactionEstimate);
|
|
680
|
+
}
|
|
681
|
+
if (command === "router") {
|
|
682
|
+
switch (action) {
|
|
683
|
+
case "status": {
|
|
684
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
685
|
+
if (!parsed) return usage(deps.stderr);
|
|
686
|
+
return callAndPrint(deps, "router.status", {}, parsed.json, formatRouterStatus);
|
|
687
|
+
}
|
|
688
|
+
case "decide": {
|
|
689
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
690
|
+
if (!parsed) return usage(deps.stderr);
|
|
691
|
+
return callAndPrint(deps, "router.decide", {}, parsed.json, formatPolicyDecision);
|
|
692
|
+
}
|
|
693
|
+
case "pause": {
|
|
694
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
695
|
+
if (!parsed) return usage(deps.stderr);
|
|
696
|
+
return callAndPrint(deps, "router.pause", {}, parsed.json, formatRouterStatus);
|
|
697
|
+
}
|
|
698
|
+
case "resume": {
|
|
699
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
700
|
+
if (!parsed) return usage(deps.stderr);
|
|
701
|
+
return callAndPrint(deps, "router.resume", {}, parsed.json, formatRouterStatus);
|
|
702
|
+
}
|
|
703
|
+
case "clear-override": {
|
|
704
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
705
|
+
if (!parsed) return usage(deps.stderr);
|
|
706
|
+
return callAndPrint(deps, "router.clear_override", {}, parsed.json, formatRouterStatus);
|
|
707
|
+
}
|
|
708
|
+
case "override": {
|
|
709
|
+
const parsed = parseRouterOverrideArgs(rest);
|
|
710
|
+
if (!parsed) return usage(deps.stderr);
|
|
711
|
+
return callAndPrint(deps, "router.override", parsed.input, parsed.json, formatRouterStatus);
|
|
712
|
+
}
|
|
713
|
+
case "current-route": {
|
|
714
|
+
const parsed = parseRouterRouteArgs(rest);
|
|
715
|
+
if (!parsed) return usage(deps.stderr);
|
|
716
|
+
return callAndPrint(deps, "router.current_route", parsed.input, parsed.json, formatRouterStatus);
|
|
717
|
+
}
|
|
718
|
+
case "available-routes": {
|
|
719
|
+
const parsed = parseRouterAvailableRoutesArgs(rest);
|
|
720
|
+
if (!parsed) return usage(deps.stderr);
|
|
721
|
+
return callAndPrint(deps, "router.available_routes", parsed.input, parsed.json, formatRouterStatus);
|
|
722
|
+
}
|
|
723
|
+
default: return usage(deps.stderr);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (command === "op") {
|
|
727
|
+
const parsed = parseOpArgs(action === undefined ? [] : [action, ...rest]);
|
|
728
|
+
if (!parsed) return usage(deps.stderr);
|
|
729
|
+
try {
|
|
730
|
+
// The escape hatch dispatches a dynamically named operation; OperationInputs/OperationOutputs
|
|
731
|
+
// are only known statically per literal operation name, so this one call site is intentionally
|
|
732
|
+
// untyped at the boundary. parseOpArgs already restricts `operation` to EXPECTED_OPERATION_NAMES.
|
|
733
|
+
const call = deps.client.call as (operation: OperationName, input: Record<string, unknown>) => Promise<unknown>;
|
|
734
|
+
const result = await call(parsed.operation, parsed.input);
|
|
735
|
+
deps.stdout(JSON.stringify(result));
|
|
736
|
+
return 0;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
deps.stderr(error instanceof Error ? error.message : String(error));
|
|
739
|
+
return 1;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (command === "benchmarks") {
|
|
743
|
+
const parsed = parseBenchmarkArgs(action, rest);
|
|
744
|
+
if (!parsed) return usage(deps.stderr);
|
|
745
|
+
try {
|
|
746
|
+
if (parsed.action === "list") {
|
|
747
|
+
const result = await deps.client.call("benchmark.query", parsed.query!);
|
|
748
|
+
deps.stdout(parsed.json ? JSON.stringify(result) : formatBenchmarkQuery(result));
|
|
749
|
+
} else if (parsed.action === "rank") {
|
|
750
|
+
const result = await deps.client.call("models.rank", parsed.recommendation!);
|
|
751
|
+
deps.stdout(parsed.json ? JSON.stringify(result) : formatModelRanking(result));
|
|
752
|
+
} else {
|
|
753
|
+
const result = parsed.action === "refresh"
|
|
754
|
+
? await deps.client.call("benchmark.refresh", { force: parsed.force })
|
|
755
|
+
: await deps.client.call("benchmark.status", {});
|
|
756
|
+
deps.stdout(parsed.json ? JSON.stringify(result) : formatBenchmarkStatus(result));
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
deps.stderr(error instanceof Error ? error.message : String(error));
|
|
761
|
+
return 1;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
118
764
|
if (command === "context") {
|
|
119
765
|
const parsed = parseContextArgs([...(action === undefined ? [] : [action]), ...rest]);
|
|
120
766
|
if (!parsed) return usage(deps.stderr);
|
|
@@ -134,6 +780,11 @@ export async function runCli(args: string[], deps: CliDependencies = DEFAULT_DEP
|
|
|
134
780
|
case "stop": deps.systemctl("stop", SYSTEMD_UNIT_NAME); return 0;
|
|
135
781
|
case "restart": deps.systemctl("restart", SYSTEMD_UNIT_NAME); return 0;
|
|
136
782
|
case "status": deps.systemctl("status", SYSTEMD_UNIT_NAME); return 0;
|
|
783
|
+
case "checkpoint": {
|
|
784
|
+
const parsed = parseJsonOnlyArgs(rest);
|
|
785
|
+
if (!parsed) return usage(deps.stderr);
|
|
786
|
+
return callAndPrint(deps, "service.checkpoint", {}, parsed.json, () => "Checkpoint complete");
|
|
787
|
+
}
|
|
137
788
|
default: return usage(deps.stderr);
|
|
138
789
|
}
|
|
139
790
|
}
|