@wrongstack/cli 1.0.19 → 1.0.26
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/data/providers.json +44 -0
- package/dist/{acp-INCNOH42.js → acp-LI665LO2.js} +18 -7
- package/dist/arg-parser.d.ts +1 -0
- package/dist/ascii-mode-SG4PRNLP.js +7 -0
- package/dist/{auth-YXK3EDUL.js → auth-TE55JF4Z.js} +68 -22
- package/dist/auth-menu/direct.d.ts +1 -0
- package/dist/auth-menu/local.d.ts +2 -0
- package/dist/auth-menu/oauth-menu.d.ts +3 -1
- package/dist/auth-menu/panel-service.d.ts +20 -0
- package/dist/{bench-YBU6BVQP.js → bench-GTLPM22B.js} +2 -2
- package/dist/boot/append-system-prompt.d.ts +10 -0
- package/dist/boot/ascii-mode.d.ts +12 -0
- package/dist/boot/config-backup-recovery.d.ts +2 -1
- package/dist/boot/container-wiring.d.ts +4 -2
- package/dist/boot/dispatch-singleshot.d.ts +4 -1
- package/dist/boot/dispatch-webui.d.ts +11 -0
- package/dist/boot/mcp-config-flag.d.ts +22 -0
- package/dist/boot/piped-stdin.d.ts +40 -0
- package/dist/boot/restricted-mode.d.ts +15 -0
- package/dist/boot/safe-mode.d.ts +30 -0
- package/dist/boot/short-circuit-desktop.d.ts +1 -0
- package/dist/boot/single-shot-budget.d.ts +30 -0
- package/dist/boot/stream-json.d.ts +36 -0
- package/dist/boot/structured-output.d.ts +16 -0
- package/dist/boot/system-prompt-builder.d.ts +2 -0
- package/dist/boot/tool-restriction-flags.d.ts +36 -0
- package/dist/boot/tui-project-picker-callback.d.ts +1 -1
- package/dist/boot/tui-settings-adapter.d.ts +15 -2
- package/dist/boot/worktree-sessions.d.ts +21 -0
- package/dist/{boot-auto-thin-MANHOYHE.js → boot-auto-thin-6Q56FY3F.js} +3 -2
- package/dist/boot-provider-selection.d.ts +67 -0
- package/dist/boot.d.ts +1 -38
- package/dist/{chunk-JY52HWL6.js → chunk-2EBVTYJR.js} +2 -2
- package/dist/{chunk-RDHFXOJM.js → chunk-2ERX3RPN.js} +112 -65
- package/dist/chunk-4TVYGG6G.js +62 -0
- package/dist/chunk-5JYDKG5B.js +154 -0
- package/dist/{chunk-KEPABQYT.js → chunk-5SEYGOCH.js} +8 -82
- package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
- package/dist/chunk-BBHWW6LR.js +549 -0
- package/dist/chunk-BEHGRNOQ.js +58 -0
- package/dist/chunk-CZTXF6HY.js +140 -0
- package/dist/{chunk-JX6KL7OF.js → chunk-EPR42SWM.js} +39 -8
- package/dist/chunk-F5DCFAKI.js +36 -0
- package/dist/chunk-HLSHFXTO.js +97 -0
- package/dist/{chunk-CJTBLTS4.js → chunk-KJS5543T.js} +4 -3
- package/dist/{chunk-DE7BZDNK.js → chunk-KOEO4CJG.js} +27 -4
- package/dist/{chunk-SIS37L3W.js → chunk-L2RVEQWO.js} +211 -193
- package/dist/chunk-LBPLTTZ6.js +105 -0
- package/dist/chunk-LDCGZJTC.js +46 -0
- package/dist/chunk-LKPEBZBE.js +143 -0
- package/dist/chunk-LUMETJFO.js +171 -0
- package/dist/{chunk-KK2LB3ZS.js → chunk-OAVBM55Q.js} +9 -5
- package/dist/chunk-PZQBGUVZ.js +18 -0
- package/dist/{chunk-C3Z4N6A6.js → chunk-Q4INH2DG.js} +51 -3
- package/dist/chunk-R4QGL6GB.js +10 -0
- package/dist/{chunk-YLQEGYV4.js → chunk-RZ4ZDRYA.js} +14 -4
- package/dist/chunk-S4FFHDJG.js +93 -0
- package/dist/chunk-SFBFYIOW.js +271 -0
- package/dist/{chunk-RMQNCHGO.js → chunk-TI7KLHYX.js} +136 -11
- package/dist/{chunk-2ENZU66X.js → chunk-WQV2NP57.js} +5 -5
- package/dist/{chunk-B4JLSXZB.js → chunk-WSTVGC2C.js} +73 -45
- package/dist/chunk-X756ZS72.js +329 -0
- package/dist/{chunk-XJ7K67PP.js → chunk-YHYINL47.js} +63 -24
- package/dist/{chunk-2VMJ3E5Z.js → chunk-YTEOAWOJ.js} +6 -2
- package/dist/{chunk-GPPRNAKW.js → chunk-ZMX5HRLI.js} +2 -2
- package/dist/{cli-context-33F74CSZ.js → cli-context-UVGAN3NO.js} +496 -384
- package/dist/cli-entry-point.d.ts +2 -0
- package/dist/{cli-main-FNGMKPBA.js → cli-main-TN3KZ2P6.js} +3561 -2724
- package/dist/cli-update-notice.d.ts +2 -1
- package/dist/config-doctor.d.ts +10 -2
- package/dist/{config-transfer-2YF3CRTN.js → config-transfer-RUWLDCNK.js} +34 -13
- package/dist/{diag-doctor-ANPRUYFM.js → diag-doctor-BIZAFIAT.js} +2 -2
- package/dist/execute-deps.d.ts +2 -1
- package/dist/{execution-A6WJLSOC.js → execution-7QIP6GRY.js} +2384 -2055
- package/dist/fleet/host-types.d.ts +3 -1
- package/dist/goal-commands.d.ts +47 -0
- package/dist/goal-host.d.ts +1 -14
- package/dist/{hq-XQWQTRDA.js → hq-GLZ7QESM.js} +277 -261
- package/dist/hq-command-controller.d.ts +1 -1
- package/dist/{hq-server-IIBZQZU4.js → hq-server-YTKDVQPY.js} +2 -2
- package/dist/{hq-service-L5INQFM4.js → hq-service-BMMJAHRT.js} +11 -4
- package/dist/import-claude-code-32PCSIQI.js +236 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -10
- package/dist/kanban-supervisor-67ECABTD.js +7 -0
- package/dist/live-settings-input.d.ts +7 -0
- package/dist/{mailbox-serve-3GFOK2I7.js → mailbox-serve-NW5VZG7R.js} +3 -3
- package/dist/{mcp-DC5WSBOO.js → mcp-LBS45HWZ.js} +4 -4
- package/dist/modeldiag-36VODVIH.js +828 -0
- package/dist/{per-subcommand-help-DHTYU524.js → per-subcommand-help-QDAQASPZ.js} +3 -3
- package/dist/permission-prompt-mirror.d.ts +1 -1
- package/dist/permission-prompt.d.ts +16 -2
- package/dist/permission-rules-view.d.ts +18 -0
- package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
- package/dist/plugin-config.d.ts +12 -0
- package/dist/plugin-management-types.d.ts +31 -0
- package/dist/plugin-management.d.ts +2 -30
- package/dist/plugin-package-install.d.ts +3 -0
- package/dist/{plugin-usage-M6KET4OZ.js → plugin-usage-CIGOOTX3.js} +6 -5
- package/dist/{plugins-WJLW6ULV.js → plugins-HQTXUD6A.js} +6 -3
- package/dist/preflight.d.ts +1 -1
- package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
- package/dist/provider-config-utils.d.ts +1 -6
- package/dist/provider-helpers.d.ts +7 -11
- package/dist/{providers-models-LFNZ5WI7.js → providers-models-5Z2PJTEW.js} +44 -34
- package/dist/release-asset-download.d.ts +13 -0
- package/dist/release-attestation.d.ts +28 -0
- package/dist/remote/remote-session.d.ts +64 -0
- package/dist/remote/remote-target.d.ts +69 -0
- package/dist/remote-O5WXHLWV.js +561 -0
- package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
- package/dist/services/commit-message.d.ts +2 -1
- package/dist/services/dispatch-classifier.d.ts +39 -5
- package/dist/session-move-P2EC2COA.js +44 -0
- package/dist/session-move.d.ts +20 -0
- package/dist/session-stats.d.ts +10 -0
- package/dist/{sessions-config-UL45XKX7.js → sessions-config-SXTTLNXA.js} +47 -9
- package/dist/settings-menu.d.ts +2 -0
- package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
- package/dist/{simpleui-dist-CXSIDAXZ.js → simpleui-dist-7BQISSEP.js} +7 -3
- package/dist/skill-suggest-L2EVWB6R.js +290 -0
- package/dist/slash-commands/command-context.d.ts +15 -5
- package/dist/slash-commands/effort.d.ts +1 -1
- package/dist/slash-commands/fallback.d.ts +1 -1
- package/dist/slash-commands/gitid.d.ts +1 -1
- package/dist/slash-commands/goal-refiner.d.ts +4 -49
- package/dist/slash-commands/jev.d.ts +4 -0
- package/dist/slash-commands/mcp.d.ts +2 -1
- package/dist/slash-commands/openai-quota.d.ts +15 -0
- package/dist/slash-commands/permissions.d.ts +4 -0
- package/dist/slash-commands/sdd-task-mutations.d.ts +4 -0
- package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
- package/dist/slash-commands/settings-keys.d.ts +2 -0
- package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
- package/dist/slash-commands/settings-reset.d.ts +6 -0
- package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
- package/dist/standalone-auto-update.d.ts +65 -0
- package/dist/standalone-update-LZO3MNLH.js +24 -0
- package/dist/standalone-update.d.ts +55 -0
- package/dist/subcommands/handlers/hq-alerts.d.ts +3 -0
- package/dist/subcommands/handlers/hq-audit.d.ts +46 -0
- package/dist/subcommands/handlers/hq-data-dir.d.ts +13 -0
- package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
- package/dist/subcommands/handlers/permissions.d.ts +9 -3
- package/dist/subcommands/handlers/remote.d.ts +6 -0
- package/dist/subcommands/handlers/skill-suggest.d.ts +21 -0
- package/dist/subcommands/handlers/typesafe-brain-replay.d.ts +26 -0
- package/dist/subcommands/handlers/typesafe-judgment-check.d.ts +3 -0
- package/dist/subcommands/handlers/typesafe-topic-replay.d.ts +19 -0
- package/dist/subcommands/handlers/typesafe-triage-replay.d.ts +18 -0
- package/dist/subcommands/handlers/typesafe.d.ts +23 -0
- package/dist/subcommands/handlers/version-help.d.ts +5 -0
- package/dist/{subcommands-XSLXMIDP.js → subcommands-Q6ORSB2E.js} +2 -2
- package/dist/tui-resume-callback.d.ts +18 -0
- package/dist/typesafe-U7YB2RRV.js +316 -0
- package/dist/typesafe-brain-replay-C5276FVR.js +222 -0
- package/dist/typesafe-judgment-check-GGYYV5UF.js +29 -0
- package/dist/typesafe-topic-replay-ZILIU3WS.js +234 -0
- package/dist/typesafe-triage-replay-FGAW7QM7.js +215 -0
- package/dist/{update-5DPB4YD4.js → update-TDZX4IL2.js} +4 -3
- package/dist/{update-check-BS3DHTSI.js → update-check-J5ZVKS73.js} +3 -2
- package/dist/update-check.d.ts +1 -1
- package/dist/version-JB2UAV2A.js +11 -0
- package/dist/version-help-XJJQ7YSH.js +10 -0
- package/dist/version.d.ts +3 -2
- package/dist/webui-client-observability.d.ts +7 -0
- package/dist/webui-server/route-contexts.d.ts +1 -1
- package/dist/webui-server/session-start-payload.d.ts +5 -1
- package/dist/{webui-server-MRPGTTKL.js → webui-server-YTHLRFD2.js} +577 -370
- package/dist/webui-server-lifecycle.d.ts +57 -0
- package/dist/webui-server-options.d.ts +11 -0
- package/dist/webui-server.d.ts +1 -1
- package/dist/wiring/brain-and-orchestration.d.ts +3 -1
- package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/domain-glossary.d.ts +5 -4
- package/dist/wiring/lifecycle-plugins.d.ts +4 -2
- package/dist/wiring/metrics.d.ts +9 -1
- package/dist/wiring/pipeline.d.ts +2 -7
- package/dist/wiring/plugins.d.ts +4 -1
- package/dist/wiring/provider-persisters.d.ts +24 -0
- package/dist/wiring/provider-runtime.d.ts +9 -1
- package/dist/wiring/provider-status.d.ts +1 -0
- package/dist/wiring/provider-utility-tools.d.ts +3 -1
- package/dist/wiring/runtime-dispatch-state.d.ts +3 -1
- package/package.json +28 -28
- package/dist/chunk-2CZEORLV.js +0 -54
- package/dist/chunk-672UXYL3.js +0 -65
- package/dist/chunk-6WRKAACA.js +0 -96
- package/dist/chunk-DR2OXU46.js +0 -174
- package/dist/chunk-JACOMTIH.js +0 -275
- package/dist/chunk-XJXDOF63.js +0 -28
- package/dist/modeldiag-CJSFHZ3J.js +0 -1356
- package/dist/short-circuit-flags-7GQIGMVD.js +0 -10
- package/dist/version-help-WXI42PZ3.js +0 -10
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyProviderOAuthRefresh
|
|
3
|
+
} from "./chunk-LDCGZJTC.js";
|
|
4
|
+
import {
|
|
5
|
+
discoverAndMergeProviders
|
|
6
|
+
} from "./chunk-D5GB5MHA.js";
|
|
7
|
+
import {
|
|
8
|
+
checkMark,
|
|
9
|
+
costLabel,
|
|
10
|
+
createProviderForId,
|
|
11
|
+
findProfile,
|
|
12
|
+
fmtMs,
|
|
13
|
+
fmtPrice,
|
|
14
|
+
fmtTokens,
|
|
15
|
+
rankModels,
|
|
16
|
+
roleCat,
|
|
17
|
+
runModeldiagEval,
|
|
18
|
+
scoreBar,
|
|
19
|
+
speedLabel
|
|
20
|
+
} from "./chunk-BBHWW6LR.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveRawProviderConnection
|
|
23
|
+
} from "./chunk-S4FFHDJG.js";
|
|
24
|
+
import {
|
|
25
|
+
mutateConfigProviders
|
|
26
|
+
} from "./chunk-LUMETJFO.js";
|
|
27
|
+
import {
|
|
28
|
+
activeProfileConfigPath
|
|
29
|
+
} from "./chunk-YMXXOOFN.js";
|
|
30
|
+
|
|
31
|
+
// src/subcommands/handlers/modeldiag.ts
|
|
32
|
+
import * as fs from "node:fs/promises";
|
|
33
|
+
import { color as color3 } from "@wrongstack/core/utils";
|
|
34
|
+
|
|
35
|
+
// src/subcommands/handlers/modeldiag-bench.ts
|
|
36
|
+
import { color, toErrorMessage } from "@wrongstack/core/utils";
|
|
37
|
+
async function runModeldiagBench(args, deps, providers, config, hasKey) {
|
|
38
|
+
const writeLine = (line = "") => {
|
|
39
|
+
deps.renderer.write(`${line}
|
|
40
|
+
`);
|
|
41
|
+
};
|
|
42
|
+
const benchArgs = args.slice(1);
|
|
43
|
+
if (benchArgs.length < 2) {
|
|
44
|
+
writeLine(
|
|
45
|
+
`${color.amber("Usage:")} wstack modeldiag bench <role> "<test prompt>" [--providers=p1,p2]`
|
|
46
|
+
);
|
|
47
|
+
writeLine();
|
|
48
|
+
writeLine(
|
|
49
|
+
color.dim(
|
|
50
|
+
'Example: wstack modeldiag bench verify "Write a function that checks if a string is a palindrome"'
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
writeLine(
|
|
54
|
+
color.dim(
|
|
55
|
+
"Tests the top 5 candidate models for the role with your prompt and reports results."
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
writeLine(color.dim("Add --providers=anthropic,google to test across multiple providers."));
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
const providersEqIdx = benchArgs.findIndex((a) => a.startsWith("--providers="));
|
|
62
|
+
let providerFilter;
|
|
63
|
+
if (providersEqIdx >= 0) {
|
|
64
|
+
const rawFilter = benchArgs[providersEqIdx]?.replace("--providers=", "").split(",");
|
|
65
|
+
if (rawFilter) {
|
|
66
|
+
providerFilter = rawFilter.map((s) => s.trim()).filter(Boolean);
|
|
67
|
+
benchArgs.splice(providersEqIdx, 1);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const rawFilter = deps.flags?.["providers"] ?? deps.flags?.["provider"];
|
|
71
|
+
if (typeof rawFilter === "string") {
|
|
72
|
+
providerFilter = rawFilter.split(",").map((s) => s.trim()).filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const benchRole = benchArgs[0];
|
|
76
|
+
if (!benchRole) {
|
|
77
|
+
writeLine(
|
|
78
|
+
`${color.amber("No benchmark role specified")}. Usage: wstack diag bench <role> [prompt]`
|
|
79
|
+
);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
const benchPrompt = benchArgs.slice(1).join(" ");
|
|
83
|
+
const cat = roleCat(benchRole);
|
|
84
|
+
const candidates = rankModels(providers, hasKey, cat, 5);
|
|
85
|
+
if (candidates.length === 0) {
|
|
86
|
+
writeLine(
|
|
87
|
+
`${color.amber("No candidate models found")} for role "${benchRole}" (category: ${cat}).`
|
|
88
|
+
);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
let targetCandidates;
|
|
92
|
+
if (providerFilter && providerFilter.length > 0) {
|
|
93
|
+
targetCandidates = candidates.filter((c) => providerFilter.includes(c.provider));
|
|
94
|
+
if (targetCandidates.length === 0) {
|
|
95
|
+
writeLine(
|
|
96
|
+
`${color.amber("No candidates match the specified providers")}: ${providerFilter.join(", ")}`
|
|
97
|
+
);
|
|
98
|
+
writeLine(
|
|
99
|
+
`Candidate providers: ${[...new Set(candidates.map((c) => c.provider))].join(", ")}`
|
|
100
|
+
);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
targetCandidates = candidates;
|
|
105
|
+
}
|
|
106
|
+
writeLine(
|
|
107
|
+
`${color.bold("Model Benchmark")} \u2014 ${color.amber(benchRole)} ${color.dim(`(category: ${cat})`)}`
|
|
108
|
+
);
|
|
109
|
+
writeLine(
|
|
110
|
+
`${color.dim("Prompt:")} "${benchPrompt.slice(0, 120)}${benchPrompt.length > 120 ? "\u2026" : ""}"`
|
|
111
|
+
);
|
|
112
|
+
writeLine();
|
|
113
|
+
writeLine(
|
|
114
|
+
` ${color.dim("# model".padEnd(52))} ${color.dim("score".padEnd(12))} ${color.dim("latency".padEnd(10))} ${color.dim("tokens".padEnd(14))} ${color.dim("first line")}`
|
|
115
|
+
);
|
|
116
|
+
writeLine(` ${color.dim("\u2500".repeat(108))}`);
|
|
117
|
+
const providerInstances = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const pid of [...new Set(targetCandidates.map((c) => c.provider))]) {
|
|
119
|
+
const prov = createProviderForId(pid, config);
|
|
120
|
+
if (prov) providerInstances.set(pid, prov);
|
|
121
|
+
}
|
|
122
|
+
let idx = 0;
|
|
123
|
+
for (const c of targetCandidates.slice(0, 20)) {
|
|
124
|
+
idx++;
|
|
125
|
+
const label = `${idx}`.padStart(2);
|
|
126
|
+
const modelKey = `${c.provider}/${c.model}`;
|
|
127
|
+
const prov = providerInstances.get(c.provider);
|
|
128
|
+
if (!prov) {
|
|
129
|
+
writeLine(
|
|
130
|
+
` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("NO PROVIDER")}`
|
|
131
|
+
);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const start = Date.now();
|
|
136
|
+
const resp = await prov.complete(
|
|
137
|
+
{
|
|
138
|
+
model: c.model,
|
|
139
|
+
messages: [{ role: "user", content: [{ type: "text", text: benchPrompt }] }],
|
|
140
|
+
maxTokens: 256
|
|
141
|
+
},
|
|
142
|
+
{ signal: AbortSignal.timeout(3e4) }
|
|
143
|
+
);
|
|
144
|
+
const latency = Date.now() - start;
|
|
145
|
+
const firstText = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
146
|
+
const firstLineClean = firstText.replace(/\n/g, " ").slice(0, 80) || color.dim("(empty)");
|
|
147
|
+
const provColor = c.provider === config.provider ? color.green : color.cyan;
|
|
148
|
+
const usage = resp.usage;
|
|
149
|
+
writeLine(
|
|
150
|
+
` ${label} ${provColor(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.amber(fmtMs(latency).padEnd(8))} ${color.dim(`in${usage?.input ?? "?"}/out${usage?.output ?? "?"}`.padEnd(12))} ${firstLineClean}`
|
|
151
|
+
);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const errMsg = toErrorMessage(err);
|
|
154
|
+
writeLine(
|
|
155
|
+
` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("FAILED")} ${color.dim(errMsg.slice(0, 40))}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const testedProviders = [...new Set(targetCandidates.map((c) => c.provider))];
|
|
160
|
+
writeLine();
|
|
161
|
+
writeLine(
|
|
162
|
+
color.dim(
|
|
163
|
+
`Tested ${idx} model(s) across ${testedProviders.length} provider(s): ${testedProviders.join(", ")}.`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
writeLine(color.dim("Pin the best: wstack setmodel set <role> <provider>/<model>"));
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/subcommands/handlers/modeldiag-test.ts
|
|
171
|
+
import { color as color2, toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
172
|
+
import {
|
|
173
|
+
buildProviderFactoriesFromRegistry,
|
|
174
|
+
makeProviderFromConfig,
|
|
175
|
+
setOAuthTokenPersister
|
|
176
|
+
} from "@wrongstack/providers";
|
|
177
|
+
|
|
178
|
+
// src/subcommands/handlers/model-smoke-test.ts
|
|
179
|
+
import { ProviderError } from "@wrongstack/core/types";
|
|
180
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
181
|
+
var DEFAULT_TIMEOUT_MS = 45e3;
|
|
182
|
+
var DEFAULT_MAX_TOKENS = 32;
|
|
183
|
+
function csv(value) {
|
|
184
|
+
if (typeof value !== "string") return void 0;
|
|
185
|
+
const values = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
186
|
+
return values.length > 0 ? values : void 0;
|
|
187
|
+
}
|
|
188
|
+
function positiveInteger(value, fallback, label) {
|
|
189
|
+
if (value === void 0) return fallback;
|
|
190
|
+
const parsed = typeof value === "string" ? Number(value) : Number.NaN;
|
|
191
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
192
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
function parseModelSmokeOptions(args, flags = {}) {
|
|
197
|
+
const local = /* @__PURE__ */ new Map();
|
|
198
|
+
for (let i = 0; i < args.length; i++) {
|
|
199
|
+
const arg = args[i];
|
|
200
|
+
if (!arg?.startsWith("--")) continue;
|
|
201
|
+
const eq = arg.indexOf("=");
|
|
202
|
+
if (eq >= 0) {
|
|
203
|
+
local.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const name = arg.slice(2);
|
|
207
|
+
const next = args[i + 1];
|
|
208
|
+
if (next && !next.startsWith("--")) {
|
|
209
|
+
local.set(name, next);
|
|
210
|
+
i++;
|
|
211
|
+
} else {
|
|
212
|
+
local.set(name, true);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const get = (name) => local.get(name) ?? flags[name];
|
|
216
|
+
const providerFilter = csv(get("providers") ?? get("provider"));
|
|
217
|
+
const modelFilter = csv(get("models") ?? get("model"));
|
|
218
|
+
return {
|
|
219
|
+
allModels: get("all-models") === true || get("all-models") === "true",
|
|
220
|
+
planOnly: get("plan") === true || get("plan") === "true",
|
|
221
|
+
json: get("json") === true || get("json") === "true",
|
|
222
|
+
timeoutMs: positiveInteger(get("timeout"), DEFAULT_TIMEOUT_MS, "--timeout"),
|
|
223
|
+
maxTokens: positiveInteger(get("max-tokens"), DEFAULT_MAX_TOKENS, "--max-tokens"),
|
|
224
|
+
...providerFilter ? { providerFilter } : {},
|
|
225
|
+
...modelFilter ? { modelFilter } : {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function pushUnique(target, values) {
|
|
229
|
+
const seen = new Set(target);
|
|
230
|
+
for (const value of values) {
|
|
231
|
+
const normalized = value?.trim();
|
|
232
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
233
|
+
seen.add(normalized);
|
|
234
|
+
target.push(normalized);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function catalogModelsFor(providerId, providerConfig, registry) {
|
|
238
|
+
const direct = await registry.getProvider(providerId).catch(() => void 0);
|
|
239
|
+
const inherited = direct ?? (providerConfig?.type && providerConfig.type !== providerId ? await registry.getProvider(providerConfig.type).catch(() => void 0) : void 0);
|
|
240
|
+
return inherited?.models ?? [];
|
|
241
|
+
}
|
|
242
|
+
function supportsTextCompletion(model) {
|
|
243
|
+
const outputs = model?.modalities?.output;
|
|
244
|
+
return !outputs?.length || outputs.includes("text");
|
|
245
|
+
}
|
|
246
|
+
async function buildModelSmokeTargets(config, registry, options) {
|
|
247
|
+
const providerIds = Object.keys(config.providers ?? {});
|
|
248
|
+
if (config.provider && !providerIds.includes(config.provider)) providerIds.push(config.provider);
|
|
249
|
+
const providerFilter = options.providerFilter ? new Set(options.providerFilter.map((id) => id.toLowerCase())) : void 0;
|
|
250
|
+
const modelFilter = options.modelFilter ? new Set(options.modelFilter) : void 0;
|
|
251
|
+
const targets = [];
|
|
252
|
+
for (const providerId of providerIds) {
|
|
253
|
+
if (providerFilter && !providerFilter.has(providerId.toLowerCase())) continue;
|
|
254
|
+
const saved = config.providers?.[providerId];
|
|
255
|
+
const catalogModels = await catalogModelsFor(providerId, saved, registry);
|
|
256
|
+
const catalogById = new Map(catalogModels.map((model) => [model.id, model]));
|
|
257
|
+
const textCatalogModels = catalogModels.filter(supportsTextCompletion);
|
|
258
|
+
const candidates = [];
|
|
259
|
+
if (options.allModels) {
|
|
260
|
+
pushUnique(candidates, saved?.models ?? []);
|
|
261
|
+
pushUnique(candidates, Object.keys(saved?.customModels ?? {}));
|
|
262
|
+
pushUnique(
|
|
263
|
+
candidates,
|
|
264
|
+
textCatalogModels.map((model) => model.id)
|
|
265
|
+
);
|
|
266
|
+
if (providerId === config.provider) pushUnique(candidates, [config.model]);
|
|
267
|
+
pushUnique(candidates, [saved?.model]);
|
|
268
|
+
} else {
|
|
269
|
+
const representative = (providerId === config.provider ? config.model : void 0) ?? saved?.model ?? saved?.models?.find(Boolean) ?? Object.keys(saved?.customModels ?? {})[0] ?? await registry.suggestModel(providerId).catch(() => void 0) ?? textCatalogModels[0]?.id;
|
|
270
|
+
pushUnique(candidates, [representative]);
|
|
271
|
+
}
|
|
272
|
+
for (const modelId of candidates) {
|
|
273
|
+
if (modelFilter && !modelFilter.has(modelId)) continue;
|
|
274
|
+
if (!supportsTextCompletion(catalogById.get(modelId))) continue;
|
|
275
|
+
targets.push({ providerId, modelId });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return targets;
|
|
279
|
+
}
|
|
280
|
+
function classifyFailure(err) {
|
|
281
|
+
if (err instanceof ProviderError || ProviderError.isProviderError(err)) {
|
|
282
|
+
const providerError = err;
|
|
283
|
+
return {
|
|
284
|
+
error: providerError.describe(),
|
|
285
|
+
errorKind: providerError.kind,
|
|
286
|
+
httpStatus: providerError.status
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const isTimeout = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError") || /timed?\s*out/i.test(toErrorMessage2(err));
|
|
290
|
+
return {
|
|
291
|
+
error: toErrorMessage2(err),
|
|
292
|
+
errorKind: isTimeout ? "timeout" : "unknown"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function runModelSmokeTests(params) {
|
|
296
|
+
const results = [];
|
|
297
|
+
let providerId;
|
|
298
|
+
let provider;
|
|
299
|
+
let providerError;
|
|
300
|
+
for (let index = 0; index < params.targets.length; index++) {
|
|
301
|
+
const target = params.targets[index];
|
|
302
|
+
params.onTargetStart?.(target, index, params.targets.length);
|
|
303
|
+
const started = Date.now();
|
|
304
|
+
if (target.providerId !== providerId) {
|
|
305
|
+
providerId = target.providerId;
|
|
306
|
+
provider = void 0;
|
|
307
|
+
providerError = void 0;
|
|
308
|
+
try {
|
|
309
|
+
provider = await params.createProvider(target.providerId);
|
|
310
|
+
} catch (err) {
|
|
311
|
+
providerError = err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!provider) {
|
|
315
|
+
const failure = classifyFailure(providerError ?? new Error("Provider could not be created."));
|
|
316
|
+
const result = {
|
|
317
|
+
providerId: target.providerId,
|
|
318
|
+
modelId: target.modelId,
|
|
319
|
+
status: "failed",
|
|
320
|
+
latencyMs: Date.now() - started,
|
|
321
|
+
...failure
|
|
322
|
+
};
|
|
323
|
+
results.push(result);
|
|
324
|
+
params.onTargetComplete?.(result, index, params.targets.length);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const response = await provider.complete(
|
|
329
|
+
{
|
|
330
|
+
model: target.modelId,
|
|
331
|
+
messages: [
|
|
332
|
+
{
|
|
333
|
+
role: "user",
|
|
334
|
+
content: [{ type: "text", text: "Reply with exactly: OK" }]
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
maxTokens: params.options.maxTokens
|
|
338
|
+
},
|
|
339
|
+
{ signal: AbortSignal.timeout(params.options.timeoutMs) }
|
|
340
|
+
);
|
|
341
|
+
const result = {
|
|
342
|
+
providerId: target.providerId,
|
|
343
|
+
modelId: target.modelId,
|
|
344
|
+
status: "passed",
|
|
345
|
+
latencyMs: Date.now() - started,
|
|
346
|
+
stopReason: response.stopReason,
|
|
347
|
+
usage: response.usage
|
|
348
|
+
};
|
|
349
|
+
results.push(result);
|
|
350
|
+
params.onTargetComplete?.(result, index, params.targets.length);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
const result = {
|
|
353
|
+
providerId: target.providerId,
|
|
354
|
+
modelId: target.modelId,
|
|
355
|
+
status: "failed",
|
|
356
|
+
latencyMs: Date.now() - started,
|
|
357
|
+
...classifyFailure(err)
|
|
358
|
+
};
|
|
359
|
+
results.push(result);
|
|
360
|
+
params.onTargetComplete?.(result, index, params.targets.length);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return results;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/subcommands/handlers/modeldiag-test.ts
|
|
367
|
+
async function createModelDiagSmokeProvider(params) {
|
|
368
|
+
const { providerId, config, modelsRegistry, providerFactories } = params;
|
|
369
|
+
const saved = config.providers?.[providerId];
|
|
370
|
+
const connection = resolveRawProviderConnection(config, providerId);
|
|
371
|
+
const factoryType = saved?.type ?? providerId;
|
|
372
|
+
const resolved = await modelsRegistry.getProvider(providerId).catch(() => void 0) ?? (factoryType !== providerId ? await modelsRegistry.getProvider(factoryType).catch(() => void 0) : void 0);
|
|
373
|
+
const providerConfig = {
|
|
374
|
+
...saved,
|
|
375
|
+
...connection.apiKey ? { apiKey: connection.apiKey } : {},
|
|
376
|
+
// Keep the user-visible alias on the factory input. The selected factory
|
|
377
|
+
// already captures the canonical models.dev provider and its per-model
|
|
378
|
+
// wire metadata.
|
|
379
|
+
type: providerId,
|
|
380
|
+
...saved?.family ?? resolved?.family ? { family: saved?.family ?? resolved?.family } : {},
|
|
381
|
+
...connection.baseUrl ?? resolved?.apiBase ? { baseUrl: connection.baseUrl ?? resolved?.apiBase } : {},
|
|
382
|
+
...saved?.envVars ?? resolved?.envVars ? { envVars: saved?.envVars ?? resolved?.envVars } : {}
|
|
383
|
+
};
|
|
384
|
+
const factory = providerFactories.get(factoryType);
|
|
385
|
+
return factory ? factory.create(providerConfig) : makeProviderFromConfig(providerId, { ...providerConfig, type: factoryType });
|
|
386
|
+
}
|
|
387
|
+
async function runModeldiagTest(args, deps, config) {
|
|
388
|
+
const writeLine = (line = "") => {
|
|
389
|
+
deps.renderer.write(`${line}
|
|
390
|
+
`);
|
|
391
|
+
};
|
|
392
|
+
if (args.slice(1).includes("--help") || deps.flags?.["help"] === true) {
|
|
393
|
+
writeLine(`${color2.bold("Usage:")} wstack modeldiag test [options]`);
|
|
394
|
+
writeLine();
|
|
395
|
+
writeLine(" (no flags) Probe one representative model per configured provider");
|
|
396
|
+
writeLine(" --all-models Probe every catalog-visible model sequentially");
|
|
397
|
+
writeLine(" --plan Show targets without making model calls");
|
|
398
|
+
writeLine(" --provider=<id,...> Restrict provider ids");
|
|
399
|
+
writeLine(" --model=<id,...> Restrict model ids");
|
|
400
|
+
writeLine(" --timeout=<ms> Per-model timeout (default 45000)");
|
|
401
|
+
writeLine(" --max-tokens=<n> Output cap for the tiny probe (default 32)");
|
|
402
|
+
writeLine(" --json Emit machine-readable output");
|
|
403
|
+
writeLine(" --yes Confirm a run larger than 50 requests");
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
406
|
+
let smokeOptions;
|
|
407
|
+
try {
|
|
408
|
+
smokeOptions = parseModelSmokeOptions(args.slice(1), deps.flags);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
writeLine(`${color2.red("Invalid smoke-test options:")} ${toErrorMessage3(err)}`);
|
|
411
|
+
return 2;
|
|
412
|
+
}
|
|
413
|
+
if ("mergeOverlay" in deps.modelsRegistry) {
|
|
414
|
+
await discoverAndMergeProviders({
|
|
415
|
+
config,
|
|
416
|
+
registry: deps.modelsRegistry,
|
|
417
|
+
cacheDir: deps.paths.cacheDir
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const targets = await buildModelSmokeTargets(
|
|
421
|
+
config,
|
|
422
|
+
deps.modelsRegistry,
|
|
423
|
+
smokeOptions
|
|
424
|
+
);
|
|
425
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
426
|
+
for (const target of targets) {
|
|
427
|
+
byProvider.set(target.providerId, (byProvider.get(target.providerId) ?? 0) + 1);
|
|
428
|
+
}
|
|
429
|
+
if (smokeOptions.planOnly) {
|
|
430
|
+
const plan = {
|
|
431
|
+
mode: smokeOptions.allModels ? "all-models" : "representative",
|
|
432
|
+
targetCount: targets.length,
|
|
433
|
+
providers: Object.fromEntries(byProvider),
|
|
434
|
+
targets
|
|
435
|
+
};
|
|
436
|
+
if (smokeOptions.json) {
|
|
437
|
+
writeLine(JSON.stringify(plan, null, 2));
|
|
438
|
+
} else {
|
|
439
|
+
writeLine(`${color2.bold("Provider/Model Smoke Test Plan")}`);
|
|
440
|
+
writeLine();
|
|
441
|
+
for (const [providerId, count] of byProvider) {
|
|
442
|
+
writeLine(` ${color2.cyan(providerId.padEnd(24))} ${count} model(s)`);
|
|
443
|
+
}
|
|
444
|
+
writeLine();
|
|
445
|
+
writeLine(
|
|
446
|
+
color2.dim(`${targets.length} request(s) would run sequentially. Remove --plan to execute.`)
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return targets.length > 0 ? 0 : 2;
|
|
450
|
+
}
|
|
451
|
+
if (targets.length === 0) {
|
|
452
|
+
const message = "No provider/model targets matched. Check the active profile or remove filters.";
|
|
453
|
+
if (smokeOptions.json) {
|
|
454
|
+
writeLine(JSON.stringify({ results: [], summary: { passed: 0, failed: 0 }, error: message }));
|
|
455
|
+
} else {
|
|
456
|
+
writeLine(color2.amber(message));
|
|
457
|
+
}
|
|
458
|
+
return 2;
|
|
459
|
+
}
|
|
460
|
+
const confirmed = deps.flags?.["yes"] === true || deps.flags?.["yes"] === "true" || args.slice(1).includes("--yes");
|
|
461
|
+
if (targets.length > 50 && !confirmed) {
|
|
462
|
+
const message = `${targets.length} live requests are planned. Review with --plan, then add --yes to run them.`;
|
|
463
|
+
if (smokeOptions.json) {
|
|
464
|
+
writeLine(JSON.stringify({ error: message, targetCount: targets.length }));
|
|
465
|
+
} else {
|
|
466
|
+
writeLine(color2.amber(message));
|
|
467
|
+
}
|
|
468
|
+
return 2;
|
|
469
|
+
}
|
|
470
|
+
if (!smokeOptions.json) {
|
|
471
|
+
writeLine(`${color2.bold("Live Provider/Model Smoke Test")}`);
|
|
472
|
+
writeLine(
|
|
473
|
+
color2.dim(
|
|
474
|
+
`${targets.length} request(s), sequential, ${smokeOptions.timeoutMs}ms timeout, max ${smokeOptions.maxTokens} output tokens.`
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
writeLine();
|
|
478
|
+
}
|
|
479
|
+
const resultLines = (result, index, total) => {
|
|
480
|
+
if (smokeOptions.json) return;
|
|
481
|
+
const label = `[${String(index + 1).padStart(String(total).length)}/${total}]`;
|
|
482
|
+
const target = `${result.providerId}/${result.modelId}`;
|
|
483
|
+
if (result.status === "passed") {
|
|
484
|
+
writeLine(
|
|
485
|
+
` ${color2.green("\u2713")} ${color2.dim(label)} ${color2.cyan(target)} ${color2.dim(
|
|
486
|
+
`${fmtMs(result.latencyMs)} \xB7 in${result.usage.input}/out${result.usage.output} \xB7 ${result.stopReason}`
|
|
487
|
+
)}`
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
const status = result.httpStatus ? `HTTP ${result.httpStatus} \xB7 ` : "";
|
|
491
|
+
writeLine(
|
|
492
|
+
` ${color2.red("\u2717")} ${color2.dim(label)} ${color2.red(target)} ${color2.dim(
|
|
493
|
+
`${fmtMs(result.latencyMs)} \xB7 ${status}${result.errorKind}`
|
|
494
|
+
)}`
|
|
495
|
+
);
|
|
496
|
+
writeLine(` ${color2.red(result.error.slice(0, 240))}`);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
let oauthWriteChain = Promise.resolve();
|
|
500
|
+
let oauthWriteScheduled = false;
|
|
501
|
+
const profilePath = activeProfileConfigPath(deps.paths, config);
|
|
502
|
+
setOAuthTokenPersister((providerId, credentials, source) => {
|
|
503
|
+
oauthWriteScheduled = true;
|
|
504
|
+
oauthWriteChain = oauthWriteChain.then(
|
|
505
|
+
() => mutateConfigProviders(profilePath, deps.vault, (all) => {
|
|
506
|
+
const providerConfig = all[providerId];
|
|
507
|
+
if (!providerConfig) return;
|
|
508
|
+
applyProviderOAuthRefresh(providerConfig, credentials, source);
|
|
509
|
+
})
|
|
510
|
+
);
|
|
511
|
+
});
|
|
512
|
+
let results;
|
|
513
|
+
try {
|
|
514
|
+
const providerFactories = new Map(
|
|
515
|
+
(await buildProviderFactoriesFromRegistry({ registry: deps.modelsRegistry })).map(
|
|
516
|
+
(factory) => [factory.type, factory]
|
|
517
|
+
)
|
|
518
|
+
);
|
|
519
|
+
results = await runModelSmokeTests({
|
|
520
|
+
targets,
|
|
521
|
+
options: smokeOptions,
|
|
522
|
+
createProvider: (providerId) => createModelDiagSmokeProvider({
|
|
523
|
+
providerId,
|
|
524
|
+
config,
|
|
525
|
+
modelsRegistry: deps.modelsRegistry,
|
|
526
|
+
providerFactories
|
|
527
|
+
}),
|
|
528
|
+
onTargetComplete: resultLines
|
|
529
|
+
});
|
|
530
|
+
} finally {
|
|
531
|
+
setOAuthTokenPersister(void 0);
|
|
532
|
+
}
|
|
533
|
+
let oauthPersistenceError;
|
|
534
|
+
if (oauthWriteScheduled) {
|
|
535
|
+
try {
|
|
536
|
+
await oauthWriteChain;
|
|
537
|
+
} catch (err) {
|
|
538
|
+
oauthPersistenceError = toErrorMessage3(err);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const passed = results.filter((result) => result.status === "passed").length;
|
|
542
|
+
const failed = results.length - passed;
|
|
543
|
+
const summary = { passed, failed, total: results.length };
|
|
544
|
+
if (smokeOptions.json) {
|
|
545
|
+
writeLine(
|
|
546
|
+
JSON.stringify(
|
|
547
|
+
{
|
|
548
|
+
results,
|
|
549
|
+
summary,
|
|
550
|
+
...oauthPersistenceError ? {
|
|
551
|
+
warning: `OAuth token refresh worked in-memory but could not be saved: ${oauthPersistenceError}`
|
|
552
|
+
} : {}
|
|
553
|
+
},
|
|
554
|
+
null,
|
|
555
|
+
2
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
writeLine();
|
|
560
|
+
writeLine(
|
|
561
|
+
`${color2.bold("Summary:")} ${color2.green(`${passed} passed`)} \xB7 ${failed > 0 ? color2.red(`${failed} failed`) : color2.dim("0 failed")}`
|
|
562
|
+
);
|
|
563
|
+
if (!smokeOptions.allModels) {
|
|
564
|
+
writeLine(color2.dim("Use --all-models to test every catalog-visible model sequentially."));
|
|
565
|
+
}
|
|
566
|
+
if (oauthPersistenceError) {
|
|
567
|
+
writeLine(
|
|
568
|
+
color2.amber(
|
|
569
|
+
`OAuth token refresh worked in-memory but could not be saved: ${oauthPersistenceError}`
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return failed === 0 ? 0 : 1;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/subcommands/handlers/modeldiag.ts
|
|
578
|
+
async function readProviders(cachePath) {
|
|
579
|
+
if (!cachePath) {
|
|
580
|
+
return { ok: false, message: `${color3.red("Models cache not available")}.`, hard: true };
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const raw = await fs.readFile(cachePath, "utf8");
|
|
584
|
+
const parsed = JSON.parse(raw);
|
|
585
|
+
const payload = parsed.payload ?? parsed;
|
|
586
|
+
return {
|
|
587
|
+
ok: true,
|
|
588
|
+
providers: Object.entries(payload).map(([id, p]) => ({
|
|
589
|
+
id: p.id ?? id,
|
|
590
|
+
name: p.name ?? id,
|
|
591
|
+
family: p.npm ?? id,
|
|
592
|
+
models: Object.values(p.models ?? {}).map(
|
|
593
|
+
(m) => ({
|
|
594
|
+
id: m.id,
|
|
595
|
+
name: m.name,
|
|
596
|
+
capabilities: {
|
|
597
|
+
contextWindow: m.limit?.context,
|
|
598
|
+
maxOutputTokens: m.limit?.output
|
|
599
|
+
},
|
|
600
|
+
pricing: m.cost
|
|
601
|
+
})
|
|
602
|
+
)
|
|
603
|
+
}))
|
|
604
|
+
};
|
|
605
|
+
} catch {
|
|
606
|
+
return {
|
|
607
|
+
ok: false,
|
|
608
|
+
message: `${color3.amber("Models cache not available")}. Run wstack sync-models.`,
|
|
609
|
+
hard: false
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function checkHasKey(pid, config) {
|
|
614
|
+
if (pid === config.provider && config.provider) return true;
|
|
615
|
+
const pc = config.providers?.[pid];
|
|
616
|
+
if (!pc) return false;
|
|
617
|
+
if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
|
|
618
|
+
if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
var modeldiagCmd = async (args, deps) => {
|
|
622
|
+
const sub = args[0]?.toLowerCase() || "full";
|
|
623
|
+
const cacheResult = await readProviders(deps.paths.modelsCache);
|
|
624
|
+
if (!cacheResult.ok) {
|
|
625
|
+
deps.renderer.write(`${cacheResult.message}
|
|
626
|
+
`);
|
|
627
|
+
return cacheResult.hard ? 1 : 0;
|
|
628
|
+
}
|
|
629
|
+
const providers = cacheResult.providers;
|
|
630
|
+
const config = deps.config;
|
|
631
|
+
const modelMatrix = config.modelMatrix ?? {};
|
|
632
|
+
function hasKey(pid) {
|
|
633
|
+
return checkHasKey(pid, config);
|
|
634
|
+
}
|
|
635
|
+
function writeLine(line = "") {
|
|
636
|
+
deps.renderer.write(`${line}
|
|
637
|
+
`);
|
|
638
|
+
}
|
|
639
|
+
if (sub === "keys") {
|
|
640
|
+
writeLine(`${color3.bold("API Key Status")}`);
|
|
641
|
+
writeLine();
|
|
642
|
+
for (const prov of providers) {
|
|
643
|
+
const k = hasKey(prov.id);
|
|
644
|
+
writeLine(` ${checkMark(k)} ${color3.bold(prov.id.padEnd(18))} ${color3.dim(prov.name)}`);
|
|
645
|
+
}
|
|
646
|
+
writeLine();
|
|
647
|
+
writeLine(`${color3.dim(`Leader: ${config.provider}/${config.model}`)}`);
|
|
648
|
+
return 0;
|
|
649
|
+
}
|
|
650
|
+
if (sub === "caps") {
|
|
651
|
+
writeLine(`${color3.bold("Model Capabilities")} ${color3.dim("\u2014 matched to known profiles")}`);
|
|
652
|
+
writeLine();
|
|
653
|
+
for (const prov of providers) {
|
|
654
|
+
if (!hasKey(prov.id)) continue;
|
|
655
|
+
writeLine(` ${color3.bold(prov.id)} ${color3.dim(`(${prov.name})`)}`);
|
|
656
|
+
const tiers = {
|
|
657
|
+
premium: [],
|
|
658
|
+
standard: [],
|
|
659
|
+
budget: [],
|
|
660
|
+
unknown: []
|
|
661
|
+
};
|
|
662
|
+
for (const m of prov.models ?? []) {
|
|
663
|
+
const profile = findProfile(prov.id, m.id);
|
|
664
|
+
tiers[profile?.costTier ?? "unknown"]?.push(m);
|
|
665
|
+
}
|
|
666
|
+
for (const tier of ["premium", "standard", "budget", "unknown"]) {
|
|
667
|
+
const tierModels = tiers[tier];
|
|
668
|
+
if (tierModels.length === 0) continue;
|
|
669
|
+
const label = tier === "unknown" ? color3.dim("unmatched") : `${costLabel(tier)} ${tier}`;
|
|
670
|
+
writeLine(` ${label}`);
|
|
671
|
+
for (const m of tierModels) {
|
|
672
|
+
const cap = m.capabilities;
|
|
673
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
674
|
+
const maxOut = cap?.maxOutputTokens ?? 0;
|
|
675
|
+
const profile = findProfile(prov.id, m.id);
|
|
676
|
+
const family = profile ? `${speedLabel(profile.speedTier)} ${color3.green(profile.family)}` : color3.dim("no profile match");
|
|
677
|
+
const pricing = m.pricing ? `${color3.dim("in")}${fmtPrice(m.pricing.input)} ${color3.dim("out")}${fmtPrice(m.pricing.output)}` : color3.dim("pricing ?");
|
|
678
|
+
writeLine(
|
|
679
|
+
` ${color3.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens(ctx).padEnd(6)}` : color3.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
writeLine();
|
|
684
|
+
}
|
|
685
|
+
writeLine(
|
|
686
|
+
color3.dim(
|
|
687
|
+
"Prices in USD per 1M tokens (input/output). ctx = context window, out = max output."
|
|
688
|
+
)
|
|
689
|
+
);
|
|
690
|
+
return 0;
|
|
691
|
+
}
|
|
692
|
+
async function renderSuggest() {
|
|
693
|
+
writeLine();
|
|
694
|
+
writeLine(
|
|
695
|
+
`${color3.bold("Agent \u2192 Model Suggestions")} ${color3.amber("(heuristic \u2014 untested)")}`
|
|
696
|
+
);
|
|
697
|
+
writeLine(
|
|
698
|
+
color3.dim(
|
|
699
|
+
'These are profile-based best guesses. Test them with wstack modeldiag bench <role> "<prompt>".'
|
|
700
|
+
)
|
|
701
|
+
);
|
|
702
|
+
writeLine();
|
|
703
|
+
const keyedProviders = providers.filter((p) => hasKey(p.id));
|
|
704
|
+
if (keyedProviders.length === 0) {
|
|
705
|
+
writeLine(
|
|
706
|
+
` ${color3.amber("No providers have API keys configured. Add keys with wstack auth.")}`
|
|
707
|
+
);
|
|
708
|
+
} else {
|
|
709
|
+
const roles = [
|
|
710
|
+
"security-scanner",
|
|
711
|
+
"bug-hunter",
|
|
712
|
+
"planner",
|
|
713
|
+
"architect",
|
|
714
|
+
"refactor-planner",
|
|
715
|
+
"verifier",
|
|
716
|
+
"test",
|
|
717
|
+
"document",
|
|
718
|
+
"reviewer",
|
|
719
|
+
"code-reviewer",
|
|
720
|
+
"executor",
|
|
721
|
+
"debugger"
|
|
722
|
+
];
|
|
723
|
+
for (const role of roles) {
|
|
724
|
+
if (modelMatrix[role]) {
|
|
725
|
+
const entry = modelMatrix[role];
|
|
726
|
+
const p = entry.provider ?? config.provider;
|
|
727
|
+
writeLine(
|
|
728
|
+
` ${color3.dim(role.padEnd(20))} \u2192 ${color3.cyan(`${p}/${entry.model}`)} ${color3.dim("(user-configured)")}`
|
|
729
|
+
);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const cat = roleCat(role);
|
|
733
|
+
const ranked = rankModels(providers, hasKey, cat, 3);
|
|
734
|
+
if (ranked.length === 0) {
|
|
735
|
+
writeLine(` ${color3.dim(role.padEnd(20))} \u2192 ${color3.dim("no candidates")}`);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const best = ranked[0];
|
|
739
|
+
const family = best.profile ? ` ${color3.dim(`(${best.profile.family})`)}` : "";
|
|
740
|
+
const bar = scoreBar(best.score, 110);
|
|
741
|
+
writeLine(
|
|
742
|
+
` ${color3.amber(role.padEnd(20))} \u2192 ${color3.cyan(`${best.provider}/${best.model}`)}${family}`
|
|
743
|
+
);
|
|
744
|
+
writeLine(` ${" ".repeat(22)} ${bar} ${color3.dim(cat)}`);
|
|
745
|
+
if (ranked.length > 1 && (ranked[1]?.score ?? Number.NEGATIVE_INFINITY) >= best.score - 15) {
|
|
746
|
+
for (const alt of ranked.slice(1)) {
|
|
747
|
+
const af = alt.profile ? ` (${alt.profile.family})` : "";
|
|
748
|
+
writeLine(
|
|
749
|
+
` ${" ".repeat(22)} ${color3.dim(`${alt.provider}/${alt.model}${af} score ${alt.score}`)}`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
writeLine();
|
|
755
|
+
writeLine(
|
|
756
|
+
` ${color3.bold("leader".padEnd(20))} \u2192 ${color3.cyan(`${config.provider}/${config.model}`)}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (sub === "suggest") {
|
|
761
|
+
await renderSuggest();
|
|
762
|
+
writeLine();
|
|
763
|
+
writeLine(color3.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
|
|
764
|
+
writeLine(color3.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
if (sub === "test") {
|
|
768
|
+
return runModeldiagTest(args, deps, config);
|
|
769
|
+
}
|
|
770
|
+
if (sub === "bench") {
|
|
771
|
+
return runModeldiagBench(args, deps, providers, config, hasKey);
|
|
772
|
+
}
|
|
773
|
+
if (sub === "eval" || sub === "evall") {
|
|
774
|
+
return runModeldiagEval(args, deps, providers, config, hasKey);
|
|
775
|
+
}
|
|
776
|
+
writeLine(`${color3.bold("API Key Status")}`);
|
|
777
|
+
writeLine();
|
|
778
|
+
for (const prov of providers) {
|
|
779
|
+
const k = hasKey(prov.id);
|
|
780
|
+
writeLine(` ${checkMark(k)} ${color3.bold(prov.id.padEnd(18))} ${color3.dim(prov.name)}`);
|
|
781
|
+
}
|
|
782
|
+
writeLine();
|
|
783
|
+
writeLine(`${color3.dim(`Leader: ${config.provider}/${config.model}`)}`);
|
|
784
|
+
writeLine();
|
|
785
|
+
writeLine(`${color3.bold("Model Capabilities")} ${color3.dim("\u2014 matched to known profiles")}`);
|
|
786
|
+
writeLine();
|
|
787
|
+
for (const prov of providers) {
|
|
788
|
+
if (!hasKey(prov.id)) continue;
|
|
789
|
+
writeLine(` ${color3.bold(prov.id)} ${color3.dim(`(${prov.name})`)}`);
|
|
790
|
+
const tiers = {
|
|
791
|
+
premium: [],
|
|
792
|
+
standard: [],
|
|
793
|
+
budget: [],
|
|
794
|
+
unknown: []
|
|
795
|
+
};
|
|
796
|
+
for (const m of prov.models ?? []) {
|
|
797
|
+
const profile = findProfile(prov.id, m.id);
|
|
798
|
+
tiers[profile?.costTier ?? "unknown"]?.push(m);
|
|
799
|
+
}
|
|
800
|
+
for (const tier of ["premium", "standard", "budget", "unknown"]) {
|
|
801
|
+
const tierModels = tiers[tier];
|
|
802
|
+
if (tierModels.length === 0) continue;
|
|
803
|
+
const label = tier === "unknown" ? color3.dim("unmatched") : `${costLabel(tier)} ${tier}`;
|
|
804
|
+
writeLine(` ${label}`);
|
|
805
|
+
for (const m of tierModels) {
|
|
806
|
+
const cap = m.capabilities;
|
|
807
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
808
|
+
const maxOut = cap?.maxOutputTokens ?? 0;
|
|
809
|
+
const profile = findProfile(prov.id, m.id);
|
|
810
|
+
const family = profile ? `${speedLabel(profile.speedTier)} ${color3.green(profile.family)}` : color3.dim("no profile match");
|
|
811
|
+
const pricing = m.pricing ? `${color3.dim("in")}${fmtPrice(m.pricing.input)} ${color3.dim("out")}${fmtPrice(m.pricing.output)}` : color3.dim("pricing ?");
|
|
812
|
+
writeLine(
|
|
813
|
+
` ${color3.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens(ctx).padEnd(6)}` : color3.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
writeLine();
|
|
818
|
+
}
|
|
819
|
+
await renderSuggest();
|
|
820
|
+
writeLine();
|
|
821
|
+
writeLine(color3.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
|
|
822
|
+
writeLine(color3.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
|
|
823
|
+
return 0;
|
|
824
|
+
};
|
|
825
|
+
export {
|
|
826
|
+
modeldiagCmd
|
|
827
|
+
};
|
|
828
|
+
//# sourceMappingURL=modeldiag-36VODVIH.js.map
|