gsd-pi 2.16.0 → 2.18.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 +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +177 -25
- package/dist/resources/extensions/gsd/commands.ts +264 -23
- package/dist/resources/extensions/gsd/complexity.ts +236 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/dist/resources/extensions/gsd/files.ts +129 -3
- package/dist/resources/extensions/gsd/git-service.ts +19 -8
- package/dist/resources/extensions/gsd/gitignore.ts +41 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +247 -10
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/metrics.ts +44 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/preferences.ts +181 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/system.md +2 -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/routing-history.ts +290 -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-recovery.test.ts +50 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -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/preferences-git.test.ts +28 -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/routing-history.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/dist/resources/extensions/gsd/types.ts +28 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +24 -2
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +493 -13
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +422 -62
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +9 -22
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
- package/packages/pi-ai/src/models.generated.ts +422 -62
- package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
- package/packages/pi-ai/src/providers/google-shared.ts +10 -19
- 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/core/tools/edit-diff.d.ts +7 -7
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
- 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/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.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/core/tools/edit-diff.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
- 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/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +10 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +177 -25
- package/src/resources/extensions/gsd/commands.ts +264 -23
- package/src/resources/extensions/gsd/complexity.ts +236 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/src/resources/extensions/gsd/files.ts +129 -3
- package/src/resources/extensions/gsd/git-service.ts +19 -8
- package/src/resources/extensions/gsd/gitignore.ts +41 -2
- package/src/resources/extensions/gsd/guided-flow.ts +247 -10
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/metrics.ts +44 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/preferences.ts +181 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/system.md +2 -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/routing-history.ts +290 -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-recovery.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -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/preferences-git.test.ts +28 -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/routing-history.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/src/resources/extensions/gsd/types.ts +28 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +24 -2
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk-based cache for discovered models.
|
|
3
|
+
* Stores results at {agentDir}/discovery-cache.json with per-provider TTLs.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { getAgentDir } from "../config.js";
|
|
8
|
+
import { getDefaultTTL } from "./model-discovery.js";
|
|
9
|
+
export class ModelDiscoveryCache {
|
|
10
|
+
constructor(cachePath) {
|
|
11
|
+
this.cachePath = cachePath ?? join(getAgentDir(), "discovery-cache.json");
|
|
12
|
+
this.data = { version: 1, entries: {} };
|
|
13
|
+
this.load();
|
|
14
|
+
}
|
|
15
|
+
get(provider) {
|
|
16
|
+
const entry = this.data.entries[provider];
|
|
17
|
+
return entry;
|
|
18
|
+
}
|
|
19
|
+
set(provider, models, ttlMs) {
|
|
20
|
+
this.data.entries[provider] = {
|
|
21
|
+
models,
|
|
22
|
+
fetchedAt: Date.now(),
|
|
23
|
+
ttlMs: ttlMs ?? getDefaultTTL(provider),
|
|
24
|
+
};
|
|
25
|
+
this.save();
|
|
26
|
+
}
|
|
27
|
+
isStale(provider) {
|
|
28
|
+
const entry = this.data.entries[provider];
|
|
29
|
+
if (!entry)
|
|
30
|
+
return true;
|
|
31
|
+
return Date.now() - entry.fetchedAt > entry.ttlMs;
|
|
32
|
+
}
|
|
33
|
+
clear(provider) {
|
|
34
|
+
if (provider) {
|
|
35
|
+
delete this.data.entries[provider];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.data.entries = {};
|
|
39
|
+
}
|
|
40
|
+
this.save();
|
|
41
|
+
}
|
|
42
|
+
getAll(includeStale = false) {
|
|
43
|
+
const result = new Map();
|
|
44
|
+
for (const [provider, entry] of Object.entries(this.data.entries)) {
|
|
45
|
+
if (includeStale || !this.isStale(provider)) {
|
|
46
|
+
result.set(provider, entry);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
load() {
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(this.cachePath)) {
|
|
54
|
+
const content = readFileSync(this.cachePath, "utf-8");
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
if (parsed.version === 1 && parsed.entries) {
|
|
57
|
+
this.data = parsed;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Corrupted or unreadable cache — start fresh
|
|
63
|
+
this.data = { version: 1, entries: {} };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
save() {
|
|
67
|
+
try {
|
|
68
|
+
const dir = dirname(this.cachePath);
|
|
69
|
+
if (!existsSync(dir)) {
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
writeFileSync(this.cachePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Silently ignore write failures (read-only FS, permissions, etc.)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=discovery-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-cache.js","sourceRoot":"","sources":["../../src/core/discovery-cache.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Disk-based cache for discovered models.\n * Stores results at {agentDir}/discovery-cache.json with per-provider TTLs.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { type DiscoveredModel, getDefaultTTL } from \"./model-discovery.js\";\n\nexport interface DiscoveryCacheEntry {\n\tmodels: DiscoveredModel[];\n\tfetchedAt: number;\n\tttlMs: number;\n}\n\nexport interface DiscoveryCacheData {\n\tversion: 1;\n\tentries: Record<string, DiscoveryCacheEntry>;\n}\n\nexport class ModelDiscoveryCache {\n\tprivate data: DiscoveryCacheData;\n\tprivate cachePath: string;\n\n\tconstructor(cachePath?: string) {\n\t\tthis.cachePath = cachePath ?? join(getAgentDir(), \"discovery-cache.json\");\n\t\tthis.data = { version: 1, entries: {} };\n\t\tthis.load();\n\t}\n\n\tget(provider: string): DiscoveryCacheEntry | undefined {\n\t\tconst entry = this.data.entries[provider];\n\t\treturn entry;\n\t}\n\n\tset(provider: string, models: DiscoveredModel[], ttlMs?: number): void {\n\t\tthis.data.entries[provider] = {\n\t\t\tmodels,\n\t\t\tfetchedAt: Date.now(),\n\t\t\tttlMs: ttlMs ?? getDefaultTTL(provider),\n\t\t};\n\t\tthis.save();\n\t}\n\n\tisStale(provider: string): boolean {\n\t\tconst entry = this.data.entries[provider];\n\t\tif (!entry) return true;\n\t\treturn Date.now() - entry.fetchedAt > entry.ttlMs;\n\t}\n\n\tclear(provider?: string): void {\n\t\tif (provider) {\n\t\t\tdelete this.data.entries[provider];\n\t\t} else {\n\t\t\tthis.data.entries = {};\n\t\t}\n\t\tthis.save();\n\t}\n\n\tgetAll(includeStale = false): Map<string, DiscoveryCacheEntry> {\n\t\tconst result = new Map<string, DiscoveryCacheEntry>();\n\t\tfor (const [provider, entry] of Object.entries(this.data.entries)) {\n\t\t\tif (includeStale || !this.isStale(provider)) {\n\t\t\t\tresult.set(provider, entry);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tload(): void {\n\t\ttry {\n\t\t\tif (existsSync(this.cachePath)) {\n\t\t\t\tconst content = readFileSync(this.cachePath, \"utf-8\");\n\t\t\t\tconst parsed = JSON.parse(content) as DiscoveryCacheData;\n\t\t\t\tif (parsed.version === 1 && parsed.entries) {\n\t\t\t\t\tthis.data = parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Corrupted or unreadable cache — start fresh\n\t\t\tthis.data = { version: 1, entries: {} };\n\t\t}\n\t}\n\n\tsave(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.cachePath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.cachePath, JSON.stringify(this.data, null, 2), \"utf-8\");\n\t\t} catch {\n\t\t\t// Silently ignore write failures (read-only FS, permissions, etc.)\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-cache.test.d.ts","sourceRoot":"","sources":["../../src/core/discovery-cache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { 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
|
+
let testDir;
|
|
8
|
+
let cachePath;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
testDir = join(tmpdir(), `discovery-cache-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
11
|
+
mkdirSync(testDir, { recursive: true });
|
|
12
|
+
cachePath = join(testDir, "discovery-cache.json");
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
try {
|
|
16
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Cleanup best-effort
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
// ─── basic operations ────────────────────────────────────────────────────────
|
|
23
|
+
describe("ModelDiscoveryCache — basic operations", () => {
|
|
24
|
+
it("starts with no entries", () => {
|
|
25
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
26
|
+
assert.equal(cache.get("openai"), undefined);
|
|
27
|
+
});
|
|
28
|
+
it("stores and retrieves models", () => {
|
|
29
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
30
|
+
const models = [{ id: "gpt-4o", name: "GPT-4o" }];
|
|
31
|
+
cache.set("openai", models);
|
|
32
|
+
const entry = cache.get("openai");
|
|
33
|
+
assert.ok(entry);
|
|
34
|
+
assert.deepEqual(entry.models, models);
|
|
35
|
+
assert.ok(entry.fetchedAt > 0);
|
|
36
|
+
assert.ok(entry.ttlMs > 0);
|
|
37
|
+
});
|
|
38
|
+
it("persists to disk and reloads", () => {
|
|
39
|
+
const cache1 = new ModelDiscoveryCache(cachePath);
|
|
40
|
+
cache1.set("openai", [{ id: "gpt-4o" }]);
|
|
41
|
+
const cache2 = new ModelDiscoveryCache(cachePath);
|
|
42
|
+
const entry = cache2.get("openai");
|
|
43
|
+
assert.ok(entry);
|
|
44
|
+
assert.equal(entry.models[0].id, "gpt-4o");
|
|
45
|
+
});
|
|
46
|
+
it("clear removes a specific provider", () => {
|
|
47
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
48
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
49
|
+
cache.set("google", [{ id: "gemini-pro" }]);
|
|
50
|
+
cache.clear("openai");
|
|
51
|
+
assert.equal(cache.get("openai"), undefined);
|
|
52
|
+
assert.ok(cache.get("google"));
|
|
53
|
+
});
|
|
54
|
+
it("clear without provider removes all entries", () => {
|
|
55
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
56
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
57
|
+
cache.set("google", [{ id: "gemini-pro" }]);
|
|
58
|
+
cache.clear();
|
|
59
|
+
assert.equal(cache.get("openai"), undefined);
|
|
60
|
+
assert.equal(cache.get("google"), undefined);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ─── staleness ───────────────────────────────────────────────────────────────
|
|
64
|
+
describe("ModelDiscoveryCache — staleness", () => {
|
|
65
|
+
it("newly set entries are not stale", () => {
|
|
66
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
67
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
68
|
+
assert.equal(cache.isStale("openai"), false);
|
|
69
|
+
});
|
|
70
|
+
it("missing providers are stale", () => {
|
|
71
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
72
|
+
assert.equal(cache.isStale("unknown"), true);
|
|
73
|
+
});
|
|
74
|
+
it("entries with expired TTL are stale", () => {
|
|
75
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
76
|
+
cache.set("openai", [{ id: "gpt-4o" }], 1); // 1ms TTL
|
|
77
|
+
// Wait for TTL to expire
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
while (Date.now() - start < 5) {
|
|
80
|
+
// busy wait
|
|
81
|
+
}
|
|
82
|
+
assert.equal(cache.isStale("openai"), true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// ─── getAll ──────────────────────────────────────────────────────────────────
|
|
86
|
+
describe("ModelDiscoveryCache — getAll", () => {
|
|
87
|
+
it("returns non-stale entries by default", () => {
|
|
88
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
89
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
90
|
+
cache.set("stale", [{ id: "old" }], 1);
|
|
91
|
+
// Wait for stale TTL
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
while (Date.now() - start < 5) {
|
|
94
|
+
// busy wait
|
|
95
|
+
}
|
|
96
|
+
const all = cache.getAll();
|
|
97
|
+
assert.ok(all.has("openai"));
|
|
98
|
+
assert.ok(!all.has("stale"));
|
|
99
|
+
});
|
|
100
|
+
it("returns all entries when includeStale is true", () => {
|
|
101
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
102
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
103
|
+
cache.set("stale", [{ id: "old" }], 1);
|
|
104
|
+
// Wait for stale TTL
|
|
105
|
+
const start = Date.now();
|
|
106
|
+
while (Date.now() - start < 5) {
|
|
107
|
+
// busy wait
|
|
108
|
+
}
|
|
109
|
+
const all = cache.getAll(true);
|
|
110
|
+
assert.ok(all.has("openai"));
|
|
111
|
+
assert.ok(all.has("stale"));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── edge cases ──────────────────────────────────────────────────────────────
|
|
115
|
+
describe("ModelDiscoveryCache — edge cases", () => {
|
|
116
|
+
it("handles corrupted cache file gracefully", () => {
|
|
117
|
+
writeFileSync(cachePath, "not valid json", "utf-8");
|
|
118
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
119
|
+
assert.equal(cache.get("openai"), undefined);
|
|
120
|
+
});
|
|
121
|
+
it("handles wrong version gracefully", () => {
|
|
122
|
+
writeFileSync(cachePath, JSON.stringify({ version: 99, entries: {} }), "utf-8");
|
|
123
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
124
|
+
assert.equal(cache.get("openai"), undefined);
|
|
125
|
+
});
|
|
126
|
+
it("handles missing cache file", () => {
|
|
127
|
+
const cache = new ModelDiscoveryCache(join(testDir, "nonexistent", "cache.json"));
|
|
128
|
+
assert.equal(cache.get("openai"), undefined);
|
|
129
|
+
});
|
|
130
|
+
it("overwrites existing entry for same provider", () => {
|
|
131
|
+
const cache = new ModelDiscoveryCache(cachePath);
|
|
132
|
+
cache.set("openai", [{ id: "gpt-4o" }]);
|
|
133
|
+
cache.set("openai", [{ id: "gpt-4o-mini" }]);
|
|
134
|
+
const entry = cache.get("openai");
|
|
135
|
+
assert.ok(entry);
|
|
136
|
+
assert.equal(entry.models.length, 1);
|
|
137
|
+
assert.equal(entry.models[0].id, "gpt-4o-mini");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=discovery-cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-cache.test.js","sourceRoot":"","sources":["../../src/core/discovery-cache.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { existsSync, mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, beforeEach, describe, it } from \"node:test\";\nimport { ModelDiscoveryCache } from \"./discovery-cache.js\";\n\nlet testDir: string;\nlet cachePath: string;\n\nbeforeEach(() => {\n\ttestDir = join(tmpdir(), `discovery-cache-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);\n\tmkdirSync(testDir, { recursive: true });\n\tcachePath = join(testDir, \"discovery-cache.json\");\n});\n\nafterEach(() => {\n\ttry {\n\t\trmSync(testDir, { recursive: true, force: true });\n\t} catch {\n\t\t// Cleanup best-effort\n\t}\n});\n\n// ─── basic operations ────────────────────────────────────────────────────────\n\ndescribe(\"ModelDiscoveryCache — basic operations\", () => {\n\tit(\"starts with no entries\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t});\n\n\tit(\"stores and retrieves models\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tconst models = [{ id: \"gpt-4o\", name: \"GPT-4o\" }];\n\t\tcache.set(\"openai\", models);\n\n\t\tconst entry = cache.get(\"openai\");\n\t\tassert.ok(entry);\n\t\tassert.deepEqual(entry.models, models);\n\t\tassert.ok(entry.fetchedAt > 0);\n\t\tassert.ok(entry.ttlMs > 0);\n\t});\n\n\tit(\"persists to disk and reloads\", () => {\n\t\tconst cache1 = new ModelDiscoveryCache(cachePath);\n\t\tcache1.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\n\t\tconst cache2 = new ModelDiscoveryCache(cachePath);\n\t\tconst entry = cache2.get(\"openai\");\n\t\tassert.ok(entry);\n\t\tassert.equal(entry.models[0].id, \"gpt-4o\");\n\t});\n\n\tit(\"clear removes a specific provider\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tcache.set(\"google\", [{ id: \"gemini-pro\" }]);\n\n\t\tcache.clear(\"openai\");\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t\tassert.ok(cache.get(\"google\"));\n\t});\n\n\tit(\"clear without provider removes all entries\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tcache.set(\"google\", [{ id: \"gemini-pro\" }]);\n\n\t\tcache.clear();\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t\tassert.equal(cache.get(\"google\"), undefined);\n\t});\n});\n\n// ─── staleness ───────────────────────────────────────────────────────────────\n\ndescribe(\"ModelDiscoveryCache — staleness\", () => {\n\tit(\"newly set entries are not stale\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tassert.equal(cache.isStale(\"openai\"), false);\n\t});\n\n\tit(\"missing providers are stale\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tassert.equal(cache.isStale(\"unknown\"), true);\n\t});\n\n\tit(\"entries with expired TTL are stale\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }], 1); // 1ms TTL\n\n\t\t// Wait for TTL to expire\n\t\tconst start = Date.now();\n\t\twhile (Date.now() - start < 5) {\n\t\t\t// busy wait\n\t\t}\n\n\t\tassert.equal(cache.isStale(\"openai\"), true);\n\t});\n});\n\n// ─── getAll ──────────────────────────────────────────────────────────────────\n\ndescribe(\"ModelDiscoveryCache — getAll\", () => {\n\tit(\"returns non-stale entries by default\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tcache.set(\"stale\", [{ id: \"old\" }], 1);\n\n\t\t// Wait for stale TTL\n\t\tconst start = Date.now();\n\t\twhile (Date.now() - start < 5) {\n\t\t\t// busy wait\n\t\t}\n\n\t\tconst all = cache.getAll();\n\t\tassert.ok(all.has(\"openai\"));\n\t\tassert.ok(!all.has(\"stale\"));\n\t});\n\n\tit(\"returns all entries when includeStale is true\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tcache.set(\"stale\", [{ id: \"old\" }], 1);\n\n\t\t// Wait for stale TTL\n\t\tconst start = Date.now();\n\t\twhile (Date.now() - start < 5) {\n\t\t\t// busy wait\n\t\t}\n\n\t\tconst all = cache.getAll(true);\n\t\tassert.ok(all.has(\"openai\"));\n\t\tassert.ok(all.has(\"stale\"));\n\t});\n});\n\n// ─── edge cases ──────────────────────────────────────────────────────────────\n\ndescribe(\"ModelDiscoveryCache — edge cases\", () => {\n\tit(\"handles corrupted cache file gracefully\", () => {\n\t\twriteFileSync(cachePath, \"not valid json\", \"utf-8\");\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t});\n\n\tit(\"handles wrong version gracefully\", () => {\n\t\twriteFileSync(cachePath, JSON.stringify({ version: 99, entries: {} }), \"utf-8\");\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t});\n\n\tit(\"handles missing cache file\", () => {\n\t\tconst cache = new ModelDiscoveryCache(join(testDir, \"nonexistent\", \"cache.json\"));\n\t\tassert.equal(cache.get(\"openai\"), undefined);\n\t});\n\n\tit(\"overwrites existing entry for same provider\", () => {\n\t\tconst cache = new ModelDiscoveryCache(cachePath);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o\" }]);\n\t\tcache.set(\"openai\", [{ id: \"gpt-4o-mini\" }]);\n\n\t\tconst entry = cache.get(\"openai\");\n\t\tassert.ok(entry);\n\t\tassert.equal(entry.models.length, 1);\n\t\tassert.equal(entry.models[0].id, \"gpt-4o-mini\");\n\t});\n});\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider discovery adapters for runtime model enumeration.
|
|
3
|
+
* Each adapter implements ProviderDiscoveryAdapter to fetch models from provider APIs.
|
|
4
|
+
*/
|
|
5
|
+
export interface DiscoveredModel {
|
|
6
|
+
id: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
contextWindow?: number;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
reasoning?: boolean;
|
|
11
|
+
input?: ("text" | "image")[];
|
|
12
|
+
cost?: {
|
|
13
|
+
input: number;
|
|
14
|
+
output: number;
|
|
15
|
+
cacheRead: number;
|
|
16
|
+
cacheWrite: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface DiscoveryResult {
|
|
20
|
+
provider: string;
|
|
21
|
+
models: DiscoveredModel[];
|
|
22
|
+
fetchedAt: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ProviderDiscoveryAdapter {
|
|
26
|
+
provider: string;
|
|
27
|
+
supportsDiscovery: boolean;
|
|
28
|
+
fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]>;
|
|
29
|
+
}
|
|
30
|
+
/** Per-provider TTLs in milliseconds */
|
|
31
|
+
export declare const DISCOVERY_TTLS: Record<string, number>;
|
|
32
|
+
export declare function getDefaultTTL(provider: string): number;
|
|
33
|
+
export declare function getDiscoveryAdapter(provider: string): ProviderDiscoveryAdapter;
|
|
34
|
+
export declare function getDiscoverableProviders(): string[];
|
|
35
|
+
//# sourceMappingURL=model-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.d.ts","sourceRoot":"","sources":["../../src/core/model-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CAC1E;AAED,wCAAwC;AACxC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMjD,CAAC;AAEF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAuLD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,wBAAwB,CAE9E;AAED,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAInD"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider discovery adapters for runtime model enumeration.
|
|
3
|
+
* Each adapter implements ProviderDiscoveryAdapter to fetch models from provider APIs.
|
|
4
|
+
*/
|
|
5
|
+
/** Per-provider TTLs in milliseconds */
|
|
6
|
+
export const DISCOVERY_TTLS = {
|
|
7
|
+
ollama: 5 * 60 * 1000, // 5 minutes (local, models change often)
|
|
8
|
+
openai: 60 * 60 * 1000, // 1 hour
|
|
9
|
+
google: 60 * 60 * 1000, // 1 hour
|
|
10
|
+
openrouter: 60 * 60 * 1000, // 1 hour
|
|
11
|
+
default: 24 * 60 * 60 * 1000, // 24 hours
|
|
12
|
+
};
|
|
13
|
+
export function getDefaultTTL(provider) {
|
|
14
|
+
return DISCOVERY_TTLS[provider] ?? DISCOVERY_TTLS.default;
|
|
15
|
+
}
|
|
16
|
+
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
19
|
+
try {
|
|
20
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
clearTimeout(timeout);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// ─── OpenAI Adapter ──────────────────────────────────────────────────────────
|
|
27
|
+
const OPENAI_EXCLUDED_PREFIXES = ["embedding", "tts", "dall-e", "whisper", "text-embedding", "davinci", "babbage"];
|
|
28
|
+
class OpenAIDiscoveryAdapter {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.provider = "openai";
|
|
31
|
+
this.supportsDiscovery = true;
|
|
32
|
+
}
|
|
33
|
+
async fetchModels(apiKey, baseUrl) {
|
|
34
|
+
const url = `${baseUrl ?? "https://api.openai.com"}/v1/models`;
|
|
35
|
+
const response = await fetchWithTimeout(url, {
|
|
36
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`OpenAI models API returned ${response.status}: ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
const data = (await response.json());
|
|
42
|
+
return data.data
|
|
43
|
+
.filter((m) => !OPENAI_EXCLUDED_PREFIXES.some((prefix) => m.id.startsWith(prefix)))
|
|
44
|
+
.map((m) => ({
|
|
45
|
+
id: m.id,
|
|
46
|
+
name: m.id,
|
|
47
|
+
input: ["text", "image"],
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ─── Ollama Adapter ──────────────────────────────────────────────────────────
|
|
52
|
+
class OllamaDiscoveryAdapter {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.provider = "ollama";
|
|
55
|
+
this.supportsDiscovery = true;
|
|
56
|
+
}
|
|
57
|
+
async fetchModels(_apiKey, baseUrl) {
|
|
58
|
+
const url = `${baseUrl ?? "http://localhost:11434"}/api/tags`;
|
|
59
|
+
const response = await fetchWithTimeout(url);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Ollama tags API returned ${response.status}: ${response.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
const data = (await response.json());
|
|
64
|
+
return (data.models ?? []).map((m) => ({
|
|
65
|
+
id: m.name,
|
|
66
|
+
name: m.name,
|
|
67
|
+
input: ["text"],
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// ─── OpenRouter Adapter ──────────────────────────────────────────────────────
|
|
72
|
+
class OpenRouterDiscoveryAdapter {
|
|
73
|
+
constructor() {
|
|
74
|
+
this.provider = "openrouter";
|
|
75
|
+
this.supportsDiscovery = true;
|
|
76
|
+
}
|
|
77
|
+
async fetchModels(apiKey, baseUrl) {
|
|
78
|
+
const url = `${baseUrl ?? "https://openrouter.ai"}/api/v1/models`;
|
|
79
|
+
const response = await fetchWithTimeout(url, {
|
|
80
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`OpenRouter models API returned ${response.status}: ${response.statusText}`);
|
|
84
|
+
}
|
|
85
|
+
const data = (await response.json());
|
|
86
|
+
return (data.data ?? []).map((m) => {
|
|
87
|
+
const cost = m.pricing?.prompt !== undefined && m.pricing?.completion !== undefined
|
|
88
|
+
? {
|
|
89
|
+
input: parseFloat(m.pricing.prompt) * 1_000_000,
|
|
90
|
+
output: parseFloat(m.pricing.completion) * 1_000_000,
|
|
91
|
+
cacheRead: 0,
|
|
92
|
+
cacheWrite: 0,
|
|
93
|
+
}
|
|
94
|
+
: undefined;
|
|
95
|
+
return {
|
|
96
|
+
id: m.id,
|
|
97
|
+
name: m.name,
|
|
98
|
+
contextWindow: m.context_length,
|
|
99
|
+
maxTokens: m.top_provider?.max_completion_tokens,
|
|
100
|
+
cost,
|
|
101
|
+
input: ["text", "image"],
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ─── Google/Gemini Adapter ───────────────────────────────────────────────────
|
|
107
|
+
class GoogleDiscoveryAdapter {
|
|
108
|
+
constructor() {
|
|
109
|
+
this.provider = "google";
|
|
110
|
+
this.supportsDiscovery = true;
|
|
111
|
+
}
|
|
112
|
+
async fetchModels(apiKey, baseUrl) {
|
|
113
|
+
const url = `${baseUrl ?? "https://generativelanguage.googleapis.com"}/v1beta/models?key=${apiKey}`;
|
|
114
|
+
const response = await fetchWithTimeout(url);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`Google models API returned ${response.status}: ${response.statusText}`);
|
|
117
|
+
}
|
|
118
|
+
const data = (await response.json());
|
|
119
|
+
return (data.models ?? [])
|
|
120
|
+
.filter((m) => m.supportedGenerationMethods?.includes("generateContent"))
|
|
121
|
+
.map((m) => ({
|
|
122
|
+
id: m.name.replace("models/", ""),
|
|
123
|
+
name: m.displayName,
|
|
124
|
+
contextWindow: m.inputTokenLimit,
|
|
125
|
+
maxTokens: m.outputTokenLimit,
|
|
126
|
+
input: ["text", "image"],
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ─── Static Adapter (no discovery) ───────────────────────────────────────────
|
|
131
|
+
class StaticDiscoveryAdapter {
|
|
132
|
+
constructor(provider) {
|
|
133
|
+
this.supportsDiscovery = false;
|
|
134
|
+
this.provider = provider;
|
|
135
|
+
}
|
|
136
|
+
async fetchModels() {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ─── Registry ────────────────────────────────────────────────────────────────
|
|
141
|
+
const adapters = {
|
|
142
|
+
openai: new OpenAIDiscoveryAdapter(),
|
|
143
|
+
ollama: new OllamaDiscoveryAdapter(),
|
|
144
|
+
openrouter: new OpenRouterDiscoveryAdapter(),
|
|
145
|
+
google: new GoogleDiscoveryAdapter(),
|
|
146
|
+
anthropic: new StaticDiscoveryAdapter("anthropic"),
|
|
147
|
+
bedrock: new StaticDiscoveryAdapter("bedrock"),
|
|
148
|
+
"azure-openai": new StaticDiscoveryAdapter("azure-openai"),
|
|
149
|
+
groq: new StaticDiscoveryAdapter("groq"),
|
|
150
|
+
cerebras: new StaticDiscoveryAdapter("cerebras"),
|
|
151
|
+
xai: new StaticDiscoveryAdapter("xai"),
|
|
152
|
+
mistral: new StaticDiscoveryAdapter("mistral"),
|
|
153
|
+
};
|
|
154
|
+
export function getDiscoveryAdapter(provider) {
|
|
155
|
+
return adapters[provider] ?? new StaticDiscoveryAdapter(provider);
|
|
156
|
+
}
|
|
157
|
+
export function getDiscoverableProviders() {
|
|
158
|
+
return Object.entries(adapters)
|
|
159
|
+
.filter(([, adapter]) => adapter.supportsDiscovery)
|
|
160
|
+
.map(([name]) => name);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=model-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.js","sourceRoot":"","sources":["../../src/core/model-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provider discovery adapters for runtime model enumeration.\n * Each adapter implements ProviderDiscoveryAdapter to fetch models from provider APIs.\n */\n\nexport interface DiscoveredModel {\n\tid: string;\n\tname?: string;\n\tcontextWindow?: number;\n\tmaxTokens?: number;\n\treasoning?: boolean;\n\tinput?: (\"text\" | \"image\")[];\n\tcost?: { input: number; output: number; cacheRead: number; cacheWrite: number };\n}\n\nexport interface DiscoveryResult {\n\tprovider: string;\n\tmodels: DiscoveredModel[];\n\tfetchedAt: number;\n\terror?: string;\n}\n\nexport interface ProviderDiscoveryAdapter {\n\tprovider: string;\n\tsupportsDiscovery: boolean;\n\tfetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]>;\n}\n\n/** Per-provider TTLs in milliseconds */\nexport const DISCOVERY_TTLS: Record<string, number> = {\n\tollama: 5 * 60 * 1000, // 5 minutes (local, models change often)\n\topenai: 60 * 60 * 1000, // 1 hour\n\tgoogle: 60 * 60 * 1000, // 1 hour\n\topenrouter: 60 * 60 * 1000, // 1 hour\n\tdefault: 24 * 60 * 60 * 1000, // 24 hours\n};\n\nexport function getDefaultTTL(provider: string): number {\n\treturn DISCOVERY_TTLS[provider] ?? DISCOVERY_TTLS.default;\n}\n\nasync function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs = 5000): Promise<Response> {\n\tconst controller = new AbortController();\n\tconst timeout = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\treturn await fetch(url, { ...options, signal: controller.signal });\n\t} finally {\n\t\tclearTimeout(timeout);\n\t}\n}\n\n// ─── OpenAI Adapter ──────────────────────────────────────────────────────────\n\nconst OPENAI_EXCLUDED_PREFIXES = [\"embedding\", \"tts\", \"dall-e\", \"whisper\", \"text-embedding\", \"davinci\", \"babbage\"];\n\nclass OpenAIDiscoveryAdapter implements ProviderDiscoveryAdapter {\n\tprovider = \"openai\";\n\tsupportsDiscovery = true;\n\n\tasync fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {\n\t\tconst url = `${baseUrl ?? \"https://api.openai.com\"}/v1/models`;\n\t\tconst response = await fetchWithTimeout(url, {\n\t\t\theaders: { Authorization: `Bearer ${apiKey}` },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`OpenAI models API returned ${response.status}: ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as { data: Array<{ id: string; owned_by?: string }> };\n\t\treturn data.data\n\t\t\t.filter((m) => !OPENAI_EXCLUDED_PREFIXES.some((prefix) => m.id.startsWith(prefix)))\n\t\t\t.map((m) => ({\n\t\t\t\tid: m.id,\n\t\t\t\tname: m.id,\n\t\t\t\tinput: [\"text\" as const, \"image\" as const],\n\t\t\t}));\n\t}\n}\n\n// ─── Ollama Adapter ──────────────────────────────────────────────────────────\n\nclass OllamaDiscoveryAdapter implements ProviderDiscoveryAdapter {\n\tprovider = \"ollama\";\n\tsupportsDiscovery = true;\n\n\tasync fetchModels(_apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {\n\t\tconst url = `${baseUrl ?? \"http://localhost:11434\"}/api/tags`;\n\t\tconst response = await fetchWithTimeout(url);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Ollama tags API returned ${response.status}: ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tmodels: Array<{ name: string; size: number; details?: { parameter_size?: string } }>;\n\t\t};\n\n\t\treturn (data.models ?? []).map((m) => ({\n\t\t\tid: m.name,\n\t\t\tname: m.name,\n\t\t\tinput: [\"text\" as const],\n\t\t}));\n\t}\n}\n\n// ─── OpenRouter Adapter ──────────────────────────────────────────────────────\n\nclass OpenRouterDiscoveryAdapter implements ProviderDiscoveryAdapter {\n\tprovider = \"openrouter\";\n\tsupportsDiscovery = true;\n\n\tasync fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {\n\t\tconst url = `${baseUrl ?? \"https://openrouter.ai\"}/api/v1/models`;\n\t\tconst response = await fetchWithTimeout(url, {\n\t\t\theaders: { Authorization: `Bearer ${apiKey}` },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`OpenRouter models API returned ${response.status}: ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tdata: Array<{\n\t\t\t\tid: string;\n\t\t\t\tname: string;\n\t\t\t\tcontext_length?: number;\n\t\t\t\ttop_provider?: { max_completion_tokens?: number };\n\t\t\t\tpricing?: { prompt: string; completion: string };\n\t\t\t}>;\n\t\t};\n\n\t\treturn (data.data ?? []).map((m) => {\n\t\t\tconst cost =\n\t\t\t\tm.pricing?.prompt !== undefined && m.pricing?.completion !== undefined\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tinput: parseFloat(m.pricing.prompt) * 1_000_000,\n\t\t\t\t\t\t\toutput: parseFloat(m.pricing.completion) * 1_000_000,\n\t\t\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined;\n\n\t\t\treturn {\n\t\t\t\tid: m.id,\n\t\t\t\tname: m.name,\n\t\t\t\tcontextWindow: m.context_length,\n\t\t\t\tmaxTokens: m.top_provider?.max_completion_tokens,\n\t\t\t\tcost,\n\t\t\t\tinput: [\"text\" as const, \"image\" as const],\n\t\t\t};\n\t\t});\n\t}\n}\n\n// ─── Google/Gemini Adapter ───────────────────────────────────────────────────\n\nclass GoogleDiscoveryAdapter implements ProviderDiscoveryAdapter {\n\tprovider = \"google\";\n\tsupportsDiscovery = true;\n\n\tasync fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {\n\t\tconst url = `${baseUrl ?? \"https://generativelanguage.googleapis.com\"}/v1beta/models?key=${apiKey}`;\n\t\tconst response = await fetchWithTimeout(url);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Google models API returned ${response.status}: ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tmodels: Array<{\n\t\t\t\tname: string;\n\t\t\t\tdisplayName: string;\n\t\t\t\tsupportedGenerationMethods?: string[];\n\t\t\t\tinputTokenLimit?: number;\n\t\t\t\toutputTokenLimit?: number;\n\t\t\t}>;\n\t\t};\n\n\t\treturn (data.models ?? [])\n\t\t\t.filter((m) => m.supportedGenerationMethods?.includes(\"generateContent\"))\n\t\t\t.map((m) => ({\n\t\t\t\tid: m.name.replace(\"models/\", \"\"),\n\t\t\t\tname: m.displayName,\n\t\t\t\tcontextWindow: m.inputTokenLimit,\n\t\t\t\tmaxTokens: m.outputTokenLimit,\n\t\t\t\tinput: [\"text\" as const, \"image\" as const],\n\t\t\t}));\n\t}\n}\n\n// ─── Static Adapter (no discovery) ───────────────────────────────────────────\n\nclass StaticDiscoveryAdapter implements ProviderDiscoveryAdapter {\n\tprovider: string;\n\tsupportsDiscovery = false;\n\n\tconstructor(provider: string) {\n\t\tthis.provider = provider;\n\t}\n\n\tasync fetchModels(): Promise<DiscoveredModel[]> {\n\t\treturn [];\n\t}\n}\n\n// ─── Registry ────────────────────────────────────────────────────────────────\n\nconst adapters: Record<string, ProviderDiscoveryAdapter> = {\n\topenai: new OpenAIDiscoveryAdapter(),\n\tollama: new OllamaDiscoveryAdapter(),\n\topenrouter: new OpenRouterDiscoveryAdapter(),\n\tgoogle: new GoogleDiscoveryAdapter(),\n\tanthropic: new StaticDiscoveryAdapter(\"anthropic\"),\n\tbedrock: new StaticDiscoveryAdapter(\"bedrock\"),\n\t\"azure-openai\": new StaticDiscoveryAdapter(\"azure-openai\"),\n\tgroq: new StaticDiscoveryAdapter(\"groq\"),\n\tcerebras: new StaticDiscoveryAdapter(\"cerebras\"),\n\txai: new StaticDiscoveryAdapter(\"xai\"),\n\tmistral: new StaticDiscoveryAdapter(\"mistral\"),\n};\n\nexport function getDiscoveryAdapter(provider: string): ProviderDiscoveryAdapter {\n\treturn adapters[provider] ?? new StaticDiscoveryAdapter(provider);\n}\n\nexport function getDiscoverableProviders(): string[] {\n\treturn Object.entries(adapters)\n\t\t.filter(([, adapter]) => adapter.supportsDiscovery)\n\t\t.map(([name]) => name);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.test.d.ts","sourceRoot":"","sources":["../../src/core/model-discovery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { DISCOVERY_TTLS, getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter, } from "./model-discovery.js";
|
|
4
|
+
// ─── getDiscoveryAdapter ─────────────────────────────────────────────────────
|
|
5
|
+
describe("getDiscoveryAdapter", () => {
|
|
6
|
+
it("returns an adapter for openai", () => {
|
|
7
|
+
const adapter = getDiscoveryAdapter("openai");
|
|
8
|
+
assert.equal(adapter.provider, "openai");
|
|
9
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
10
|
+
});
|
|
11
|
+
it("returns an adapter for ollama", () => {
|
|
12
|
+
const adapter = getDiscoveryAdapter("ollama");
|
|
13
|
+
assert.equal(adapter.provider, "ollama");
|
|
14
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
15
|
+
});
|
|
16
|
+
it("returns an adapter for openrouter", () => {
|
|
17
|
+
const adapter = getDiscoveryAdapter("openrouter");
|
|
18
|
+
assert.equal(adapter.provider, "openrouter");
|
|
19
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
20
|
+
});
|
|
21
|
+
it("returns an adapter for google", () => {
|
|
22
|
+
const adapter = getDiscoveryAdapter("google");
|
|
23
|
+
assert.equal(adapter.provider, "google");
|
|
24
|
+
assert.equal(adapter.supportsDiscovery, true);
|
|
25
|
+
});
|
|
26
|
+
it("returns a static adapter for anthropic", () => {
|
|
27
|
+
const adapter = getDiscoveryAdapter("anthropic");
|
|
28
|
+
assert.equal(adapter.provider, "anthropic");
|
|
29
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
30
|
+
});
|
|
31
|
+
it("returns a static adapter for bedrock", () => {
|
|
32
|
+
const adapter = getDiscoveryAdapter("bedrock");
|
|
33
|
+
assert.equal(adapter.provider, "bedrock");
|
|
34
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
35
|
+
});
|
|
36
|
+
it("returns a static adapter for unknown providers", () => {
|
|
37
|
+
const adapter = getDiscoveryAdapter("unknown-provider");
|
|
38
|
+
assert.equal(adapter.provider, "unknown-provider");
|
|
39
|
+
assert.equal(adapter.supportsDiscovery, false);
|
|
40
|
+
});
|
|
41
|
+
it("static adapter fetchModels returns empty array", async () => {
|
|
42
|
+
const adapter = getDiscoveryAdapter("anthropic");
|
|
43
|
+
const models = await adapter.fetchModels("key");
|
|
44
|
+
assert.deepEqual(models, []);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// ─── getDiscoverableProviders ────────────────────────────────────────────────
|
|
48
|
+
describe("getDiscoverableProviders", () => {
|
|
49
|
+
it("returns only providers that support discovery", () => {
|
|
50
|
+
const providers = getDiscoverableProviders();
|
|
51
|
+
assert.ok(providers.includes("openai"));
|
|
52
|
+
assert.ok(providers.includes("ollama"));
|
|
53
|
+
assert.ok(providers.includes("openrouter"));
|
|
54
|
+
assert.ok(providers.includes("google"));
|
|
55
|
+
assert.ok(!providers.includes("anthropic"));
|
|
56
|
+
assert.ok(!providers.includes("bedrock"));
|
|
57
|
+
});
|
|
58
|
+
it("returns an array of strings", () => {
|
|
59
|
+
const providers = getDiscoverableProviders();
|
|
60
|
+
assert.ok(Array.isArray(providers));
|
|
61
|
+
for (const p of providers) {
|
|
62
|
+
assert.equal(typeof p, "string");
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
// ─── getDefaultTTL ───────────────────────────────────────────────────────────
|
|
67
|
+
describe("getDefaultTTL", () => {
|
|
68
|
+
it("returns 5 minutes for ollama", () => {
|
|
69
|
+
assert.equal(getDefaultTTL("ollama"), 5 * 60 * 1000);
|
|
70
|
+
});
|
|
71
|
+
it("returns 1 hour for openai", () => {
|
|
72
|
+
assert.equal(getDefaultTTL("openai"), 60 * 60 * 1000);
|
|
73
|
+
});
|
|
74
|
+
it("returns 1 hour for google", () => {
|
|
75
|
+
assert.equal(getDefaultTTL("google"), 60 * 60 * 1000);
|
|
76
|
+
});
|
|
77
|
+
it("returns 1 hour for openrouter", () => {
|
|
78
|
+
assert.equal(getDefaultTTL("openrouter"), 60 * 60 * 1000);
|
|
79
|
+
});
|
|
80
|
+
it("returns 24 hours for unknown providers", () => {
|
|
81
|
+
assert.equal(getDefaultTTL("some-custom"), 24 * 60 * 60 * 1000);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ─── DISCOVERY_TTLS ──────────────────────────────────────────────────────────
|
|
85
|
+
describe("DISCOVERY_TTLS", () => {
|
|
86
|
+
it("has expected keys", () => {
|
|
87
|
+
assert.ok("ollama" in DISCOVERY_TTLS);
|
|
88
|
+
assert.ok("openai" in DISCOVERY_TTLS);
|
|
89
|
+
assert.ok("google" in DISCOVERY_TTLS);
|
|
90
|
+
assert.ok("openrouter" in DISCOVERY_TTLS);
|
|
91
|
+
assert.ok("default" in DISCOVERY_TTLS);
|
|
92
|
+
});
|
|
93
|
+
it("all values are positive numbers", () => {
|
|
94
|
+
for (const [, value] of Object.entries(DISCOVERY_TTLS)) {
|
|
95
|
+
assert.equal(typeof value, "number");
|
|
96
|
+
assert.ok(value > 0);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=model-discovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.test.js","sourceRoot":"","sources":["../../src/core/model-discovery.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\nimport {\n\tDISCOVERY_TTLS,\n\tgetDefaultTTL,\n\tgetDiscoverableProviders,\n\tgetDiscoveryAdapter,\n} from \"./model-discovery.js\";\n\n// ─── getDiscoveryAdapter ─────────────────────────────────────────────────────\n\ndescribe(\"getDiscoveryAdapter\", () => {\n\tit(\"returns an adapter for openai\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"openai\");\n\t\tassert.equal(adapter.provider, \"openai\");\n\t\tassert.equal(adapter.supportsDiscovery, true);\n\t});\n\n\tit(\"returns an adapter for ollama\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"ollama\");\n\t\tassert.equal(adapter.provider, \"ollama\");\n\t\tassert.equal(adapter.supportsDiscovery, true);\n\t});\n\n\tit(\"returns an adapter for openrouter\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"openrouter\");\n\t\tassert.equal(adapter.provider, \"openrouter\");\n\t\tassert.equal(adapter.supportsDiscovery, true);\n\t});\n\n\tit(\"returns an adapter for google\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"google\");\n\t\tassert.equal(adapter.provider, \"google\");\n\t\tassert.equal(adapter.supportsDiscovery, true);\n\t});\n\n\tit(\"returns a static adapter for anthropic\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"anthropic\");\n\t\tassert.equal(adapter.provider, \"anthropic\");\n\t\tassert.equal(adapter.supportsDiscovery, false);\n\t});\n\n\tit(\"returns a static adapter for bedrock\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"bedrock\");\n\t\tassert.equal(adapter.provider, \"bedrock\");\n\t\tassert.equal(adapter.supportsDiscovery, false);\n\t});\n\n\tit(\"returns a static adapter for unknown providers\", () => {\n\t\tconst adapter = getDiscoveryAdapter(\"unknown-provider\");\n\t\tassert.equal(adapter.provider, \"unknown-provider\");\n\t\tassert.equal(adapter.supportsDiscovery, false);\n\t});\n\n\tit(\"static adapter fetchModels returns empty array\", async () => {\n\t\tconst adapter = getDiscoveryAdapter(\"anthropic\");\n\t\tconst models = await adapter.fetchModels(\"key\");\n\t\tassert.deepEqual(models, []);\n\t});\n});\n\n// ─── getDiscoverableProviders ────────────────────────────────────────────────\n\ndescribe(\"getDiscoverableProviders\", () => {\n\tit(\"returns only providers that support discovery\", () => {\n\t\tconst providers = getDiscoverableProviders();\n\t\tassert.ok(providers.includes(\"openai\"));\n\t\tassert.ok(providers.includes(\"ollama\"));\n\t\tassert.ok(providers.includes(\"openrouter\"));\n\t\tassert.ok(providers.includes(\"google\"));\n\t\tassert.ok(!providers.includes(\"anthropic\"));\n\t\tassert.ok(!providers.includes(\"bedrock\"));\n\t});\n\n\tit(\"returns an array of strings\", () => {\n\t\tconst providers = getDiscoverableProviders();\n\t\tassert.ok(Array.isArray(providers));\n\t\tfor (const p of providers) {\n\t\t\tassert.equal(typeof p, \"string\");\n\t\t}\n\t});\n});\n\n// ─── getDefaultTTL ───────────────────────────────────────────────────────────\n\ndescribe(\"getDefaultTTL\", () => {\n\tit(\"returns 5 minutes for ollama\", () => {\n\t\tassert.equal(getDefaultTTL(\"ollama\"), 5 * 60 * 1000);\n\t});\n\n\tit(\"returns 1 hour for openai\", () => {\n\t\tassert.equal(getDefaultTTL(\"openai\"), 60 * 60 * 1000);\n\t});\n\n\tit(\"returns 1 hour for google\", () => {\n\t\tassert.equal(getDefaultTTL(\"google\"), 60 * 60 * 1000);\n\t});\n\n\tit(\"returns 1 hour for openrouter\", () => {\n\t\tassert.equal(getDefaultTTL(\"openrouter\"), 60 * 60 * 1000);\n\t});\n\n\tit(\"returns 24 hours for unknown providers\", () => {\n\t\tassert.equal(getDefaultTTL(\"some-custom\"), 24 * 60 * 60 * 1000);\n\t});\n});\n\n// ─── DISCOVERY_TTLS ──────────────────────────────────────────────────────────\n\ndescribe(\"DISCOVERY_TTLS\", () => {\n\tit(\"has expected keys\", () => {\n\t\tassert.ok(\"ollama\" in DISCOVERY_TTLS);\n\t\tassert.ok(\"openai\" in DISCOVERY_TTLS);\n\t\tassert.ok(\"google\" in DISCOVERY_TTLS);\n\t\tassert.ok(\"openrouter\" in DISCOVERY_TTLS);\n\t\tassert.ok(\"default\" in DISCOVERY_TTLS);\n\t});\n\n\tit(\"all values are positive numbers\", () => {\n\t\tfor (const [, value] of Object.entries(DISCOVERY_TTLS)) {\n\t\t\tassert.equal(typeof value, \"number\");\n\t\t\tassert.ok(value > 0);\n\t\t}\n\t});\n});\n"]}
|