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