@contractspec/lib.ai-providers 2.9.0 → 3.1.1
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 +19 -9
- package/dist/browser/factory.js +107 -85
- package/dist/browser/index.js +276 -85
- package/dist/browser/legacy.js +107 -85
- package/dist/browser/models.js +49 -1
- package/dist/browser/selector-types.js +0 -0
- package/dist/browser/selector.js +693 -0
- package/dist/browser/validation.js +107 -85
- package/dist/factory.js +107 -85
- package/dist/index.d.ts +3 -0
- package/dist/index.js +276 -85
- package/dist/legacy.js +107 -85
- package/dist/models.js +49 -1
- package/dist/models.test.d.ts +1 -0
- package/dist/node/factory.js +107 -85
- package/dist/node/index.js +276 -85
- package/dist/node/legacy.js +107 -85
- package/dist/node/models.js +49 -1
- package/dist/node/selector-types.js +0 -0
- package/dist/node/selector.js +693 -0
- package/dist/node/validation.js +107 -85
- package/dist/selector-types.d.ts +50 -0
- package/dist/selector-types.js +1 -0
- package/dist/selector.d.ts +16 -0
- package/dist/selector.js +694 -0
- package/dist/types.d.ts +13 -1
- package/dist/validation.js +107 -85
- package/package.json +37 -8
package/dist/index.js
CHANGED
|
@@ -160,11 +160,23 @@ var MODELS = [
|
|
|
160
160
|
},
|
|
161
161
|
costPerMillion: { input: 2, output: 6 }
|
|
162
162
|
},
|
|
163
|
+
{
|
|
164
|
+
id: "mistral-medium-latest",
|
|
165
|
+
name: "Mistral Medium",
|
|
166
|
+
provider: "mistral",
|
|
167
|
+
contextWindow: 128000,
|
|
168
|
+
capabilities: {
|
|
169
|
+
vision: false,
|
|
170
|
+
tools: true,
|
|
171
|
+
reasoning: false,
|
|
172
|
+
streaming: true
|
|
173
|
+
}
|
|
174
|
+
},
|
|
163
175
|
{
|
|
164
176
|
id: "codestral-latest",
|
|
165
177
|
name: "Codestral",
|
|
166
178
|
provider: "mistral",
|
|
167
|
-
contextWindow:
|
|
179
|
+
contextWindow: 256000,
|
|
168
180
|
capabilities: {
|
|
169
181
|
vision: false,
|
|
170
182
|
tools: true,
|
|
@@ -173,6 +185,42 @@ var MODELS = [
|
|
|
173
185
|
},
|
|
174
186
|
costPerMillion: { input: 0.2, output: 0.6 }
|
|
175
187
|
},
|
|
188
|
+
{
|
|
189
|
+
id: "devstral-small-latest",
|
|
190
|
+
name: "Devstral Small",
|
|
191
|
+
provider: "mistral",
|
|
192
|
+
contextWindow: 128000,
|
|
193
|
+
capabilities: {
|
|
194
|
+
vision: false,
|
|
195
|
+
tools: true,
|
|
196
|
+
reasoning: true,
|
|
197
|
+
streaming: true
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: "magistral-medium-latest",
|
|
202
|
+
name: "Magistral Medium",
|
|
203
|
+
provider: "mistral",
|
|
204
|
+
contextWindow: 128000,
|
|
205
|
+
capabilities: {
|
|
206
|
+
vision: false,
|
|
207
|
+
tools: true,
|
|
208
|
+
reasoning: true,
|
|
209
|
+
streaming: true
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "pixtral-large-latest",
|
|
214
|
+
name: "Pixtral Large",
|
|
215
|
+
provider: "mistral",
|
|
216
|
+
contextWindow: 128000,
|
|
217
|
+
capabilities: {
|
|
218
|
+
vision: true,
|
|
219
|
+
tools: true,
|
|
220
|
+
reasoning: false,
|
|
221
|
+
streaming: true
|
|
222
|
+
}
|
|
223
|
+
},
|
|
176
224
|
{
|
|
177
225
|
id: "mistral-small-latest",
|
|
178
226
|
name: "Mistral Small",
|
|
@@ -241,22 +289,30 @@ function getDefaultModel(provider) {
|
|
|
241
289
|
}
|
|
242
290
|
|
|
243
291
|
// src/factory.ts
|
|
244
|
-
import {
|
|
245
|
-
import {
|
|
246
|
-
import {
|
|
247
|
-
import {
|
|
248
|
-
import {
|
|
292
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
293
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
294
|
+
import { createMistral } from "@ai-sdk/mistral";
|
|
295
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
296
|
+
import { createOllama } from "ollama-ai-provider";
|
|
249
297
|
class BaseProvider {
|
|
250
298
|
name;
|
|
251
299
|
model;
|
|
252
300
|
mode;
|
|
253
301
|
config;
|
|
302
|
+
transport;
|
|
303
|
+
authMethod;
|
|
304
|
+
apiVersion;
|
|
305
|
+
customHeaders;
|
|
254
306
|
cachedModel = null;
|
|
255
307
|
constructor(config) {
|
|
256
308
|
this.name = config.provider;
|
|
257
309
|
this.model = config.model ?? DEFAULT_MODELS[config.provider];
|
|
258
310
|
this.mode = this.determineMode(config);
|
|
259
311
|
this.config = config;
|
|
312
|
+
this.transport = config.transport;
|
|
313
|
+
this.authMethod = config.authMethod;
|
|
314
|
+
this.apiVersion = config.apiVersion;
|
|
315
|
+
this.customHeaders = config.customHeaders;
|
|
260
316
|
}
|
|
261
317
|
getModel() {
|
|
262
318
|
if (!this.cachedModel) {
|
|
@@ -296,81 +352,33 @@ class BaseProvider {
|
|
|
296
352
|
return "managed";
|
|
297
353
|
}
|
|
298
354
|
createModel() {
|
|
299
|
-
const { baseUrl, proxyUrl } = this.config;
|
|
355
|
+
const { baseUrl, proxyUrl, apiKey } = this.config;
|
|
356
|
+
const headers = this.customHeaders;
|
|
357
|
+
if (this.name === "ollama") {
|
|
358
|
+
const provider = createOllama({ baseURL: baseUrl, headers });
|
|
359
|
+
return provider(this.model);
|
|
360
|
+
}
|
|
361
|
+
if (this.mode === "managed" && proxyUrl) {
|
|
362
|
+
const provider = createOpenAI({ baseURL: proxyUrl, apiKey, headers });
|
|
363
|
+
return provider(this.model);
|
|
364
|
+
}
|
|
300
365
|
switch (this.name) {
|
|
301
|
-
case "
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
366
|
+
case "openai": {
|
|
367
|
+
const provider = createOpenAI({ apiKey, headers });
|
|
368
|
+
return provider(this.model);
|
|
369
|
+
}
|
|
370
|
+
case "anthropic": {
|
|
371
|
+
const provider = createAnthropic({ apiKey, headers });
|
|
372
|
+
return provider(this.model);
|
|
373
|
+
}
|
|
374
|
+
case "mistral": {
|
|
375
|
+
const provider = createMistral({ apiKey, headers });
|
|
376
|
+
return provider(this.model);
|
|
377
|
+
}
|
|
378
|
+
case "gemini": {
|
|
379
|
+
const provider = createGoogleGenerativeAI({ apiKey, headers });
|
|
380
|
+
return provider(this.model);
|
|
313
381
|
}
|
|
314
|
-
case "openai":
|
|
315
|
-
if (this.mode === "managed") {
|
|
316
|
-
const originalBaseUrl = process.env.OPENAI_BASE_URL;
|
|
317
|
-
if (proxyUrl) {
|
|
318
|
-
process.env.OPENAI_BASE_URL = proxyUrl;
|
|
319
|
-
}
|
|
320
|
-
const model = openai(this.model);
|
|
321
|
-
if (originalBaseUrl !== undefined) {
|
|
322
|
-
process.env.OPENAI_BASE_URL = originalBaseUrl;
|
|
323
|
-
} else if (proxyUrl) {
|
|
324
|
-
delete process.env.OPENAI_BASE_URL;
|
|
325
|
-
}
|
|
326
|
-
return model;
|
|
327
|
-
}
|
|
328
|
-
return openai(this.model);
|
|
329
|
-
case "anthropic":
|
|
330
|
-
if (this.mode === "managed") {
|
|
331
|
-
const originalBaseUrl = process.env.OPENAI_BASE_URL;
|
|
332
|
-
if (proxyUrl) {
|
|
333
|
-
process.env.OPENAI_BASE_URL = proxyUrl;
|
|
334
|
-
}
|
|
335
|
-
const model = openai(this.model);
|
|
336
|
-
if (originalBaseUrl !== undefined) {
|
|
337
|
-
process.env.OPENAI_BASE_URL = originalBaseUrl;
|
|
338
|
-
} else if (proxyUrl) {
|
|
339
|
-
delete process.env.OPENAI_BASE_URL;
|
|
340
|
-
}
|
|
341
|
-
return model;
|
|
342
|
-
}
|
|
343
|
-
return anthropic(this.model);
|
|
344
|
-
case "mistral":
|
|
345
|
-
if (this.mode === "managed") {
|
|
346
|
-
const originalBaseUrl = process.env.OPENAI_BASE_URL;
|
|
347
|
-
if (proxyUrl) {
|
|
348
|
-
process.env.OPENAI_BASE_URL = proxyUrl;
|
|
349
|
-
}
|
|
350
|
-
const model = openai(this.model);
|
|
351
|
-
if (originalBaseUrl !== undefined) {
|
|
352
|
-
process.env.OPENAI_BASE_URL = originalBaseUrl;
|
|
353
|
-
} else if (proxyUrl) {
|
|
354
|
-
delete process.env.OPENAI_BASE_URL;
|
|
355
|
-
}
|
|
356
|
-
return model;
|
|
357
|
-
}
|
|
358
|
-
return mistral(this.model);
|
|
359
|
-
case "gemini":
|
|
360
|
-
if (this.mode === "managed") {
|
|
361
|
-
const originalBaseUrl = process.env.OPENAI_BASE_URL;
|
|
362
|
-
if (proxyUrl) {
|
|
363
|
-
process.env.OPENAI_BASE_URL = proxyUrl;
|
|
364
|
-
}
|
|
365
|
-
const model = openai(this.model);
|
|
366
|
-
if (originalBaseUrl !== undefined) {
|
|
367
|
-
process.env.OPENAI_BASE_URL = originalBaseUrl;
|
|
368
|
-
} else if (proxyUrl) {
|
|
369
|
-
delete process.env.OPENAI_BASE_URL;
|
|
370
|
-
}
|
|
371
|
-
return model;
|
|
372
|
-
}
|
|
373
|
-
return google(this.model);
|
|
374
382
|
default:
|
|
375
383
|
throw new Error(`Unknown provider: ${this.name}`);
|
|
376
384
|
}
|
|
@@ -452,13 +460,17 @@ function createProviderFromEnv() {
|
|
|
452
460
|
case "ollama":
|
|
453
461
|
break;
|
|
454
462
|
}
|
|
463
|
+
const transport = process.env.CONTRACTSPEC_AI_TRANSPORT;
|
|
464
|
+
const apiVersion = process.env.CONTRACTSPEC_AI_API_VERSION;
|
|
455
465
|
return createProvider({
|
|
456
466
|
provider,
|
|
457
467
|
model,
|
|
458
468
|
apiKey,
|
|
459
469
|
baseUrl: process.env.OLLAMA_BASE_URL,
|
|
460
470
|
proxyUrl: process.env.CONTRACTSPEC_AI_PROXY_URL,
|
|
461
|
-
organizationId: process.env.CONTRACTSPEC_ORG_ID
|
|
471
|
+
organizationId: process.env.CONTRACTSPEC_ORG_ID,
|
|
472
|
+
transport,
|
|
473
|
+
apiVersion
|
|
462
474
|
});
|
|
463
475
|
}
|
|
464
476
|
function getAvailableProviders() {
|
|
@@ -466,35 +478,45 @@ function getAvailableProviders() {
|
|
|
466
478
|
providers.push({
|
|
467
479
|
provider: "ollama",
|
|
468
480
|
available: true,
|
|
469
|
-
mode: "local"
|
|
481
|
+
mode: "local",
|
|
482
|
+
transports: ["rest", "sdk"],
|
|
483
|
+
authMethods: []
|
|
470
484
|
});
|
|
471
485
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
472
486
|
providers.push({
|
|
473
487
|
provider: "openai",
|
|
474
488
|
available: Boolean(openaiKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
|
|
475
489
|
mode: openaiKey ? "byok" : "managed",
|
|
476
|
-
reason: !openaiKey ? "Set OPENAI_API_KEY for BYOK mode" : undefined
|
|
490
|
+
reason: !openaiKey ? "Set OPENAI_API_KEY for BYOK mode" : undefined,
|
|
491
|
+
transports: ["rest", "sdk"],
|
|
492
|
+
authMethods: ["api-key"]
|
|
477
493
|
});
|
|
478
494
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
479
495
|
providers.push({
|
|
480
496
|
provider: "anthropic",
|
|
481
497
|
available: Boolean(anthropicKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
|
|
482
498
|
mode: anthropicKey ? "byok" : "managed",
|
|
483
|
-
reason: !anthropicKey ? "Set ANTHROPIC_API_KEY for BYOK mode" : undefined
|
|
499
|
+
reason: !anthropicKey ? "Set ANTHROPIC_API_KEY for BYOK mode" : undefined,
|
|
500
|
+
transports: ["rest", "sdk"],
|
|
501
|
+
authMethods: ["api-key"]
|
|
484
502
|
});
|
|
485
503
|
const mistralKey = process.env.MISTRAL_API_KEY;
|
|
486
504
|
providers.push({
|
|
487
505
|
provider: "mistral",
|
|
488
506
|
available: Boolean(mistralKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
|
|
489
507
|
mode: mistralKey ? "byok" : "managed",
|
|
490
|
-
reason: !mistralKey ? "Set MISTRAL_API_KEY for BYOK mode" : undefined
|
|
508
|
+
reason: !mistralKey ? "Set MISTRAL_API_KEY for BYOK mode" : undefined,
|
|
509
|
+
transports: ["rest", "sdk"],
|
|
510
|
+
authMethods: ["api-key"]
|
|
491
511
|
});
|
|
492
512
|
const geminiKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
|
|
493
513
|
providers.push({
|
|
494
514
|
provider: "gemini",
|
|
495
515
|
available: Boolean(geminiKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
|
|
496
516
|
mode: geminiKey ? "byok" : "managed",
|
|
497
|
-
reason: !geminiKey ? "Set GOOGLE_API_KEY for BYOK mode" : undefined
|
|
517
|
+
reason: !geminiKey ? "Set GOOGLE_API_KEY for BYOK mode" : undefined,
|
|
518
|
+
transports: ["rest", "sdk"],
|
|
519
|
+
authMethods: ["api-key"]
|
|
498
520
|
});
|
|
499
521
|
return providers;
|
|
500
522
|
}
|
|
@@ -554,6 +576,174 @@ async function listOllamaModels(baseUrl = "http://localhost:11434") {
|
|
|
554
576
|
return [];
|
|
555
577
|
}
|
|
556
578
|
}
|
|
579
|
+
// src/selector.ts
|
|
580
|
+
function createModelSelector(options) {
|
|
581
|
+
const { store, fallbackModels, defaultConstraints } = options;
|
|
582
|
+
const catalog = fallbackModels ?? MODELS;
|
|
583
|
+
return {
|
|
584
|
+
async select(context) {
|
|
585
|
+
const merged = mergeConstraints(defaultConstraints, context.constraints);
|
|
586
|
+
if (context.priorities?.length) {
|
|
587
|
+
return selectMultiObjective(store, catalog, context.priorities, merged);
|
|
588
|
+
}
|
|
589
|
+
const dimension = context.taskDimension ?? "reasoning";
|
|
590
|
+
return selectByDimension(store, catalog, dimension, merged);
|
|
591
|
+
},
|
|
592
|
+
async selectAndCreate(context) {
|
|
593
|
+
const selection = await this.select(context);
|
|
594
|
+
const model = createProvider({
|
|
595
|
+
provider: selection.providerKey,
|
|
596
|
+
model: selection.modelId
|
|
597
|
+
}).getModel();
|
|
598
|
+
return { model, selection };
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
async function selectByDimension(store, catalog, dimension, constraints) {
|
|
603
|
+
const { rankings } = await store.listModelRankings({ dimension, limit: 50 });
|
|
604
|
+
const eligible = filterRankings(rankings, catalog, constraints);
|
|
605
|
+
const topCandidate = eligible[0];
|
|
606
|
+
if (topCandidate) {
|
|
607
|
+
const dimScore = topCandidate.dimensionScores[dimension]?.score ?? topCandidate.compositeScore;
|
|
608
|
+
return {
|
|
609
|
+
modelId: topCandidate.modelId,
|
|
610
|
+
providerKey: topCandidate.providerKey,
|
|
611
|
+
score: dimScore,
|
|
612
|
+
reason: `Top-ranked for "${dimension}" (score ${Math.round(dimScore)})`,
|
|
613
|
+
alternatives: eligible.slice(1, 4).map((r) => ({
|
|
614
|
+
modelId: r.modelId,
|
|
615
|
+
providerKey: r.providerKey,
|
|
616
|
+
score: r.dimensionScores[dimension]?.score ?? r.compositeScore
|
|
617
|
+
}))
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
return fallbackFromCatalog(catalog, constraints, dimension);
|
|
621
|
+
}
|
|
622
|
+
async function selectMultiObjective(store, catalog, priorities, constraints) {
|
|
623
|
+
const { rankings } = await store.listModelRankings({ limit: 100 });
|
|
624
|
+
const eligible = filterRankings(rankings, catalog, constraints);
|
|
625
|
+
if (eligible.length === 0) {
|
|
626
|
+
const primaryDim = priorities.reduce((a, b) => b.weight > a.weight ? b : a).dimension;
|
|
627
|
+
return fallbackFromCatalog(catalog, constraints, primaryDim);
|
|
628
|
+
}
|
|
629
|
+
const totalWeight = priorities.reduce((sum, p) => sum + p.weight, 0) || 1;
|
|
630
|
+
const scored = eligible.map((r) => {
|
|
631
|
+
let weightedScore = 0;
|
|
632
|
+
for (const p of priorities) {
|
|
633
|
+
const dimScore = r.dimensionScores[p.dimension]?.score ?? 0;
|
|
634
|
+
weightedScore += dimScore * (p.weight / totalWeight);
|
|
635
|
+
}
|
|
636
|
+
return { ranking: r, weightedScore };
|
|
637
|
+
});
|
|
638
|
+
scored.sort((a, b) => b.weightedScore - a.weightedScore);
|
|
639
|
+
const best = scored[0];
|
|
640
|
+
if (!best) {
|
|
641
|
+
const primaryDim = priorities.reduce((a, b) => b.weight > a.weight ? b : a).dimension;
|
|
642
|
+
return fallbackFromCatalog(catalog, constraints, primaryDim);
|
|
643
|
+
}
|
|
644
|
+
const dims = priorities.map((p) => p.dimension).join(", ");
|
|
645
|
+
return {
|
|
646
|
+
modelId: best.ranking.modelId,
|
|
647
|
+
providerKey: best.ranking.providerKey,
|
|
648
|
+
score: Math.round(best.weightedScore * 100) / 100,
|
|
649
|
+
reason: `Multi-objective optimum across [${dims}]`,
|
|
650
|
+
alternatives: scored.slice(1, 4).map((s) => ({
|
|
651
|
+
modelId: s.ranking.modelId,
|
|
652
|
+
providerKey: s.ranking.providerKey,
|
|
653
|
+
score: Math.round(s.weightedScore * 100) / 100
|
|
654
|
+
}))
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function filterRankings(rankings, catalog, constraints) {
|
|
658
|
+
return rankings.filter((r) => {
|
|
659
|
+
if (constraints.allowedProviders?.length) {
|
|
660
|
+
if (!constraints.allowedProviders.includes(r.providerKey))
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
if (constraints.excludeModels?.length) {
|
|
664
|
+
if (constraints.excludeModels.includes(r.modelId))
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const info = getModelInfo(r.modelId) ?? catalog.find((m) => m.id === r.modelId);
|
|
668
|
+
if (!info)
|
|
669
|
+
return true;
|
|
670
|
+
if (constraints.minContextWindow && info.contextWindow < constraints.minContextWindow) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
if (constraints.maxCostPerMillionInput && info.costPerMillion) {
|
|
674
|
+
if (info.costPerMillion.input > constraints.maxCostPerMillionInput)
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
if (constraints.maxCostPerMillionOutput && info.costPerMillion) {
|
|
678
|
+
if (info.costPerMillion.output > constraints.maxCostPerMillionOutput)
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (constraints.requiredCapabilities?.length) {
|
|
682
|
+
for (const cap of constraints.requiredCapabilities) {
|
|
683
|
+
if (!info.capabilities[cap])
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return true;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function fallbackFromCatalog(catalog, constraints, dimension) {
|
|
691
|
+
let eligible = catalog.filter((m) => m.costPerMillion != null);
|
|
692
|
+
const {
|
|
693
|
+
allowedProviders,
|
|
694
|
+
excludeModels,
|
|
695
|
+
minContextWindow,
|
|
696
|
+
requiredCapabilities
|
|
697
|
+
} = constraints;
|
|
698
|
+
if (allowedProviders?.length) {
|
|
699
|
+
eligible = eligible.filter((m) => allowedProviders.includes(m.provider));
|
|
700
|
+
}
|
|
701
|
+
if (excludeModels?.length) {
|
|
702
|
+
eligible = eligible.filter((m) => !excludeModels.includes(m.id));
|
|
703
|
+
}
|
|
704
|
+
if (minContextWindow) {
|
|
705
|
+
eligible = eligible.filter((m) => m.contextWindow >= minContextWindow);
|
|
706
|
+
}
|
|
707
|
+
if (requiredCapabilities?.length) {
|
|
708
|
+
eligible = eligible.filter((m) => requiredCapabilities.every((cap) => m.capabilities[cap]));
|
|
709
|
+
}
|
|
710
|
+
if (eligible.length === 0) {
|
|
711
|
+
eligible = catalog.slice(0, 5);
|
|
712
|
+
}
|
|
713
|
+
eligible.sort((a, b) => {
|
|
714
|
+
const costA = a.costPerMillion ? (a.costPerMillion.input + a.costPerMillion.output) / 2 : 999;
|
|
715
|
+
const costB = b.costPerMillion ? (b.costPerMillion.input + b.costPerMillion.output) / 2 : 999;
|
|
716
|
+
return b.contextWindow / 1e5 - costB - (a.contextWindow / 1e5 - costA);
|
|
717
|
+
});
|
|
718
|
+
const best = eligible[0];
|
|
719
|
+
if (!best) {
|
|
720
|
+
return {
|
|
721
|
+
modelId: "unknown",
|
|
722
|
+
providerKey: "openai",
|
|
723
|
+
score: 0,
|
|
724
|
+
reason: `No eligible models found for "${dimension}"`,
|
|
725
|
+
alternatives: []
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
modelId: best.id,
|
|
730
|
+
providerKey: best.provider,
|
|
731
|
+
score: 0,
|
|
732
|
+
reason: `Fallback from catalog (no ranking data for "${dimension}")`,
|
|
733
|
+
alternatives: eligible.slice(1, 4).map((m) => ({
|
|
734
|
+
modelId: m.id,
|
|
735
|
+
providerKey: m.provider,
|
|
736
|
+
score: 0
|
|
737
|
+
}))
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function mergeConstraints(defaults, overrides) {
|
|
741
|
+
if (!defaults)
|
|
742
|
+
return overrides ?? {};
|
|
743
|
+
if (!overrides)
|
|
744
|
+
return defaults;
|
|
745
|
+
return { ...defaults, ...overrides };
|
|
746
|
+
}
|
|
557
747
|
|
|
558
748
|
// src/legacy.ts
|
|
559
749
|
function mapLegacyProvider(legacy) {
|
|
@@ -637,6 +827,7 @@ export {
|
|
|
637
827
|
getAIProvider,
|
|
638
828
|
createProviderFromEnv,
|
|
639
829
|
createProvider,
|
|
830
|
+
createModelSelector,
|
|
640
831
|
MODELS,
|
|
641
832
|
DEFAULT_MODELS
|
|
642
833
|
};
|