@oh-my-pi/pi-coding-agent 12.10.1 → 12.11.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/CHANGELOG.md +31 -0
- package/package.json +7 -7
- package/src/cli/web-search-cli.ts +1 -0
- package/src/commands/web-search.ts +1 -0
- package/src/commit/model-selection.ts +2 -2
- package/src/config/model-registry.ts +259 -308
- package/src/config/model-resolver.ts +5 -69
- package/src/config/settings-schema.ts +10 -0
- package/src/modes/components/model-selector.ts +24 -0
- package/src/modes/components/settings-defs.ts +11 -1
- package/src/modes/controllers/selector-controller.ts +4 -0
- package/src/modes/interactive-mode.ts +1 -0
- package/src/priority.json +28 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/session/agent-session.ts +41 -8
- package/src/session/auth-storage.ts +75 -0
- package/src/tools/browser.ts +30 -2
- package/src/utils/title-generator.ts +3 -2
- package/src/web/search/index.ts +4 -4
- package/src/web/search/provider.ts +4 -1
- package/src/web/search/providers/anthropic.ts +2 -17
- package/src/web/search/providers/synthetic.ts +136 -0
- package/src/web/search/types.ts +10 -1
|
@@ -1,38 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Api,
|
|
3
3
|
type AssistantMessageEventStream,
|
|
4
|
-
anthropicModelManagerOptions,
|
|
5
4
|
type Context,
|
|
6
|
-
cerebrasModelManagerOptions,
|
|
7
5
|
createModelManager,
|
|
8
|
-
cursorModelManagerOptions,
|
|
9
6
|
getBundledModels,
|
|
10
7
|
getBundledProviders,
|
|
11
8
|
getGitHubCopilotBaseUrl,
|
|
12
|
-
githubCopilotModelManagerOptions,
|
|
13
9
|
googleAntigravityModelManagerOptions,
|
|
14
10
|
googleGeminiCliModelManagerOptions,
|
|
15
|
-
googleModelManagerOptions,
|
|
16
|
-
groqModelManagerOptions,
|
|
17
|
-
kimiCodeModelManagerOptions,
|
|
18
11
|
type Model,
|
|
19
12
|
type ModelManagerOptions,
|
|
20
|
-
mistralModelManagerOptions,
|
|
21
13
|
normalizeDomain,
|
|
22
14
|
type OAuthCredentials,
|
|
23
15
|
type OAuthLoginCallbacks,
|
|
24
16
|
openaiCodexModelManagerOptions,
|
|
25
|
-
|
|
26
|
-
opencodeModelManagerOptions,
|
|
27
|
-
openrouterModelManagerOptions,
|
|
17
|
+
PROVIDER_DESCRIPTORS,
|
|
28
18
|
registerCustomApi,
|
|
29
19
|
registerOAuthProvider,
|
|
30
20
|
type SimpleStreamOptions,
|
|
31
|
-
syntheticModelManagerOptions,
|
|
32
21
|
unregisterCustomApis,
|
|
33
22
|
unregisterOAuthProviders,
|
|
34
|
-
vercelAiGatewayModelManagerOptions,
|
|
35
|
-
xaiModelManagerOptions,
|
|
36
23
|
} from "@oh-my-pi/pi-ai";
|
|
37
24
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
38
25
|
import { type Static, Type } from "@sinclair/typebox";
|
|
@@ -177,55 +164,104 @@ type ModelsConfig = Static<typeof ModelsConfigSchema>;
|
|
|
177
164
|
type ProviderAuthMode = Static<typeof ProviderAuthSchema>;
|
|
178
165
|
type ProviderDiscovery = Static<typeof ProviderDiscoverySchema>;
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
"models",
|
|
182
|
-
config => {
|
|
183
|
-
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
184
|
-
const hasProviderApi = !!providerConfig.api;
|
|
185
|
-
const models = providerConfig.models ?? [];
|
|
186
|
-
|
|
187
|
-
if (models.length === 0) {
|
|
188
|
-
// Override-only config: needs baseUrl, modelOverrides, or discovery
|
|
189
|
-
const hasModelOverrides =
|
|
190
|
-
providerConfig.modelOverrides && Object.keys(providerConfig.modelOverrides).length > 0;
|
|
191
|
-
if (!providerConfig.baseUrl && !hasModelOverrides && !providerConfig.discovery) {
|
|
192
|
-
throw new Error(
|
|
193
|
-
`Provider ${providerName}: must specify "baseUrl", "modelOverrides", "discovery", or "models".`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
// Full replacement: needs baseUrl and apiKey unless auth is disabled
|
|
198
|
-
if (!providerConfig.baseUrl) {
|
|
199
|
-
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
|
|
200
|
-
}
|
|
201
|
-
if (!providerConfig.apiKey && providerConfig.auth !== "none") {
|
|
202
|
-
throw new Error(
|
|
203
|
-
`Provider ${providerName}: "apiKey" is required when defining custom models unless auth is "none".`,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
167
|
+
type ProviderValidationMode = "models-config" | "runtime-register";
|
|
207
168
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
169
|
+
interface ProviderValidationModel {
|
|
170
|
+
id: string;
|
|
171
|
+
api?: Api;
|
|
172
|
+
contextWindow?: number;
|
|
173
|
+
maxTokens?: number;
|
|
174
|
+
}
|
|
211
175
|
|
|
212
|
-
|
|
213
|
-
|
|
176
|
+
interface ProviderValidationConfig {
|
|
177
|
+
baseUrl?: string;
|
|
178
|
+
apiKey?: string;
|
|
179
|
+
api?: Api;
|
|
180
|
+
auth?: ProviderAuthMode;
|
|
181
|
+
oauthConfigured?: boolean;
|
|
182
|
+
discovery?: ProviderDiscovery;
|
|
183
|
+
modelOverrides?: Record<string, unknown>;
|
|
184
|
+
models: ProviderValidationModel[];
|
|
185
|
+
}
|
|
214
186
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
187
|
+
function validateProviderConfiguration(
|
|
188
|
+
providerName: string,
|
|
189
|
+
config: ProviderValidationConfig,
|
|
190
|
+
mode: ProviderValidationMode,
|
|
191
|
+
): void {
|
|
192
|
+
const hasProviderApi = !!config.api;
|
|
193
|
+
const models = config.models;
|
|
194
|
+
|
|
195
|
+
if (models.length === 0) {
|
|
196
|
+
if (mode === "models-config") {
|
|
197
|
+
const hasModelOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
|
|
198
|
+
if (!config.baseUrl && !hasModelOverrides && !config.discovery) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Provider ${providerName}: must specify "baseUrl", "modelOverrides", "discovery", or "models".`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
if (!config.baseUrl) {
|
|
206
|
+
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
|
|
207
|
+
}
|
|
208
|
+
const requiresAuth =
|
|
209
|
+
mode === "runtime-register"
|
|
210
|
+
? !config.apiKey && !config.oauthConfigured
|
|
211
|
+
: !config.apiKey && (config.auth ?? "apiKey") !== "none";
|
|
212
|
+
if (requiresAuth) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
mode === "runtime-register"
|
|
215
|
+
? `Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`
|
|
216
|
+
: `Provider ${providerName}: "apiKey" is required when defining custom models unless auth is "none".`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (mode === "models-config" && config.discovery && !config.api) {
|
|
222
|
+
throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
|
|
223
|
+
}
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
for (const modelDef of models) {
|
|
226
|
+
if (!hasProviderApi && !modelDef.api) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
mode === "runtime-register"
|
|
229
|
+
? `Provider ${providerName}, model ${modelDef.id}: no "api" specified.`
|
|
230
|
+
: `Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (!modelDef.id) {
|
|
234
|
+
throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
235
|
+
}
|
|
236
|
+
if (mode === "models-config") {
|
|
237
|
+
if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0) {
|
|
238
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
239
|
+
}
|
|
240
|
+
if (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0) {
|
|
241
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
227
242
|
}
|
|
228
243
|
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
|
|
248
|
+
"models",
|
|
249
|
+
config => {
|
|
250
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
251
|
+
validateProviderConfiguration(
|
|
252
|
+
providerName,
|
|
253
|
+
{
|
|
254
|
+
baseUrl: providerConfig.baseUrl,
|
|
255
|
+
apiKey: providerConfig.apiKey,
|
|
256
|
+
api: providerConfig.api as Api | undefined,
|
|
257
|
+
auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
|
|
258
|
+
discovery: providerConfig.discovery as ProviderDiscovery | undefined,
|
|
259
|
+
modelOverrides: providerConfig.modelOverrides,
|
|
260
|
+
models: (providerConfig.models ?? []) as ProviderValidationModel[],
|
|
261
|
+
},
|
|
262
|
+
"models-config",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
229
265
|
},
|
|
230
266
|
);
|
|
231
267
|
|
|
@@ -356,6 +392,72 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
356
392
|
return result;
|
|
357
393
|
}
|
|
358
394
|
|
|
395
|
+
interface CustomModelDefinitionLike {
|
|
396
|
+
id: string;
|
|
397
|
+
name?: string;
|
|
398
|
+
api?: Api;
|
|
399
|
+
reasoning?: boolean;
|
|
400
|
+
input?: ("text" | "image")[];
|
|
401
|
+
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
402
|
+
contextWindow?: number;
|
|
403
|
+
maxTokens?: number;
|
|
404
|
+
headers?: Record<string, string>;
|
|
405
|
+
compat?: Model<Api>["compat"];
|
|
406
|
+
contextPromotionTarget?: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
interface CustomModelBuildOptions {
|
|
410
|
+
useDefaults: boolean;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function mergeCustomModelHeaders(
|
|
414
|
+
providerHeaders: Record<string, string> | undefined,
|
|
415
|
+
modelHeaders: Record<string, string> | undefined,
|
|
416
|
+
authHeader: boolean | undefined,
|
|
417
|
+
apiKeyConfig: string | undefined,
|
|
418
|
+
): Record<string, string> | undefined {
|
|
419
|
+
let headers = providerHeaders || modelHeaders ? { ...providerHeaders, ...modelHeaders } : undefined;
|
|
420
|
+
if (authHeader && apiKeyConfig) {
|
|
421
|
+
const resolvedKey = resolveApiKeyConfig(apiKeyConfig);
|
|
422
|
+
if (resolvedKey) {
|
|
423
|
+
headers = { ...headers, Authorization: `Bearer ${resolvedKey}` };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return headers;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function buildCustomModel(
|
|
430
|
+
providerName: string,
|
|
431
|
+
providerBaseUrl: string,
|
|
432
|
+
providerApi: Api | undefined,
|
|
433
|
+
providerHeaders: Record<string, string> | undefined,
|
|
434
|
+
providerApiKey: string | undefined,
|
|
435
|
+
authHeader: boolean | undefined,
|
|
436
|
+
modelDef: CustomModelDefinitionLike,
|
|
437
|
+
options: CustomModelBuildOptions,
|
|
438
|
+
): Model<Api> | undefined {
|
|
439
|
+
const api = modelDef.api ?? providerApi;
|
|
440
|
+
if (!api) return undefined;
|
|
441
|
+
const withDefaults = options.useDefaults;
|
|
442
|
+
const cost = modelDef.cost ?? (withDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
443
|
+
const input = modelDef.input ?? (withDefaults ? ["text"] : undefined);
|
|
444
|
+
return {
|
|
445
|
+
id: modelDef.id,
|
|
446
|
+
name: modelDef.name ?? (withDefaults ? modelDef.id : undefined),
|
|
447
|
+
api,
|
|
448
|
+
provider: providerName,
|
|
449
|
+
baseUrl: providerBaseUrl,
|
|
450
|
+
reasoning: modelDef.reasoning ?? (withDefaults ? false : undefined),
|
|
451
|
+
input: input as ("text" | "image")[],
|
|
452
|
+
cost,
|
|
453
|
+
contextWindow: modelDef.contextWindow ?? (withDefaults ? 128000 : undefined),
|
|
454
|
+
maxTokens: modelDef.maxTokens ?? (withDefaults ? 16384 : undefined),
|
|
455
|
+
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
456
|
+
compat: modelDef.compat,
|
|
457
|
+
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
458
|
+
} as Model<Api>;
|
|
459
|
+
}
|
|
460
|
+
|
|
359
461
|
/**
|
|
360
462
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
361
463
|
*/
|
|
@@ -620,7 +722,11 @@ export class ModelRegistry {
|
|
|
620
722
|
}
|
|
621
723
|
|
|
622
724
|
async #discoverBuiltInProviderModels(): Promise<Model<Api>[]> {
|
|
623
|
-
|
|
725
|
+
// Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
|
|
726
|
+
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
|
|
727
|
+
const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(
|
|
728
|
+
opts => !configuredDiscoveryProviders.has(opts.providerId),
|
|
729
|
+
);
|
|
624
730
|
if (managerOptions.length === 0) {
|
|
625
731
|
return [];
|
|
626
732
|
}
|
|
@@ -629,189 +735,77 @@ export class ModelRegistry {
|
|
|
629
735
|
}
|
|
630
736
|
|
|
631
737
|
async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
738
|
+
const specialProviderDescriptors: Array<{
|
|
739
|
+
providerId: string;
|
|
740
|
+
resolveKey: (value: string | undefined) => string | undefined;
|
|
741
|
+
createOptions: (key: string) => ModelManagerOptions<Api>;
|
|
742
|
+
}> = [
|
|
743
|
+
{
|
|
744
|
+
providerId: "google-antigravity",
|
|
745
|
+
resolveKey: extractGoogleOAuthToken,
|
|
746
|
+
createOptions: oauthToken =>
|
|
747
|
+
googleAntigravityModelManagerOptions({
|
|
748
|
+
oauthToken,
|
|
749
|
+
endpoint: this.getProviderBaseUrl("google-antigravity"),
|
|
750
|
+
}),
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
providerId: "google-gemini-cli",
|
|
754
|
+
resolveKey: extractGoogleOAuthToken,
|
|
755
|
+
createOptions: oauthToken =>
|
|
756
|
+
googleGeminiCliModelManagerOptions({
|
|
757
|
+
oauthToken,
|
|
758
|
+
endpoint: this.getProviderBaseUrl("google-gemini-cli"),
|
|
759
|
+
}),
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
providerId: "openai-codex",
|
|
763
|
+
resolveKey: value => value,
|
|
764
|
+
createOptions: accessToken => {
|
|
765
|
+
const accountId = resolveOAuthAccountIdForAccessToken(this.authStorage, "openai-codex", accessToken);
|
|
766
|
+
return openaiCodexModelManagerOptions({
|
|
767
|
+
accessToken,
|
|
768
|
+
accountId,
|
|
769
|
+
});
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
];
|
|
773
|
+
const [standardProviderKeys, specialKeys] = await Promise.all([
|
|
774
|
+
Promise.all(PROVIDER_DESCRIPTORS.map(descriptor => this.getApiKeyForProvider(descriptor.providerId))),
|
|
775
|
+
Promise.all(specialProviderDescriptors.map(descriptor => this.getApiKeyForProvider(descriptor.providerId))),
|
|
668
776
|
]);
|
|
669
|
-
|
|
670
777
|
const options: ModelManagerOptions<Api>[] = [];
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
apiKey: openaiApiKey,
|
|
683
|
-
baseUrl: this.getProviderBaseUrl("openai"),
|
|
684
|
-
}),
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
if (isAuthenticated(groqApiKey)) {
|
|
688
|
-
options.push(
|
|
689
|
-
groqModelManagerOptions({
|
|
690
|
-
apiKey: groqApiKey,
|
|
691
|
-
baseUrl: this.getProviderBaseUrl("groq"),
|
|
692
|
-
}),
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
if (isAuthenticated(cerebrasApiKey)) {
|
|
696
|
-
options.push(
|
|
697
|
-
cerebrasModelManagerOptions({
|
|
698
|
-
apiKey: cerebrasApiKey,
|
|
699
|
-
baseUrl: this.getProviderBaseUrl("cerebras"),
|
|
700
|
-
}),
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
if (isAuthenticated(xaiApiKey)) {
|
|
704
|
-
options.push(
|
|
705
|
-
xaiModelManagerOptions({
|
|
706
|
-
apiKey: xaiApiKey,
|
|
707
|
-
baseUrl: this.getProviderBaseUrl("xai"),
|
|
708
|
-
}),
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
if (isAuthenticated(mistralApiKey)) {
|
|
712
|
-
options.push(
|
|
713
|
-
mistralModelManagerOptions({
|
|
714
|
-
apiKey: mistralApiKey,
|
|
715
|
-
baseUrl: this.getProviderBaseUrl("mistral"),
|
|
716
|
-
}),
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
if (isAuthenticated(opencodeApiKey)) {
|
|
720
|
-
options.push(
|
|
721
|
-
opencodeModelManagerOptions({
|
|
722
|
-
apiKey: opencodeApiKey,
|
|
723
|
-
baseUrl: this.getProviderBaseUrl("opencode"),
|
|
724
|
-
}),
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
if (isAuthenticated(openrouterApiKey)) {
|
|
728
|
-
options.push(
|
|
729
|
-
openrouterModelManagerOptions({
|
|
730
|
-
apiKey: openrouterApiKey,
|
|
731
|
-
baseUrl: this.getProviderBaseUrl("openrouter"),
|
|
732
|
-
}),
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
if (isAuthenticated(vercelGatewayApiKey)) {
|
|
736
|
-
options.push(
|
|
737
|
-
vercelAiGatewayModelManagerOptions({
|
|
738
|
-
apiKey: vercelGatewayApiKey,
|
|
739
|
-
baseUrl: this.getProviderBaseUrl("vercel-ai-gateway"),
|
|
740
|
-
}),
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
if (isAuthenticated(kimiApiKey)) {
|
|
744
|
-
options.push(
|
|
745
|
-
kimiCodeModelManagerOptions({
|
|
746
|
-
apiKey: kimiApiKey,
|
|
747
|
-
baseUrl: this.getProviderBaseUrl("kimi-code"),
|
|
748
|
-
}),
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (isAuthenticated(syntheticApiKey)) {
|
|
752
|
-
options.push(
|
|
753
|
-
syntheticModelManagerOptions({
|
|
754
|
-
apiKey: syntheticApiKey,
|
|
755
|
-
baseUrl: this.getProviderBaseUrl("synthetic"),
|
|
756
|
-
}),
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
if (isAuthenticated(githubCopilotApiKey)) {
|
|
760
|
-
options.push(
|
|
761
|
-
githubCopilotModelManagerOptions({
|
|
762
|
-
apiKey: githubCopilotApiKey,
|
|
763
|
-
baseUrl: this.getProviderBaseUrl("github-copilot"),
|
|
764
|
-
}),
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
if (isAuthenticated(googleApiKey)) options.push(googleModelManagerOptions({ apiKey: googleApiKey }));
|
|
768
|
-
if (isAuthenticated(cursorApiKey)) {
|
|
769
|
-
options.push(
|
|
770
|
-
cursorModelManagerOptions({
|
|
771
|
-
apiKey: cursorApiKey,
|
|
772
|
-
baseUrl: this.getProviderBaseUrl("cursor"),
|
|
773
|
-
}),
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
const antigravityToken = extractGoogleOAuthToken(googleAntigravityApiKey);
|
|
778
|
-
if (isAuthenticated(antigravityToken)) {
|
|
779
|
-
options.push(
|
|
780
|
-
googleAntigravityModelManagerOptions({
|
|
781
|
-
oauthToken: antigravityToken,
|
|
782
|
-
endpoint: this.getProviderBaseUrl("google-antigravity"),
|
|
783
|
-
}),
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const geminiCliToken = extractGoogleOAuthToken(googleGeminiCliApiKey);
|
|
788
|
-
if (isAuthenticated(geminiCliToken)) {
|
|
789
|
-
options.push(
|
|
790
|
-
googleGeminiCliModelManagerOptions({
|
|
791
|
-
oauthToken: geminiCliToken,
|
|
792
|
-
endpoint: this.getProviderBaseUrl("google-gemini-cli"),
|
|
793
|
-
}),
|
|
794
|
-
);
|
|
778
|
+
for (let i = 0; i < PROVIDER_DESCRIPTORS.length; i++) {
|
|
779
|
+
const descriptor = PROVIDER_DESCRIPTORS[i];
|
|
780
|
+
const apiKey = standardProviderKeys[i];
|
|
781
|
+
if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated) {
|
|
782
|
+
options.push(
|
|
783
|
+
descriptor.createModelManagerOptions({
|
|
784
|
+
apiKey: isAuthenticated(apiKey) ? apiKey : undefined,
|
|
785
|
+
baseUrl: this.getProviderBaseUrl(descriptor.providerId),
|
|
786
|
+
}),
|
|
787
|
+
);
|
|
788
|
+
}
|
|
795
789
|
}
|
|
796
790
|
|
|
797
|
-
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
);
|
|
791
|
+
for (let i = 0; i < specialProviderDescriptors.length; i++) {
|
|
792
|
+
const descriptor = specialProviderDescriptors[i];
|
|
793
|
+
const key = descriptor.resolveKey(specialKeys[i]);
|
|
794
|
+
if (!isAuthenticated(key)) {
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
options.push(descriptor.createOptions(key));
|
|
805
798
|
}
|
|
806
|
-
|
|
807
799
|
return options;
|
|
808
800
|
}
|
|
809
801
|
|
|
810
802
|
async #discoverWithModelManager(options: ModelManagerOptions<Api>): Promise<Model<Api>[]> {
|
|
811
803
|
try {
|
|
812
804
|
const manager = createModelManager(options);
|
|
813
|
-
const result = await manager.refresh(
|
|
814
|
-
return result.models
|
|
805
|
+
const result = await manager.refresh();
|
|
806
|
+
return result.models.map(model =>
|
|
807
|
+
model.provider === options.providerId ? model : { ...model, provider: options.providerId },
|
|
808
|
+
);
|
|
815
809
|
} catch (error) {
|
|
816
810
|
logger.warn("model discovery failed for provider", {
|
|
817
811
|
provider: options.providerId,
|
|
@@ -905,51 +899,24 @@ export class ModelRegistry {
|
|
|
905
899
|
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
906
900
|
const modelDefs = providerConfig.models ?? [];
|
|
907
901
|
if (modelDefs.length === 0) continue; // Override-only, no custom models
|
|
908
|
-
|
|
909
|
-
// Store API key config for fallback resolver
|
|
910
902
|
if (providerConfig.apiKey) {
|
|
911
903
|
this.#customProviderApiKeys.set(providerName, providerConfig.apiKey);
|
|
912
904
|
}
|
|
913
|
-
|
|
914
905
|
for (const modelDef of modelDefs) {
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
providerConfig.
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (
|
|
926
|
-
|
|
927
|
-
if (resolvedKey) {
|
|
928
|
-
headers = { ...headers, Authorization: `Bearer ${resolvedKey}` };
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// baseUrl is validated to exist for providers with models
|
|
933
|
-
// Apply defaults for optional fields
|
|
934
|
-
const defaultCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
935
|
-
models.push({
|
|
936
|
-
id: modelDef.id,
|
|
937
|
-
name: modelDef.name ?? modelDef.id,
|
|
938
|
-
api: api as Api,
|
|
939
|
-
provider: providerName,
|
|
940
|
-
baseUrl: providerConfig.baseUrl!,
|
|
941
|
-
reasoning: modelDef.reasoning ?? false,
|
|
942
|
-
input: (modelDef.input ?? ["text"]) as ("text" | "image")[],
|
|
943
|
-
cost: modelDef.cost ?? defaultCost,
|
|
944
|
-
contextWindow: modelDef.contextWindow ?? 128000,
|
|
945
|
-
maxTokens: modelDef.maxTokens ?? 16384,
|
|
946
|
-
headers,
|
|
947
|
-
compat: modelDef.compat,
|
|
948
|
-
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
949
|
-
} as Model<Api>);
|
|
906
|
+
const model = buildCustomModel(
|
|
907
|
+
providerName,
|
|
908
|
+
providerConfig.baseUrl!,
|
|
909
|
+
providerConfig.api as Api | undefined,
|
|
910
|
+
providerConfig.headers,
|
|
911
|
+
providerConfig.apiKey,
|
|
912
|
+
providerConfig.authHeader,
|
|
913
|
+
modelDef,
|
|
914
|
+
{ useDefaults: true },
|
|
915
|
+
);
|
|
916
|
+
if (!model) continue;
|
|
917
|
+
models.push(model);
|
|
950
918
|
}
|
|
951
919
|
}
|
|
952
|
-
|
|
953
920
|
return models;
|
|
954
921
|
}
|
|
955
922
|
|
|
@@ -1045,20 +1012,17 @@ export class ModelRegistry {
|
|
|
1045
1012
|
throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
|
|
1046
1013
|
}
|
|
1047
1014
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1015
|
+
validateProviderConfiguration(
|
|
1016
|
+
providerName,
|
|
1017
|
+
{
|
|
1018
|
+
baseUrl: config.baseUrl,
|
|
1019
|
+
apiKey: config.apiKey,
|
|
1020
|
+
api: config.api,
|
|
1021
|
+
oauthConfigured: Boolean(config.oauth),
|
|
1022
|
+
models: (config.models ?? []) as ProviderValidationModel[],
|
|
1023
|
+
},
|
|
1024
|
+
"runtime-register",
|
|
1025
|
+
);
|
|
1062
1026
|
|
|
1063
1027
|
if (config.streamSimple && config.api) {
|
|
1064
1028
|
const streamSimple = config.streamSimple;
|
|
@@ -1085,33 +1049,20 @@ export class ModelRegistry {
|
|
|
1085
1049
|
if (config.models && config.models.length > 0) {
|
|
1086
1050
|
const nextModels = this.#models.filter(m => m.provider !== providerName);
|
|
1087
1051
|
for (const modelDef of config.models) {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1052
|
+
const model = buildCustomModel(
|
|
1053
|
+
providerName,
|
|
1054
|
+
config.baseUrl!,
|
|
1055
|
+
config.api,
|
|
1056
|
+
config.headers,
|
|
1057
|
+
config.apiKey,
|
|
1058
|
+
config.authHeader,
|
|
1059
|
+
modelDef,
|
|
1060
|
+
{ useDefaults: false },
|
|
1061
|
+
);
|
|
1062
|
+
if (!model) {
|
|
1090
1063
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
1091
1064
|
}
|
|
1092
|
-
|
|
1093
|
-
if (config.authHeader && config.apiKey) {
|
|
1094
|
-
const resolvedKey = resolveApiKeyConfig(config.apiKey);
|
|
1095
|
-
if (resolvedKey) {
|
|
1096
|
-
headers = { ...headers, Authorization: `Bearer ${resolvedKey}` };
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
nextModels.push({
|
|
1101
|
-
id: modelDef.id,
|
|
1102
|
-
name: modelDef.name,
|
|
1103
|
-
api,
|
|
1104
|
-
provider: providerName,
|
|
1105
|
-
baseUrl: config.baseUrl!,
|
|
1106
|
-
reasoning: modelDef.reasoning,
|
|
1107
|
-
input: modelDef.input as ("text" | "image")[],
|
|
1108
|
-
cost: modelDef.cost,
|
|
1109
|
-
contextWindow: modelDef.contextWindow,
|
|
1110
|
-
maxTokens: modelDef.maxTokens,
|
|
1111
|
-
headers,
|
|
1112
|
-
compat: modelDef.compat,
|
|
1113
|
-
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
1114
|
-
} as Model<Api>);
|
|
1065
|
+
nextModels.push(model);
|
|
1115
1066
|
}
|
|
1116
1067
|
|
|
1117
1068
|
if (config.oauth?.modifyModels) {
|