gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.82e249f7b
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/dist/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +13 -2
- package/dist/resources/extensions/gsd/auto-start.js +12 -7
- package/dist/resources/extensions/gsd/auto.js +4 -1
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -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 +76 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +16 -1
- package/src/resources/extensions/gsd/auto-start.ts +12 -7
- package/src/resources/extensions/gsd/auto.ts +4 -1
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → ecSsu49rxxcpbNmVP4mLD}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → ecSsu49rxxcpbNmVP4mLD}/_ssgManifest.js +0 -0
|
@@ -26,6 +26,14 @@ export interface ProviderDiscoveryAdapter {
|
|
|
26
26
|
fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export const OPENAI_COMPAT_DISCOVERY_APIS = new Set([
|
|
30
|
+
"openai",
|
|
31
|
+
"openai-completions",
|
|
32
|
+
"openai-responses",
|
|
33
|
+
"openai-codex-responses",
|
|
34
|
+
"azure-openai-responses",
|
|
35
|
+
]);
|
|
36
|
+
|
|
29
37
|
/** Per-provider TTLs in milliseconds */
|
|
30
38
|
export const DISCOVERY_TTLS: Record<string, number> = {
|
|
31
39
|
ollama: 5 * 60 * 1000, // 5 minutes (local, models change often)
|
|
@@ -53,10 +61,77 @@ async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutM
|
|
|
53
61
|
|
|
54
62
|
const OPENAI_EXCLUDED_PREFIXES = ["embedding", "tts", "dall-e", "whisper", "text-embedding", "davinci", "babbage"];
|
|
55
63
|
|
|
64
|
+
function asPositiveNumber(value: unknown): number | undefined {
|
|
65
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
const n = Number.parseFloat(value);
|
|
68
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function pickFirstPositiveNumber(record: Record<string, unknown>, keys: string[]): number | undefined {
|
|
74
|
+
for (const key of keys) {
|
|
75
|
+
const value = asPositiveNumber(record[key]);
|
|
76
|
+
if (value !== undefined) return value;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function discoverInputModalities(rawModel: Record<string, unknown>, id: string): Array<"text" | "image"> {
|
|
82
|
+
const directModalities = rawModel.input_modalities;
|
|
83
|
+
const capabilitiesModalities = (rawModel.capabilities as Record<string, unknown> | undefined)?.input_modalities;
|
|
84
|
+
const source = Array.isArray(directModalities)
|
|
85
|
+
? directModalities
|
|
86
|
+
: Array.isArray(capabilitiesModalities)
|
|
87
|
+
? capabilitiesModalities
|
|
88
|
+
: [];
|
|
89
|
+
const supportsImage = source.some((m) => typeof m === "string" && /image|vision/i.test(m))
|
|
90
|
+
|| /vision|image|omni|multimodal/i.test(id);
|
|
91
|
+
return supportsImage ? ["text", "image"] : ["text"];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseOpenAICompatibleModel(rawModel: Record<string, unknown>): DiscoveredModel | undefined {
|
|
95
|
+
const id = typeof rawModel.id === "string" ? rawModel.id : "";
|
|
96
|
+
if (!id) return undefined;
|
|
97
|
+
if (OPENAI_EXCLUDED_PREFIXES.some((prefix) => id.startsWith(prefix))) return undefined;
|
|
98
|
+
|
|
99
|
+
const contextWindow = pickFirstPositiveNumber(rawModel, [
|
|
100
|
+
"context_window",
|
|
101
|
+
"context_length",
|
|
102
|
+
"max_context_length",
|
|
103
|
+
"max_input_tokens",
|
|
104
|
+
"input_token_limit",
|
|
105
|
+
"max_model_len",
|
|
106
|
+
]);
|
|
107
|
+
const maxTokens = pickFirstPositiveNumber(rawModel, [
|
|
108
|
+
"max_output_tokens",
|
|
109
|
+
"output_token_limit",
|
|
110
|
+
"max_completion_tokens",
|
|
111
|
+
"max_tokens",
|
|
112
|
+
]);
|
|
113
|
+
const reasoning = rawModel.reasoning === true
|
|
114
|
+
|| rawModel.supports_reasoning === true
|
|
115
|
+
|| ((rawModel.capabilities as Record<string, unknown> | undefined)?.reasoning === true);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
id,
|
|
119
|
+
name: typeof rawModel.name === "string" && rawModel.name.length > 0 ? rawModel.name : id,
|
|
120
|
+
contextWindow,
|
|
121
|
+
maxTokens,
|
|
122
|
+
reasoning,
|
|
123
|
+
input: discoverInputModalities(rawModel, id),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
56
127
|
class OpenAIDiscoveryAdapter implements ProviderDiscoveryAdapter {
|
|
57
|
-
provider
|
|
128
|
+
provider: string;
|
|
58
129
|
supportsDiscovery = true;
|
|
59
130
|
|
|
131
|
+
constructor(provider: string) {
|
|
132
|
+
this.provider = provider;
|
|
133
|
+
}
|
|
134
|
+
|
|
60
135
|
async fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {
|
|
61
136
|
const url = `${baseUrl ?? "https://api.openai.com"}/v1/models`;
|
|
62
137
|
const response = await fetchWithTimeout(url, {
|
|
@@ -67,14 +142,10 @@ class OpenAIDiscoveryAdapter implements ProviderDiscoveryAdapter {
|
|
|
67
142
|
throw new Error(`OpenAI models API returned ${response.status}: ${response.statusText}`);
|
|
68
143
|
}
|
|
69
144
|
|
|
70
|
-
const data = (await response.json()) as { data
|
|
71
|
-
return data.data
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
id: m.id,
|
|
75
|
-
name: m.id,
|
|
76
|
-
input: ["text" as const, "image" as const],
|
|
77
|
-
}));
|
|
145
|
+
const data = (await response.json()) as { data?: Array<Record<string, unknown>> };
|
|
146
|
+
return (data.data ?? [])
|
|
147
|
+
.map((m) => parseOpenAICompatibleModel(m))
|
|
148
|
+
.filter((m): m is DiscoveredModel => !!m);
|
|
78
149
|
}
|
|
79
150
|
}
|
|
80
151
|
|
|
@@ -207,7 +278,7 @@ class StaticDiscoveryAdapter implements ProviderDiscoveryAdapter {
|
|
|
207
278
|
// ─── Registry ────────────────────────────────────────────────────────────────
|
|
208
279
|
|
|
209
280
|
const adapters: Record<string, ProviderDiscoveryAdapter> = {
|
|
210
|
-
openai: new OpenAIDiscoveryAdapter(),
|
|
281
|
+
openai: new OpenAIDiscoveryAdapter("openai"),
|
|
211
282
|
ollama: new OllamaDiscoveryAdapter(),
|
|
212
283
|
openrouter: new OpenRouterDiscoveryAdapter(),
|
|
213
284
|
google: new GoogleDiscoveryAdapter(),
|
|
@@ -220,8 +291,24 @@ const adapters: Record<string, ProviderDiscoveryAdapter> = {
|
|
|
220
291
|
mistral: new StaticDiscoveryAdapter("mistral"),
|
|
221
292
|
};
|
|
222
293
|
|
|
223
|
-
export function
|
|
224
|
-
|
|
294
|
+
export function supportsDiscoveryForApi(api: string | undefined): boolean {
|
|
295
|
+
if (!api) return false;
|
|
296
|
+
return OPENAI_COMPAT_DISCOVERY_APIS.has(api);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function getDiscoveryAdapter(provider: string, providerApis?: Iterable<string>): ProviderDiscoveryAdapter {
|
|
300
|
+
const known = adapters[provider];
|
|
301
|
+
if (known) return known;
|
|
302
|
+
|
|
303
|
+
if (providerApis) {
|
|
304
|
+
for (const api of providerApis) {
|
|
305
|
+
if (supportsDiscoveryForApi(api)) {
|
|
306
|
+
return new OpenAIDiscoveryAdapter(provider);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return new StaticDiscoveryAdapter(provider);
|
|
225
312
|
}
|
|
226
313
|
|
|
227
314
|
export function getDiscoverableProviders(): string[] {
|
|
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, it } from "node:test";
|
|
|
6
6
|
import { AuthStorage } from "./auth-storage.js";
|
|
7
7
|
import { ModelDiscoveryCache } from "./discovery-cache.js";
|
|
8
8
|
import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter } from "./model-discovery.js";
|
|
9
|
+
import { ModelRegistry } from "./model-registry.js";
|
|
9
10
|
|
|
10
11
|
let testDir: string;
|
|
11
12
|
|
|
@@ -133,3 +134,77 @@ describe("Discovery TTL configuration", () => {
|
|
|
133
134
|
assert.equal(customTTL, defaultTTL);
|
|
134
135
|
});
|
|
135
136
|
});
|
|
137
|
+
|
|
138
|
+
describe("ModelRegistry discovery — OpenAI-compatible custom providers", () => {
|
|
139
|
+
it("discovers custom OpenAI-compatible providers and maps capability metadata", async () => {
|
|
140
|
+
const providerName = `minimax-openai-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
141
|
+
const modelsPath = join(testDir, "models.json");
|
|
142
|
+
writeFileSync(
|
|
143
|
+
modelsPath,
|
|
144
|
+
JSON.stringify(
|
|
145
|
+
{
|
|
146
|
+
providers: {
|
|
147
|
+
[providerName]: {
|
|
148
|
+
baseUrl: "https://api.minimax.example",
|
|
149
|
+
apiKey: "minimax-test-key",
|
|
150
|
+
api: "openai-completions",
|
|
151
|
+
models: [{ id: "bootstrap-model" }],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
null,
|
|
156
|
+
2,
|
|
157
|
+
),
|
|
158
|
+
"utf-8",
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const prevFetch = globalThis.fetch;
|
|
162
|
+
let requestedUrl = "";
|
|
163
|
+
globalThis.fetch = (async (input: string | URL | Request) => {
|
|
164
|
+
requestedUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
165
|
+
return new Response(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
data: [
|
|
168
|
+
{
|
|
169
|
+
id: "MiniMax-M2.7-highspeed",
|
|
170
|
+
name: "MiniMax M2.7 Highspeed",
|
|
171
|
+
context_window: 165000,
|
|
172
|
+
max_output_tokens: 32768,
|
|
173
|
+
supports_reasoning: true,
|
|
174
|
+
input_modalities: ["text", "image"],
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
}),
|
|
178
|
+
{
|
|
179
|
+
status: 200,
|
|
180
|
+
headers: { "content-type": "application/json" },
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
}) as typeof globalThis.fetch;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const registry = new ModelRegistry(AuthStorage.inMemory({}), modelsPath);
|
|
187
|
+
// Guard against global cache leakage from prior test runs.
|
|
188
|
+
registry.getDiscoveryCache().clear(providerName);
|
|
189
|
+
const results = await registry.discoverModels([providerName]);
|
|
190
|
+
|
|
191
|
+
const discovery = results.find((r) => r.provider === providerName);
|
|
192
|
+
assert.ok(discovery, "discovery result should include custom provider");
|
|
193
|
+
assert.equal(discovery?.error, undefined, "custom provider discovery should succeed");
|
|
194
|
+
assert.equal(requestedUrl, "https://api.minimax.example/v1/models");
|
|
195
|
+
|
|
196
|
+
const discovered = registry
|
|
197
|
+
.getAllWithDiscovered()
|
|
198
|
+
.find((m) => m.provider === providerName && m.id === "MiniMax-M2.7-highspeed");
|
|
199
|
+
assert.ok(discovered, "discovered model should be merged into model list");
|
|
200
|
+
assert.equal(discovered?.api, "openai-completions");
|
|
201
|
+
assert.equal(discovered?.baseUrl, "https://api.minimax.example");
|
|
202
|
+
assert.equal(discovered?.contextWindow, 165000);
|
|
203
|
+
assert.equal(discovered?.maxTokens, 32768);
|
|
204
|
+
assert.equal(discovered?.reasoning, true);
|
|
205
|
+
assert.deepEqual(discovered?.input, ["text", "image"]);
|
|
206
|
+
} finally {
|
|
207
|
+
globalThis.fetch = prevFetch;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -29,7 +29,7 @@ import { getAgentDir } from "../config.js";
|
|
|
29
29
|
import type { AuthStorage } from "./auth-storage.js";
|
|
30
30
|
import { ModelDiscoveryCache } from "./discovery-cache.js";
|
|
31
31
|
import type { DiscoveredModel, DiscoveryResult } from "./model-discovery.js";
|
|
32
|
-
import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter } from "./model-discovery.js";
|
|
32
|
+
import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter, supportsDiscoveryForApi } from "./model-discovery.js";
|
|
33
33
|
import { clearConfigValueCache, resolveConfigValue, resolveHeaders } from "./resolve-config-value.js";
|
|
34
34
|
import { isLocalModel } from "./local-model-check.js";
|
|
35
35
|
|
|
@@ -788,11 +788,12 @@ export class ModelRegistry {
|
|
|
788
788
|
* Results are cached and merged into the registry (never overrides existing models).
|
|
789
789
|
*/
|
|
790
790
|
async discoverModels(providers?: string[]): Promise<DiscoveryResult[]> {
|
|
791
|
-
const targetProviders = providers ??
|
|
791
|
+
const targetProviders = providers ?? this.getAutoDiscoverableProviders();
|
|
792
792
|
const results: DiscoveryResult[] = [];
|
|
793
793
|
|
|
794
794
|
for (const providerName of targetProviders) {
|
|
795
|
-
const
|
|
795
|
+
const providerApis = this.getProviderApis(providerName);
|
|
796
|
+
const adapter = getDiscoveryAdapter(providerName, providerApis);
|
|
796
797
|
if (!adapter.supportsDiscovery) continue;
|
|
797
798
|
|
|
798
799
|
// Skip if cache is still fresh
|
|
@@ -812,8 +813,10 @@ export class ModelRegistry {
|
|
|
812
813
|
const apiKey = await this.authStorage.getApiKey(providerName);
|
|
813
814
|
if (!apiKey && !this.isProviderRequestReady(providerName)) continue;
|
|
814
815
|
|
|
815
|
-
const
|
|
816
|
-
|
|
816
|
+
const baseUrl = this.getProviderBaseUrl(providerName);
|
|
817
|
+
const models = await adapter.fetchModels(apiKey ?? "", baseUrl);
|
|
818
|
+
const ttlMs = this.getDiscoveryTtl(providerName, providerApis);
|
|
819
|
+
this.discoveryCache.set(providerName, models, ttlMs);
|
|
817
820
|
results.push({
|
|
818
821
|
provider: providerName,
|
|
819
822
|
models,
|
|
@@ -865,24 +868,97 @@ export class ModelRegistry {
|
|
|
865
868
|
const converted: Model<Api>[] = [];
|
|
866
869
|
for (const result of results) {
|
|
867
870
|
if (result.error) continue;
|
|
871
|
+
const providerDefaults = this.getDiscoveryProviderDefaults(result.provider);
|
|
868
872
|
for (const dm of result.models) {
|
|
869
873
|
converted.push({
|
|
870
874
|
id: dm.id,
|
|
871
875
|
name: dm.name ?? dm.id,
|
|
872
|
-
api:
|
|
876
|
+
api: providerDefaults.api,
|
|
873
877
|
provider: result.provider,
|
|
874
|
-
baseUrl:
|
|
878
|
+
baseUrl: providerDefaults.baseUrl,
|
|
875
879
|
reasoning: dm.reasoning ?? false,
|
|
876
|
-
input: dm.input ??
|
|
880
|
+
input: dm.input ?? providerDefaults.input,
|
|
877
881
|
cost: dm.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
878
|
-
contextWindow: dm.contextWindow ??
|
|
879
|
-
maxTokens: dm.maxTokens ??
|
|
882
|
+
contextWindow: dm.contextWindow ?? providerDefaults.contextWindow,
|
|
883
|
+
maxTokens: dm.maxTokens ?? providerDefaults.maxTokens,
|
|
880
884
|
} as Model<Api>);
|
|
881
885
|
}
|
|
882
886
|
}
|
|
883
887
|
return converted;
|
|
884
888
|
}
|
|
885
889
|
|
|
890
|
+
private getProviderApis(provider: string): Set<string> {
|
|
891
|
+
const apis = new Set<string>();
|
|
892
|
+
for (const model of this.models) {
|
|
893
|
+
if (model.provider === provider && typeof model.api === "string" && model.api.length > 0) {
|
|
894
|
+
apis.add(model.api);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const providerConfig = this.registeredProviders.get(provider);
|
|
899
|
+
if (providerConfig?.api) apis.add(providerConfig.api);
|
|
900
|
+
for (const modelDef of providerConfig?.models ?? []) {
|
|
901
|
+
if (modelDef.api) apis.add(modelDef.api);
|
|
902
|
+
}
|
|
903
|
+
return apis;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private getAutoDiscoverableProviders(): string[] {
|
|
907
|
+
const discoverable = new Set<string>(getDiscoverableProviders());
|
|
908
|
+
for (const provider of new Set(this.models.map((m) => m.provider))) {
|
|
909
|
+
const apis = this.getProviderApis(provider);
|
|
910
|
+
for (const api of apis) {
|
|
911
|
+
if (supportsDiscoveryForApi(api)) {
|
|
912
|
+
discoverable.add(provider);
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return [...discoverable];
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
private getProviderBaseUrl(provider: string): string | undefined {
|
|
921
|
+
const fromModels = this.models.find((m) => m.provider === provider && typeof m.baseUrl === "string" && m.baseUrl.length > 0);
|
|
922
|
+
if (fromModels?.baseUrl) return fromModels.baseUrl;
|
|
923
|
+
return this.registeredProviders.get(provider)?.baseUrl;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private getDiscoveryProviderDefaults(provider: string): {
|
|
927
|
+
api: Api;
|
|
928
|
+
baseUrl: string;
|
|
929
|
+
input: ("text" | "image")[];
|
|
930
|
+
contextWindow: number;
|
|
931
|
+
maxTokens: number;
|
|
932
|
+
} {
|
|
933
|
+
const first = this.models.find((m) => m.provider === provider);
|
|
934
|
+
if (first) {
|
|
935
|
+
return {
|
|
936
|
+
api: first.api,
|
|
937
|
+
baseUrl: first.baseUrl,
|
|
938
|
+
input: first.input,
|
|
939
|
+
contextWindow: first.contextWindow,
|
|
940
|
+
maxTokens: first.maxTokens,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return {
|
|
945
|
+
api: "openai-completions",
|
|
946
|
+
baseUrl: this.registeredProviders.get(provider)?.baseUrl ?? "",
|
|
947
|
+
input: ["text"],
|
|
948
|
+
contextWindow: 128000,
|
|
949
|
+
maxTokens: 16384,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private getDiscoveryTtl(provider: string, providerApis: Set<string>): number {
|
|
954
|
+
for (const api of providerApis) {
|
|
955
|
+
if (supportsDiscoveryForApi(api)) {
|
|
956
|
+
return getDefaultTTL("openai");
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return getDefaultTTL(provider);
|
|
960
|
+
}
|
|
961
|
+
|
|
886
962
|
/**
|
|
887
963
|
* Check if a model's baseUrl points to a local endpoint.
|
|
888
964
|
* Delegates to standalone isLocalModel() function.
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type TUI,
|
|
13
13
|
} from "@gsd/pi-tui";
|
|
14
14
|
import type { AuthStorage } from "../../../core/auth-storage.js";
|
|
15
|
-
import { getDiscoverableProviders } from "../../../core/model-discovery.js";
|
|
15
|
+
import { getDiscoverableProviders, getDiscoveryAdapter } from "../../../core/model-discovery.js";
|
|
16
16
|
import { providerDisplayName } from "./model-selector.js";
|
|
17
17
|
import type { ModelRegistry } from "../../../core/model-registry.js";
|
|
18
18
|
import { ModelsJsonWriter } from "../../../core/models-json-writer.js";
|
|
@@ -102,12 +102,21 @@ export class ProviderManagerComponent extends Container implements Focusable {
|
|
|
102
102
|
|
|
103
103
|
this.providers = Array.from(providerNames)
|
|
104
104
|
.sort()
|
|
105
|
-
.map((name) =>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
.map((name) => {
|
|
106
|
+
const providerApis = new Set(
|
|
107
|
+
allModels
|
|
108
|
+
.filter((m) => m.provider === name)
|
|
109
|
+
.map((m) => m.api)
|
|
110
|
+
.filter((api): api is string => typeof api === "string" && api.length > 0),
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
name,
|
|
114
|
+
hasAuth: this.authStorage.hasAuth(name),
|
|
115
|
+
supportsDiscovery:
|
|
116
|
+
discoverableSet.has(name) || getDiscoveryAdapter(name, providerApis).supportsDiscovery,
|
|
117
|
+
modelCount: providerModelCounts.get(name) ?? 0,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
111
120
|
this.clampSelectedIndex();
|
|
112
121
|
}
|
|
113
122
|
|