gsd-pi 2.17.0 → 2.19.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 +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +399 -29
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +382 -23
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/dist/resources/extensions/gsd/files.ts +123 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +237 -4
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/metrics.ts +48 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +132 -1
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/dist/resources/extensions/remote-questions/format.ts +12 -6
- package/dist/resources/extensions/remote-questions/manager.ts +8 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +399 -29
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +382 -23
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/src/resources/extensions/gsd/files.ts +123 -1
- package/src/resources/extensions/gsd/guided-flow.ts +237 -4
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/metrics.ts +48 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +132 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/src/resources/extensions/remote-questions/format.ts +12 -6
- package/src/resources/extensions/remote-questions/manager.ts +8 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -38,6 +38,11 @@ export interface Args {
|
|
|
38
38
|
themes?: string[];
|
|
39
39
|
noThemes?: boolean;
|
|
40
40
|
listModels?: string | true;
|
|
41
|
+
discover?: boolean;
|
|
42
|
+
addProvider?: string;
|
|
43
|
+
addProviderBaseUrl?: string;
|
|
44
|
+
addProviderApiKey?: string;
|
|
45
|
+
discoverModels?: string | true;
|
|
41
46
|
offline?: boolean;
|
|
42
47
|
verbose?: boolean;
|
|
43
48
|
messages: string[];
|
|
@@ -150,6 +155,18 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
150
155
|
} else {
|
|
151
156
|
result.listModels = true;
|
|
152
157
|
}
|
|
158
|
+
} else if (arg === "--discover") {
|
|
159
|
+
result.discover = true;
|
|
160
|
+
} else if (arg === "--add-provider" && i + 1 < args.length) {
|
|
161
|
+
result.addProvider = args[++i];
|
|
162
|
+
} else if (arg === "--base-url" && i + 1 < args.length) {
|
|
163
|
+
result.addProviderBaseUrl = args[++i];
|
|
164
|
+
} else if (arg === "--discover-models") {
|
|
165
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
|
|
166
|
+
result.discoverModels = args[++i];
|
|
167
|
+
} else {
|
|
168
|
+
result.discoverModels = true;
|
|
169
|
+
}
|
|
153
170
|
} else if (arg === "--verbose") {
|
|
154
171
|
result.verbose = true;
|
|
155
172
|
} else if (arg === "--offline") {
|
|
@@ -219,6 +236,10 @@ ${chalk.bold("Options:")}
|
|
|
219
236
|
--no-themes Disable theme discovery and loading
|
|
220
237
|
--export <file> Export session file to HTML and exit
|
|
221
238
|
--list-models [search] List available models (with optional fuzzy search)
|
|
239
|
+
--discover Include discovered models in --list-models output
|
|
240
|
+
--discover-models [provider] Discover models from provider APIs (all or specific)
|
|
241
|
+
--add-provider <name> Add a provider to models.json (use with --base-url, --api-key)
|
|
242
|
+
--base-url <url> Base URL for --add-provider
|
|
222
243
|
--verbose Force verbose startup (overrides quietStartup setting)
|
|
223
244
|
--offline Disable startup network operations (same as PI_OFFLINE=1)
|
|
224
245
|
--help, -h Show this help
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* List available models with optional fuzzy search
|
|
2
|
+
* List available models with optional fuzzy search and discovery support
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { Api, Model } from "@gsd/pi-ai";
|
|
6
6
|
import { fuzzyFilter } from "@gsd/pi-tui";
|
|
7
7
|
import type { ModelRegistry } from "../core/model-registry.js";
|
|
8
8
|
|
|
9
|
+
export interface ListModelsOptions {
|
|
10
|
+
/** Include discovered models in output */
|
|
11
|
+
discover?: boolean;
|
|
12
|
+
/** Search pattern for fuzzy filtering */
|
|
13
|
+
searchPattern?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Format a number as human-readable (e.g., 200000 -> "200K", 1000000 -> "1M")
|
|
11
18
|
*/
|
|
@@ -22,10 +29,48 @@ function formatTokenCount(count: number): string {
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
|
-
*
|
|
32
|
+
* Discover models from provider APIs and print results.
|
|
26
33
|
*/
|
|
27
|
-
export async function
|
|
28
|
-
|
|
34
|
+
export async function discoverAndPrintModels(
|
|
35
|
+
modelRegistry: ModelRegistry,
|
|
36
|
+
provider?: string,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const providers = provider ? [provider] : undefined;
|
|
39
|
+
|
|
40
|
+
console.log("Discovering models...");
|
|
41
|
+
const results = await modelRegistry.discoverModels(providers);
|
|
42
|
+
|
|
43
|
+
for (const result of results) {
|
|
44
|
+
if (result.error) {
|
|
45
|
+
console.log(` ${result.provider}: error - ${result.error}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(` ${result.provider}: ${result.models.length} models found`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* List available models, optionally filtered by search pattern.
|
|
54
|
+
* Accepts either a string (backward compat) or ListModelsOptions.
|
|
55
|
+
*/
|
|
56
|
+
export async function listModels(
|
|
57
|
+
modelRegistry: ModelRegistry,
|
|
58
|
+
optionsOrSearch?: string | ListModelsOptions,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const options: ListModelsOptions =
|
|
61
|
+
typeof optionsOrSearch === "string"
|
|
62
|
+
? { searchPattern: optionsOrSearch }
|
|
63
|
+
: optionsOrSearch ?? {};
|
|
64
|
+
|
|
65
|
+
// If discover flag is set, run discovery first
|
|
66
|
+
if (options.discover) {
|
|
67
|
+
await modelRegistry.discoverModels();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get models — include discovered if discovery was run
|
|
71
|
+
const models = options.discover
|
|
72
|
+
? modelRegistry.getAllWithDiscovered()
|
|
73
|
+
: modelRegistry.getAvailable();
|
|
29
74
|
|
|
30
75
|
if (models.length === 0) {
|
|
31
76
|
console.log("No models available. Set API keys in environment variables.");
|
|
@@ -34,12 +79,12 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
34
79
|
|
|
35
80
|
// Apply fuzzy filter if search pattern provided
|
|
36
81
|
let filteredModels: Model<Api>[] = models;
|
|
37
|
-
if (searchPattern) {
|
|
38
|
-
filteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);
|
|
82
|
+
if (options.searchPattern) {
|
|
83
|
+
filteredModels = fuzzyFilter(models, options.searchPattern, (m) => `${m.provider} ${m.id}`);
|
|
39
84
|
}
|
|
40
85
|
|
|
41
86
|
if (filteredModels.length === 0) {
|
|
42
|
-
console.log(`No models matching "${searchPattern}"`);
|
|
87
|
+
console.log(`No models matching "${options.searchPattern}"`);
|
|
43
88
|
return;
|
|
44
89
|
}
|
|
45
90
|
|
|
@@ -53,15 +98,19 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
53
98
|
});
|
|
54
99
|
|
|
55
100
|
// Calculate column widths
|
|
56
|
-
const rows = filteredModels.map((m) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
101
|
+
const rows = filteredModels.map((m) => {
|
|
102
|
+
const isDiscovered = options.discover && modelRegistry.isDiscovered(m);
|
|
103
|
+
return {
|
|
104
|
+
provider: m.provider,
|
|
105
|
+
model: m.id,
|
|
106
|
+
name: m.name,
|
|
107
|
+
context: formatTokenCount(m.contextWindow),
|
|
108
|
+
maxOut: formatTokenCount(m.maxTokens),
|
|
109
|
+
thinking: m.reasoning ? "yes" : "no",
|
|
110
|
+
images: m.input.includes("image") ? "yes" : "no",
|
|
111
|
+
badge: isDiscovered ? "[discovered]" : "",
|
|
112
|
+
};
|
|
113
|
+
});
|
|
65
114
|
|
|
66
115
|
const headers = {
|
|
67
116
|
provider: "provider",
|
|
@@ -71,6 +120,7 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
71
120
|
maxOut: "max-out",
|
|
72
121
|
thinking: "thinking",
|
|
73
122
|
images: "images",
|
|
123
|
+
badge: "",
|
|
74
124
|
};
|
|
75
125
|
|
|
76
126
|
const widths = {
|
|
@@ -105,7 +155,10 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
105
155
|
row.maxOut.padEnd(widths.maxOut),
|
|
106
156
|
row.thinking.padEnd(widths.thinking),
|
|
107
157
|
row.images.padEnd(widths.images),
|
|
108
|
-
|
|
158
|
+
row.badge,
|
|
159
|
+
]
|
|
160
|
+
.join(" ")
|
|
161
|
+
.trimEnd();
|
|
109
162
|
console.log(line);
|
|
110
163
|
}
|
|
111
164
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
6
|
+
import { ModelDiscoveryCache } from "./discovery-cache.js";
|
|
7
|
+
|
|
8
|
+
let testDir: string;
|
|
9
|
+
let cachePath: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
testDir = join(tmpdir(), `discovery-cache-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
13
|
+
mkdirSync(testDir, { recursive: true });
|
|
14
|
+
cachePath = join(testDir, "discovery-cache.json");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
try {
|
|
19
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
20
|
+
} catch {
|
|
21
|
+
// Cleanup best-effort
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ─── basic operations ────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
describe("ModelDiscoveryCache — basic operations", () => {
|
|
28
|
+
it("starts with no entries", () => {
|
|
29
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
30
|
+
assert.equal(cache.get("openai"), undefined);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("stores and retrieves models", () => {
|
|
34
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
35
|
+
const models = [{ id: "gpt-4o", name: "GPT-4o" }];
|
|
36
|
+
cache.set("openai", models);
|
|
37
|
+
|
|
38
|
+
const entry = cache.get("openai");
|
|
39
|
+
assert.ok(entry);
|
|
40
|
+
assert.deepEqual(entry.models, models);
|
|
41
|
+
assert.ok(entry.fetchedAt > 0);
|
|
42
|
+
assert.ok(entry.ttlMs > 0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("persists to disk and reloads", () => {
|
|
46
|
+
const cache1 = new ModelDiscoveryCache(cachePath);
|
|
47
|
+
cache1.set("openai", [{ id: "gpt-4o" }]);
|
|
48
|
+
|
|
49
|
+
const cache2 = new ModelDiscoveryCache(cachePath);
|
|
50
|
+
const entry = cache2.get("openai");
|
|
51
|
+
assert.ok(entry);
|
|
52
|
+
assert.equal(entry.models[0].id, "gpt-4o");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("clear removes a specific provider", () => {
|
|
56
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
57
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
58
|
+
cache.set("google", [{ id: "gemini-pro" }]);
|
|
59
|
+
|
|
60
|
+
cache.clear("openai");
|
|
61
|
+
assert.equal(cache.get("openai"), undefined);
|
|
62
|
+
assert.ok(cache.get("google"));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("clear without provider removes all entries", () => {
|
|
66
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
67
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
68
|
+
cache.set("google", [{ id: "gemini-pro" }]);
|
|
69
|
+
|
|
70
|
+
cache.clear();
|
|
71
|
+
assert.equal(cache.get("openai"), undefined);
|
|
72
|
+
assert.equal(cache.get("google"), undefined);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ─── staleness ───────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
describe("ModelDiscoveryCache — staleness", () => {
|
|
79
|
+
it("newly set entries are not stale", () => {
|
|
80
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
81
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
82
|
+
assert.equal(cache.isStale("openai"), false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("missing providers are stale", () => {
|
|
86
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
87
|
+
assert.equal(cache.isStale("unknown"), true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("entries with expired TTL are stale", () => {
|
|
91
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
92
|
+
cache.set("openai", [{ id: "gpt-4o" }], 1); // 1ms TTL
|
|
93
|
+
|
|
94
|
+
// Wait for TTL to expire
|
|
95
|
+
const start = Date.now();
|
|
96
|
+
while (Date.now() - start < 5) {
|
|
97
|
+
// busy wait
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
assert.equal(cache.isStale("openai"), true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ─── getAll ──────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
describe("ModelDiscoveryCache — getAll", () => {
|
|
107
|
+
it("returns non-stale entries by default", () => {
|
|
108
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
109
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
110
|
+
cache.set("stale", [{ id: "old" }], 1);
|
|
111
|
+
|
|
112
|
+
// Wait for stale TTL
|
|
113
|
+
const start = Date.now();
|
|
114
|
+
while (Date.now() - start < 5) {
|
|
115
|
+
// busy wait
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const all = cache.getAll();
|
|
119
|
+
assert.ok(all.has("openai"));
|
|
120
|
+
assert.ok(!all.has("stale"));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns all entries when includeStale is true", () => {
|
|
124
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
125
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
126
|
+
cache.set("stale", [{ id: "old" }], 1);
|
|
127
|
+
|
|
128
|
+
// Wait for stale TTL
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
while (Date.now() - start < 5) {
|
|
131
|
+
// busy wait
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const all = cache.getAll(true);
|
|
135
|
+
assert.ok(all.has("openai"));
|
|
136
|
+
assert.ok(all.has("stale"));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ─── edge cases ──────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
describe("ModelDiscoveryCache — edge cases", () => {
|
|
143
|
+
it("handles corrupted cache file gracefully", () => {
|
|
144
|
+
writeFileSync(cachePath, "not valid json", "utf-8");
|
|
145
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
146
|
+
assert.equal(cache.get("openai"), undefined);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("handles wrong version gracefully", () => {
|
|
150
|
+
writeFileSync(cachePath, JSON.stringify({ version: 99, entries: {} }), "utf-8");
|
|
151
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
152
|
+
assert.equal(cache.get("openai"), undefined);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("handles missing cache file", () => {
|
|
156
|
+
const cache = new ModelDiscoveryCache(join(testDir, "nonexistent", "cache.json"));
|
|
157
|
+
assert.equal(cache.get("openai"), undefined);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("overwrites existing entry for same provider", () => {
|
|
161
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
162
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
163
|
+
cache.set("openai", [{ id: "gpt-4o-mini" }]);
|
|
164
|
+
|
|
165
|
+
const entry = cache.get("openai");
|
|
166
|
+
assert.ok(entry);
|
|
167
|
+
assert.equal(entry.models.length, 1);
|
|
168
|
+
assert.equal(entry.models[0].id, "gpt-4o-mini");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk-based cache for discovered models.
|
|
3
|
+
* Stores results at {agentDir}/discovery-cache.json with per-provider TTLs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { getAgentDir } from "../config.js";
|
|
9
|
+
import { type DiscoveredModel, getDefaultTTL } from "./model-discovery.js";
|
|
10
|
+
|
|
11
|
+
export interface DiscoveryCacheEntry {
|
|
12
|
+
models: DiscoveredModel[];
|
|
13
|
+
fetchedAt: number;
|
|
14
|
+
ttlMs: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DiscoveryCacheData {
|
|
18
|
+
version: 1;
|
|
19
|
+
entries: Record<string, DiscoveryCacheEntry>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ModelDiscoveryCache {
|
|
23
|
+
private data: DiscoveryCacheData;
|
|
24
|
+
private cachePath: string;
|
|
25
|
+
|
|
26
|
+
constructor(cachePath?: string) {
|
|
27
|
+
this.cachePath = cachePath ?? join(getAgentDir(), "discovery-cache.json");
|
|
28
|
+
this.data = { version: 1, entries: {} };
|
|
29
|
+
this.load();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get(provider: string): DiscoveryCacheEntry | undefined {
|
|
33
|
+
const entry = this.data.entries[provider];
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set(provider: string, models: DiscoveredModel[], ttlMs?: number): void {
|
|
38
|
+
this.data.entries[provider] = {
|
|
39
|
+
models,
|
|
40
|
+
fetchedAt: Date.now(),
|
|
41
|
+
ttlMs: ttlMs ?? getDefaultTTL(provider),
|
|
42
|
+
};
|
|
43
|
+
this.save();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isStale(provider: string): boolean {
|
|
47
|
+
const entry = this.data.entries[provider];
|
|
48
|
+
if (!entry) return true;
|
|
49
|
+
return Date.now() - entry.fetchedAt > entry.ttlMs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
clear(provider?: string): void {
|
|
53
|
+
if (provider) {
|
|
54
|
+
delete this.data.entries[provider];
|
|
55
|
+
} else {
|
|
56
|
+
this.data.entries = {};
|
|
57
|
+
}
|
|
58
|
+
this.save();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getAll(includeStale = false): Map<string, DiscoveryCacheEntry> {
|
|
62
|
+
const result = new Map<string, DiscoveryCacheEntry>();
|
|
63
|
+
for (const [provider, entry] of Object.entries(this.data.entries)) {
|
|
64
|
+
if (includeStale || !this.isStale(provider)) {
|
|
65
|
+
result.set(provider, entry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
load(): void {
|
|
72
|
+
try {
|
|
73
|
+
if (existsSync(this.cachePath)) {
|
|
74
|
+
const content = readFileSync(this.cachePath, "utf-8");
|
|
75
|
+
const parsed = JSON.parse(content) as DiscoveryCacheData;
|
|
76
|
+
if (parsed.version === 1 && parsed.entries) {
|
|
77
|
+
this.data = parsed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Corrupted or unreadable cache — start fresh
|
|
82
|
+
this.data = { version: 1, entries: {} };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
save(): void {
|
|
87
|
+
try {
|
|
88
|
+
const dir = dirname(this.cachePath);
|
|
89
|
+
if (!existsSync(dir)) {
|
|
90
|
+
mkdirSync(dir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
writeFileSync(this.cachePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
93
|
+
} catch {
|
|
94
|
+
// Silently ignore write failures (read-only FS, permissions, etc.)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
DISCOVERY_TTLS,
|
|
5
|
+
getDefaultTTL,
|
|
6
|
+
getDiscoverableProviders,
|
|
7
|
+
getDiscoveryAdapter,
|
|
8
|
+
} from "./model-discovery.js";
|
|
9
|
+
|
|
10
|
+
// ─── getDiscoveryAdapter ─────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
describe("getDiscoveryAdapter", () => {
|
|
13
|
+
it("returns an adapter for openai", () => {
|
|
14
|
+
const adapter = getDiscoveryAdapter("openai");
|
|
15
|
+
assert.equal(adapter.provider, "openai");
|
|
16
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns an adapter for ollama", () => {
|
|
20
|
+
const adapter = getDiscoveryAdapter("ollama");
|
|
21
|
+
assert.equal(adapter.provider, "ollama");
|
|
22
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns an adapter for openrouter", () => {
|
|
26
|
+
const adapter = getDiscoveryAdapter("openrouter");
|
|
27
|
+
assert.equal(adapter.provider, "openrouter");
|
|
28
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns an adapter for google", () => {
|
|
32
|
+
const adapter = getDiscoveryAdapter("google");
|
|
33
|
+
assert.equal(adapter.provider, "google");
|
|
34
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns a static adapter for anthropic", () => {
|
|
38
|
+
const adapter = getDiscoveryAdapter("anthropic");
|
|
39
|
+
assert.equal(adapter.provider, "anthropic");
|
|
40
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns a static adapter for bedrock", () => {
|
|
44
|
+
const adapter = getDiscoveryAdapter("bedrock");
|
|
45
|
+
assert.equal(adapter.provider, "bedrock");
|
|
46
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns a static adapter for unknown providers", () => {
|
|
50
|
+
const adapter = getDiscoveryAdapter("unknown-provider");
|
|
51
|
+
assert.equal(adapter.provider, "unknown-provider");
|
|
52
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("static adapter fetchModels returns empty array", async () => {
|
|
56
|
+
const adapter = getDiscoveryAdapter("anthropic");
|
|
57
|
+
const models = await adapter.fetchModels("key");
|
|
58
|
+
assert.deepEqual(models, []);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ─── getDiscoverableProviders ────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
describe("getDiscoverableProviders", () => {
|
|
65
|
+
it("returns only providers that support discovery", () => {
|
|
66
|
+
const providers = getDiscoverableProviders();
|
|
67
|
+
assert.ok(providers.includes("openai"));
|
|
68
|
+
assert.ok(providers.includes("ollama"));
|
|
69
|
+
assert.ok(providers.includes("openrouter"));
|
|
70
|
+
assert.ok(providers.includes("google"));
|
|
71
|
+
assert.ok(!providers.includes("anthropic"));
|
|
72
|
+
assert.ok(!providers.includes("bedrock"));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("returns an array of strings", () => {
|
|
76
|
+
const providers = getDiscoverableProviders();
|
|
77
|
+
assert.ok(Array.isArray(providers));
|
|
78
|
+
for (const p of providers) {
|
|
79
|
+
assert.equal(typeof p, "string");
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── getDefaultTTL ───────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("getDefaultTTL", () => {
|
|
87
|
+
it("returns 5 minutes for ollama", () => {
|
|
88
|
+
assert.equal(getDefaultTTL("ollama"), 5 * 60 * 1000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns 1 hour for openai", () => {
|
|
92
|
+
assert.equal(getDefaultTTL("openai"), 60 * 60 * 1000);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns 1 hour for google", () => {
|
|
96
|
+
assert.equal(getDefaultTTL("google"), 60 * 60 * 1000);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns 1 hour for openrouter", () => {
|
|
100
|
+
assert.equal(getDefaultTTL("openrouter"), 60 * 60 * 1000);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns 24 hours for unknown providers", () => {
|
|
104
|
+
assert.equal(getDefaultTTL("some-custom"), 24 * 60 * 60 * 1000);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ─── DISCOVERY_TTLS ──────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
describe("DISCOVERY_TTLS", () => {
|
|
111
|
+
it("has expected keys", () => {
|
|
112
|
+
assert.ok("ollama" in DISCOVERY_TTLS);
|
|
113
|
+
assert.ok("openai" in DISCOVERY_TTLS);
|
|
114
|
+
assert.ok("google" in DISCOVERY_TTLS);
|
|
115
|
+
assert.ok("openrouter" in DISCOVERY_TTLS);
|
|
116
|
+
assert.ok("default" in DISCOVERY_TTLS);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("all values are positive numbers", () => {
|
|
120
|
+
for (const [, value] of Object.entries(DISCOVERY_TTLS)) {
|
|
121
|
+
assert.equal(typeof value, "number");
|
|
122
|
+
assert.ok(value > 0);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|