@contractspec/lib.ai-providers 3.7.12 → 3.7.14

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/selector.js CHANGED
@@ -1,846 +1,2 @@
1
1
  // @bun
2
- // src/models.ts
3
- var DEFAULT_MODELS = {
4
- ollama: "llama3.2",
5
- openai: "gpt-5.4",
6
- anthropic: "claude-sonnet-4-6",
7
- mistral: "mistral-large-latest",
8
- gemini: "gemini-2.5-flash"
9
- };
10
- var MODELS = [
11
- {
12
- id: "llama3.2",
13
- name: "Llama 3.2",
14
- provider: "ollama",
15
- contextWindow: 128000,
16
- capabilities: {
17
- vision: false,
18
- tools: true,
19
- reasoning: false,
20
- streaming: true
21
- }
22
- },
23
- {
24
- id: "codellama",
25
- name: "Code Llama",
26
- provider: "ollama",
27
- contextWindow: 16000,
28
- capabilities: {
29
- vision: false,
30
- tools: false,
31
- reasoning: false,
32
- streaming: true
33
- }
34
- },
35
- {
36
- id: "deepseek-coder",
37
- name: "DeepSeek Coder",
38
- provider: "ollama",
39
- contextWindow: 16000,
40
- capabilities: {
41
- vision: false,
42
- tools: false,
43
- reasoning: false,
44
- streaming: true
45
- }
46
- },
47
- {
48
- id: "mistral",
49
- name: "Mistral 7B",
50
- provider: "ollama",
51
- contextWindow: 32000,
52
- capabilities: {
53
- vision: false,
54
- tools: false,
55
- reasoning: false,
56
- streaming: true
57
- }
58
- },
59
- {
60
- id: "gpt-5.4",
61
- name: "GPT-5.4",
62
- provider: "openai",
63
- contextWindow: 1e6,
64
- capabilities: {
65
- vision: true,
66
- tools: true,
67
- reasoning: true,
68
- streaming: true
69
- },
70
- costPerMillion: { input: 2.5, output: 15 }
71
- },
72
- {
73
- id: "gpt-4o",
74
- name: "GPT-4o",
75
- provider: "openai",
76
- contextWindow: 128000,
77
- capabilities: {
78
- vision: true,
79
- tools: true,
80
- reasoning: false,
81
- streaming: true
82
- },
83
- costPerMillion: { input: 2.5, output: 10 }
84
- },
85
- {
86
- id: "gpt-4o-mini",
87
- name: "GPT-4o Mini",
88
- provider: "openai",
89
- contextWindow: 128000,
90
- capabilities: {
91
- vision: true,
92
- tools: true,
93
- reasoning: false,
94
- streaming: true
95
- },
96
- costPerMillion: { input: 0.15, output: 0.6 }
97
- },
98
- {
99
- id: "o1",
100
- name: "o1",
101
- provider: "openai",
102
- contextWindow: 200000,
103
- capabilities: {
104
- vision: true,
105
- tools: true,
106
- reasoning: true,
107
- streaming: true
108
- },
109
- costPerMillion: { input: 15, output: 60 }
110
- },
111
- {
112
- id: "o1-mini",
113
- name: "o1 Mini",
114
- provider: "openai",
115
- contextWindow: 128000,
116
- capabilities: {
117
- vision: false,
118
- tools: true,
119
- reasoning: true,
120
- streaming: true
121
- },
122
- costPerMillion: { input: 3, output: 12 }
123
- },
124
- {
125
- id: "gpt-5-mini",
126
- name: "GPT-5 Mini",
127
- provider: "openai",
128
- contextWindow: 400000,
129
- capabilities: {
130
- vision: true,
131
- tools: true,
132
- reasoning: false,
133
- streaming: true
134
- },
135
- costPerMillion: { input: 0.25, output: 2 }
136
- },
137
- {
138
- id: "claude-opus-4-6",
139
- name: "Claude Opus 4.6",
140
- provider: "anthropic",
141
- contextWindow: 200000,
142
- capabilities: {
143
- vision: true,
144
- tools: true,
145
- reasoning: true,
146
- streaming: true
147
- },
148
- costPerMillion: { input: 5, output: 25 }
149
- },
150
- {
151
- id: "claude-sonnet-4-6",
152
- name: "Claude Sonnet 4.6",
153
- provider: "anthropic",
154
- contextWindow: 200000,
155
- capabilities: {
156
- vision: true,
157
- tools: true,
158
- reasoning: true,
159
- streaming: true
160
- },
161
- costPerMillion: { input: 3, output: 15 }
162
- },
163
- {
164
- id: "claude-haiku-4-5",
165
- name: "Claude Haiku 4.5",
166
- provider: "anthropic",
167
- contextWindow: 200000,
168
- capabilities: {
169
- vision: true,
170
- tools: true,
171
- reasoning: false,
172
- streaming: true
173
- },
174
- costPerMillion: { input: 1, output: 5 }
175
- },
176
- {
177
- id: "claude-sonnet-4-20250514",
178
- name: "Claude Sonnet 4",
179
- provider: "anthropic",
180
- contextWindow: 200000,
181
- capabilities: {
182
- vision: true,
183
- tools: true,
184
- reasoning: true,
185
- streaming: true
186
- },
187
- costPerMillion: { input: 3, output: 15 }
188
- },
189
- {
190
- id: "claude-3-5-sonnet-20241022",
191
- name: "Claude 3.5 Sonnet",
192
- provider: "anthropic",
193
- contextWindow: 200000,
194
- capabilities: {
195
- vision: true,
196
- tools: true,
197
- reasoning: false,
198
- streaming: true
199
- },
200
- costPerMillion: { input: 3, output: 15 }
201
- },
202
- {
203
- id: "claude-3-5-haiku-20241022",
204
- name: "Claude 3.5 Haiku",
205
- provider: "anthropic",
206
- contextWindow: 200000,
207
- capabilities: {
208
- vision: true,
209
- tools: true,
210
- reasoning: false,
211
- streaming: true
212
- },
213
- costPerMillion: { input: 0.8, output: 4 }
214
- },
215
- {
216
- id: "mistral-large-2512",
217
- name: "Mistral Large 3",
218
- provider: "mistral",
219
- contextWindow: 256000,
220
- capabilities: {
221
- vision: true,
222
- tools: true,
223
- reasoning: false,
224
- streaming: true
225
- },
226
- costPerMillion: { input: 0.5, output: 1.5 }
227
- },
228
- {
229
- id: "devstral-2512",
230
- name: "Devstral 2",
231
- provider: "mistral",
232
- contextWindow: 256000,
233
- capabilities: {
234
- vision: false,
235
- tools: true,
236
- reasoning: true,
237
- streaming: true
238
- },
239
- costPerMillion: { input: 0.4, output: 2 }
240
- },
241
- {
242
- id: "mistral-medium-2508",
243
- name: "Mistral Medium 3.1",
244
- provider: "mistral",
245
- contextWindow: 128000,
246
- capabilities: {
247
- vision: true,
248
- tools: true,
249
- reasoning: false,
250
- streaming: true
251
- },
252
- costPerMillion: { input: 0.4, output: 2 }
253
- },
254
- {
255
- id: "mistral-small-2506",
256
- name: "Mistral Small 3.2",
257
- provider: "mistral",
258
- contextWindow: 128000,
259
- capabilities: {
260
- vision: false,
261
- tools: true,
262
- reasoning: false,
263
- streaming: true
264
- }
265
- },
266
- {
267
- id: "mistral-large-latest",
268
- name: "Mistral Large",
269
- provider: "mistral",
270
- contextWindow: 128000,
271
- capabilities: {
272
- vision: false,
273
- tools: true,
274
- reasoning: false,
275
- streaming: true
276
- },
277
- costPerMillion: { input: 2, output: 6 }
278
- },
279
- {
280
- id: "mistral-medium-latest",
281
- name: "Mistral Medium",
282
- provider: "mistral",
283
- contextWindow: 128000,
284
- capabilities: {
285
- vision: false,
286
- tools: true,
287
- reasoning: false,
288
- streaming: true
289
- }
290
- },
291
- {
292
- id: "codestral-latest",
293
- name: "Codestral",
294
- provider: "mistral",
295
- contextWindow: 256000,
296
- capabilities: {
297
- vision: false,
298
- tools: true,
299
- reasoning: false,
300
- streaming: true
301
- },
302
- costPerMillion: { input: 0.2, output: 0.6 }
303
- },
304
- {
305
- id: "devstral-small-latest",
306
- name: "Devstral Small",
307
- provider: "mistral",
308
- contextWindow: 128000,
309
- capabilities: {
310
- vision: false,
311
- tools: true,
312
- reasoning: true,
313
- streaming: true
314
- }
315
- },
316
- {
317
- id: "magistral-medium-latest",
318
- name: "Magistral Medium",
319
- provider: "mistral",
320
- contextWindow: 128000,
321
- capabilities: {
322
- vision: false,
323
- tools: true,
324
- reasoning: true,
325
- streaming: true
326
- }
327
- },
328
- {
329
- id: "pixtral-large-latest",
330
- name: "Pixtral Large",
331
- provider: "mistral",
332
- contextWindow: 128000,
333
- capabilities: {
334
- vision: true,
335
- tools: true,
336
- reasoning: false,
337
- streaming: true
338
- }
339
- },
340
- {
341
- id: "mistral-small-latest",
342
- name: "Mistral Small",
343
- provider: "mistral",
344
- contextWindow: 32000,
345
- capabilities: {
346
- vision: false,
347
- tools: true,
348
- reasoning: false,
349
- streaming: true
350
- },
351
- costPerMillion: { input: 0.2, output: 0.6 }
352
- },
353
- {
354
- id: "gemini-2.0-flash",
355
- name: "Gemini 2.0 Flash",
356
- provider: "gemini",
357
- contextWindow: 1e6,
358
- capabilities: {
359
- vision: true,
360
- tools: true,
361
- reasoning: false,
362
- streaming: true
363
- },
364
- costPerMillion: { input: 0.075, output: 0.3 }
365
- },
366
- {
367
- id: "gemini-2.5-pro-preview-06-05",
368
- name: "Gemini 2.5 Pro",
369
- provider: "gemini",
370
- contextWindow: 1e6,
371
- capabilities: {
372
- vision: true,
373
- tools: true,
374
- reasoning: true,
375
- streaming: true
376
- },
377
- costPerMillion: { input: 1.25, output: 10 }
378
- },
379
- {
380
- id: "gemini-2.5-flash-preview-05-20",
381
- name: "Gemini 2.5 Flash",
382
- provider: "gemini",
383
- contextWindow: 1e6,
384
- capabilities: {
385
- vision: true,
386
- tools: true,
387
- reasoning: true,
388
- streaming: true
389
- },
390
- costPerMillion: { input: 0.15, output: 0.6 }
391
- },
392
- {
393
- id: "gemini-3.1-pro-preview",
394
- name: "Gemini 3.1 Pro",
395
- provider: "gemini",
396
- contextWindow: 1e6,
397
- capabilities: {
398
- vision: true,
399
- tools: true,
400
- reasoning: true,
401
- streaming: true
402
- }
403
- },
404
- {
405
- id: "gemini-3.1-flash-lite-preview",
406
- name: "Gemini 3.1 Flash-Lite",
407
- provider: "gemini",
408
- contextWindow: 1e6,
409
- capabilities: {
410
- vision: true,
411
- tools: true,
412
- reasoning: true,
413
- streaming: true
414
- }
415
- },
416
- {
417
- id: "gemini-3-flash-preview",
418
- name: "Gemini 3 Flash",
419
- provider: "gemini",
420
- contextWindow: 1e6,
421
- capabilities: {
422
- vision: true,
423
- tools: true,
424
- reasoning: false,
425
- streaming: true
426
- }
427
- }
428
- ];
429
- function getModelsForProvider(provider) {
430
- return MODELS.filter((m) => m.provider === provider);
431
- }
432
- function getModelInfo(modelId) {
433
- return MODELS.find((m) => m.id === modelId);
434
- }
435
- function getRecommendedModels(provider) {
436
- const normalizedProvider = provider === "claude" ? "anthropic" : provider === "custom" ? "openai" : provider;
437
- return getModelsForProvider(normalizedProvider).map((m) => m.id);
438
- }
439
- function getDefaultModel(provider) {
440
- return DEFAULT_MODELS[provider];
441
- }
442
-
443
- // src/factory.ts
444
- import { createAnthropic } from "@ai-sdk/anthropic";
445
- import { createGoogleGenerativeAI } from "@ai-sdk/google";
446
- import { createMistral } from "@ai-sdk/mistral";
447
- import { createOpenAI } from "@ai-sdk/openai";
448
- import { createOllama } from "ollama-ai-provider";
449
- class BaseProvider {
450
- name;
451
- model;
452
- mode;
453
- config;
454
- transport;
455
- authMethod;
456
- apiVersion;
457
- customHeaders;
458
- cachedModel = null;
459
- constructor(config) {
460
- this.name = config.provider;
461
- this.model = config.model ?? DEFAULT_MODELS[config.provider];
462
- this.mode = this.determineMode(config);
463
- this.config = config;
464
- this.transport = config.transport;
465
- this.authMethod = config.authMethod;
466
- this.apiVersion = config.apiVersion;
467
- this.customHeaders = config.customHeaders;
468
- }
469
- getModel() {
470
- if (!this.cachedModel) {
471
- this.cachedModel = this.createModel();
472
- }
473
- return this.cachedModel;
474
- }
475
- async listModels() {
476
- if (this.name === "ollama") {
477
- return this.listOllamaModels();
478
- }
479
- return getModelsForProvider(this.name);
480
- }
481
- async validate() {
482
- if (this.name === "ollama") {
483
- return this.validateOllama();
484
- }
485
- if (this.mode === "byok" && !this.config.apiKey) {
486
- return {
487
- valid: false,
488
- error: `API key required for ${this.name}`
489
- };
490
- }
491
- if (this.mode === "managed" && !this.config.proxyUrl && !this.config.organizationId) {
492
- return {
493
- valid: false,
494
- error: "Managed mode requires proxyUrl or organizationId"
495
- };
496
- }
497
- return { valid: true };
498
- }
499
- determineMode(config) {
500
- if (config.provider === "ollama")
501
- return "local";
502
- if (config.apiKey)
503
- return "byok";
504
- return "managed";
505
- }
506
- createModel() {
507
- const { baseUrl, proxyUrl, apiKey } = this.config;
508
- const headers = this.customHeaders;
509
- if (this.name === "ollama") {
510
- const provider = createOllama({ baseURL: baseUrl, headers });
511
- return provider(this.model);
512
- }
513
- if (this.mode === "managed" && proxyUrl) {
514
- const provider = createOpenAI({ baseURL: proxyUrl, apiKey, headers });
515
- return provider(this.model);
516
- }
517
- switch (this.name) {
518
- case "openai": {
519
- const provider = createOpenAI({ apiKey, headers });
520
- return provider(this.model);
521
- }
522
- case "anthropic": {
523
- const provider = createAnthropic({ apiKey, headers });
524
- return provider(this.model);
525
- }
526
- case "mistral": {
527
- const provider = createMistral({ apiKey, headers });
528
- return provider(this.model);
529
- }
530
- case "gemini": {
531
- const provider = createGoogleGenerativeAI({ apiKey, headers });
532
- return provider(this.model);
533
- }
534
- default:
535
- throw new Error(`Unknown provider: ${this.name}`);
536
- }
537
- }
538
- async listOllamaModels() {
539
- try {
540
- const baseUrl = this.config.baseUrl ?? "http://localhost:11434";
541
- const response = await fetch(`${baseUrl}/api/tags`);
542
- if (!response.ok) {
543
- return getModelsForProvider("ollama");
544
- }
545
- const data = await response.json();
546
- const models = data.models ?? [];
547
- return models.map((m) => ({
548
- id: m.name,
549
- name: m.name,
550
- provider: "ollama",
551
- contextWindow: 8000,
552
- capabilities: {
553
- vision: false,
554
- tools: false,
555
- reasoning: false,
556
- streaming: true
557
- }
558
- }));
559
- } catch {
560
- return getModelsForProvider("ollama");
561
- }
562
- }
563
- async validateOllama() {
564
- try {
565
- const baseUrl = this.config.baseUrl ?? "http://localhost:11434";
566
- const response = await fetch(`${baseUrl}/api/tags`);
567
- if (!response.ok) {
568
- return {
569
- valid: false,
570
- error: `Ollama server returned ${response.status}`
571
- };
572
- }
573
- const data = await response.json();
574
- const models = data.models ?? [];
575
- const hasModel = models.some((m) => m.name === this.model);
576
- if (!hasModel) {
577
- return {
578
- valid: false,
579
- error: `Model "${this.model}" not found. Available: ${models.map((m) => m.name).join(", ")}`
580
- };
581
- }
582
- return { valid: true };
583
- } catch (error) {
584
- const baseUrl = this.config.baseUrl ?? "http://localhost:11434";
585
- return {
586
- valid: false,
587
- error: `Cannot connect to Ollama at ${baseUrl}: ${error instanceof Error ? error.message : String(error)}`
588
- };
589
- }
590
- }
591
- }
592
- function createProvider(config) {
593
- return new BaseProvider(config);
594
- }
595
- function createProviderFromEnv() {
596
- const provider = process.env.CONTRACTSPEC_AI_PROVIDER ?? "openai";
597
- const model = process.env.CONTRACTSPEC_AI_MODEL;
598
- let apiKey;
599
- switch (provider) {
600
- case "openai":
601
- apiKey = process.env.OPENAI_API_KEY;
602
- break;
603
- case "anthropic":
604
- apiKey = process.env.ANTHROPIC_API_KEY;
605
- break;
606
- case "mistral":
607
- apiKey = process.env.MISTRAL_API_KEY;
608
- break;
609
- case "gemini":
610
- apiKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
611
- break;
612
- case "ollama":
613
- break;
614
- }
615
- const transport = process.env.CONTRACTSPEC_AI_TRANSPORT;
616
- const apiVersion = process.env.CONTRACTSPEC_AI_API_VERSION;
617
- return createProvider({
618
- provider,
619
- model,
620
- apiKey,
621
- baseUrl: process.env.OLLAMA_BASE_URL,
622
- proxyUrl: process.env.CONTRACTSPEC_AI_PROXY_URL,
623
- organizationId: process.env.CONTRACTSPEC_ORG_ID,
624
- transport,
625
- apiVersion
626
- });
627
- }
628
- function getAvailableProviders() {
629
- const providers = [];
630
- providers.push({
631
- provider: "ollama",
632
- available: true,
633
- mode: "local",
634
- transports: ["rest", "sdk"],
635
- authMethods: []
636
- });
637
- const openaiKey = process.env.OPENAI_API_KEY;
638
- providers.push({
639
- provider: "openai",
640
- available: Boolean(openaiKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
641
- mode: openaiKey ? "byok" : "managed",
642
- reason: !openaiKey ? "Set OPENAI_API_KEY for BYOK mode" : undefined,
643
- transports: ["rest", "sdk"],
644
- authMethods: ["api-key"]
645
- });
646
- const anthropicKey = process.env.ANTHROPIC_API_KEY;
647
- providers.push({
648
- provider: "anthropic",
649
- available: Boolean(anthropicKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
650
- mode: anthropicKey ? "byok" : "managed",
651
- reason: !anthropicKey ? "Set ANTHROPIC_API_KEY for BYOK mode" : undefined,
652
- transports: ["rest", "sdk"],
653
- authMethods: ["api-key"]
654
- });
655
- const mistralKey = process.env.MISTRAL_API_KEY;
656
- providers.push({
657
- provider: "mistral",
658
- available: Boolean(mistralKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
659
- mode: mistralKey ? "byok" : "managed",
660
- reason: !mistralKey ? "Set MISTRAL_API_KEY for BYOK mode" : undefined,
661
- transports: ["rest", "sdk"],
662
- authMethods: ["api-key"]
663
- });
664
- const geminiKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
665
- providers.push({
666
- provider: "gemini",
667
- available: Boolean(geminiKey) || Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),
668
- mode: geminiKey ? "byok" : "managed",
669
- reason: !geminiKey ? "Set GOOGLE_API_KEY for BYOK mode" : undefined,
670
- transports: ["rest", "sdk"],
671
- authMethods: ["api-key"]
672
- });
673
- return providers;
674
- }
675
-
676
- // src/selector.ts
677
- function createModelSelector(options) {
678
- const { store, fallbackModels, defaultConstraints } = options;
679
- const catalog = fallbackModels ?? MODELS;
680
- return {
681
- async select(context) {
682
- const merged = mergeConstraints(defaultConstraints, context.constraints);
683
- if (context.priorities?.length) {
684
- return selectMultiObjective(store, catalog, context.priorities, merged);
685
- }
686
- const dimension = context.taskDimension ?? "reasoning";
687
- return selectByDimension(store, catalog, dimension, merged);
688
- },
689
- async selectAndCreate(context) {
690
- const selection = await this.select(context);
691
- const model = createProvider({
692
- provider: selection.providerKey,
693
- model: selection.modelId
694
- }).getModel();
695
- return { model, selection };
696
- }
697
- };
698
- }
699
- async function selectByDimension(store, catalog, dimension, constraints) {
700
- const { rankings } = await store.listModelRankings({ dimension, limit: 50 });
701
- const eligible = filterRankings(rankings, catalog, constraints);
702
- const topCandidate = eligible[0];
703
- if (topCandidate) {
704
- const dimScore = topCandidate.dimensionScores[dimension]?.score ?? topCandidate.compositeScore;
705
- return {
706
- modelId: topCandidate.modelId,
707
- providerKey: topCandidate.providerKey,
708
- score: dimScore,
709
- reason: `Top-ranked for "${dimension}" (score ${Math.round(dimScore)})`,
710
- alternatives: eligible.slice(1, 4).map((r) => ({
711
- modelId: r.modelId,
712
- providerKey: r.providerKey,
713
- score: r.dimensionScores[dimension]?.score ?? r.compositeScore
714
- }))
715
- };
716
- }
717
- return fallbackFromCatalog(catalog, constraints, dimension);
718
- }
719
- async function selectMultiObjective(store, catalog, priorities, constraints) {
720
- const { rankings } = await store.listModelRankings({ limit: 100 });
721
- const eligible = filterRankings(rankings, catalog, constraints);
722
- if (eligible.length === 0) {
723
- const primaryDim = priorities.reduce((a, b) => b.weight > a.weight ? b : a).dimension;
724
- return fallbackFromCatalog(catalog, constraints, primaryDim);
725
- }
726
- const totalWeight = priorities.reduce((sum, p) => sum + p.weight, 0) || 1;
727
- const scored = eligible.map((r) => {
728
- let weightedScore = 0;
729
- for (const p of priorities) {
730
- const dimScore = r.dimensionScores[p.dimension]?.score ?? 0;
731
- weightedScore += dimScore * (p.weight / totalWeight);
732
- }
733
- return { ranking: r, weightedScore };
734
- });
735
- scored.sort((a, b) => b.weightedScore - a.weightedScore);
736
- const best = scored[0];
737
- if (!best) {
738
- const primaryDim = priorities.reduce((a, b) => b.weight > a.weight ? b : a).dimension;
739
- return fallbackFromCatalog(catalog, constraints, primaryDim);
740
- }
741
- const dims = priorities.map((p) => p.dimension).join(", ");
742
- return {
743
- modelId: best.ranking.modelId,
744
- providerKey: best.ranking.providerKey,
745
- score: Math.round(best.weightedScore * 100) / 100,
746
- reason: `Multi-objective optimum across [${dims}]`,
747
- alternatives: scored.slice(1, 4).map((s) => ({
748
- modelId: s.ranking.modelId,
749
- providerKey: s.ranking.providerKey,
750
- score: Math.round(s.weightedScore * 100) / 100
751
- }))
752
- };
753
- }
754
- function filterRankings(rankings, catalog, constraints) {
755
- return rankings.filter((r) => {
756
- if (constraints.allowedProviders?.length) {
757
- if (!constraints.allowedProviders.includes(r.providerKey))
758
- return false;
759
- }
760
- if (constraints.excludeModels?.length) {
761
- if (constraints.excludeModels.includes(r.modelId))
762
- return false;
763
- }
764
- const info = getModelInfo(r.modelId) ?? catalog.find((m) => m.id === r.modelId);
765
- if (!info)
766
- return true;
767
- if (constraints.minContextWindow && info.contextWindow < constraints.minContextWindow) {
768
- return false;
769
- }
770
- if (constraints.maxCostPerMillionInput && info.costPerMillion) {
771
- if (info.costPerMillion.input > constraints.maxCostPerMillionInput)
772
- return false;
773
- }
774
- if (constraints.maxCostPerMillionOutput && info.costPerMillion) {
775
- if (info.costPerMillion.output > constraints.maxCostPerMillionOutput)
776
- return false;
777
- }
778
- if (constraints.requiredCapabilities?.length) {
779
- for (const cap of constraints.requiredCapabilities) {
780
- if (!info.capabilities[cap])
781
- return false;
782
- }
783
- }
784
- return true;
785
- });
786
- }
787
- function fallbackFromCatalog(catalog, constraints, dimension) {
788
- let eligible = catalog.filter((m) => m.costPerMillion != null);
789
- const {
790
- allowedProviders,
791
- excludeModels,
792
- minContextWindow,
793
- requiredCapabilities
794
- } = constraints;
795
- if (allowedProviders?.length) {
796
- eligible = eligible.filter((m) => allowedProviders.includes(m.provider));
797
- }
798
- if (excludeModels?.length) {
799
- eligible = eligible.filter((m) => !excludeModels.includes(m.id));
800
- }
801
- if (minContextWindow) {
802
- eligible = eligible.filter((m) => m.contextWindow >= minContextWindow);
803
- }
804
- if (requiredCapabilities?.length) {
805
- eligible = eligible.filter((m) => requiredCapabilities.every((cap) => m.capabilities[cap]));
806
- }
807
- if (eligible.length === 0) {
808
- eligible = catalog.slice(0, 5);
809
- }
810
- eligible.sort((a, b) => {
811
- const costA = a.costPerMillion ? (a.costPerMillion.input + a.costPerMillion.output) / 2 : 999;
812
- const costB = b.costPerMillion ? (b.costPerMillion.input + b.costPerMillion.output) / 2 : 999;
813
- return b.contextWindow / 1e5 - costB - (a.contextWindow / 1e5 - costA);
814
- });
815
- const best = eligible[0];
816
- if (!best) {
817
- return {
818
- modelId: "unknown",
819
- providerKey: "openai",
820
- score: 0,
821
- reason: `No eligible models found for "${dimension}"`,
822
- alternatives: []
823
- };
824
- }
825
- return {
826
- modelId: best.id,
827
- providerKey: best.provider,
828
- score: 0,
829
- reason: `Fallback from catalog (no ranking data for "${dimension}")`,
830
- alternatives: eligible.slice(1, 4).map((m) => ({
831
- modelId: m.id,
832
- providerKey: m.provider,
833
- score: 0
834
- }))
835
- };
836
- }
837
- function mergeConstraints(defaults, overrides) {
838
- if (!defaults)
839
- return overrides ?? {};
840
- if (!overrides)
841
- return defaults;
842
- return { ...defaults, ...overrides };
843
- }
844
- export {
845
- createModelSelector
846
- };
2
+ var W={ollama:"llama3.2",openai:"gpt-5.4",anthropic:"claude-sonnet-4-6",mistral:"mistral-large-latest",gemini:"gemini-2.5-flash"},_=[{id:"llama3.2",name:"Llama 3.2",provider:"ollama",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0}},{id:"codellama",name:"Code Llama",provider:"ollama",contextWindow:16000,capabilities:{vision:!1,tools:!1,reasoning:!1,streaming:!0}},{id:"deepseek-coder",name:"DeepSeek Coder",provider:"ollama",contextWindow:16000,capabilities:{vision:!1,tools:!1,reasoning:!1,streaming:!0}},{id:"mistral",name:"Mistral 7B",provider:"ollama",contextWindow:32000,capabilities:{vision:!1,tools:!1,reasoning:!1,streaming:!0}},{id:"gpt-5.4",name:"GPT-5.4",provider:"openai",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:2.5,output:15}},{id:"gpt-4o",name:"GPT-4o",provider:"openai",contextWindow:128000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:2.5,output:10}},{id:"gpt-4o-mini",name:"GPT-4o Mini",provider:"openai",contextWindow:128000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.15,output:0.6}},{id:"o1",name:"o1",provider:"openai",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:15,output:60}},{id:"o1-mini",name:"o1 Mini",provider:"openai",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:3,output:12}},{id:"gpt-5-mini",name:"GPT-5 Mini",provider:"openai",contextWindow:400000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.25,output:2}},{id:"claude-opus-4-6",name:"Claude Opus 4.6",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:5,output:25}},{id:"claude-sonnet-4-6",name:"Claude Sonnet 4.6",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:3,output:15}},{id:"claude-haiku-4-5",name:"Claude Haiku 4.5",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:1,output:5}},{id:"claude-sonnet-4-20250514",name:"Claude Sonnet 4",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:3,output:15}},{id:"claude-3-5-sonnet-20241022",name:"Claude 3.5 Sonnet",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:3,output:15}},{id:"claude-3-5-haiku-20241022",name:"Claude 3.5 Haiku",provider:"anthropic",contextWindow:200000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.8,output:4}},{id:"mistral-large-2512",name:"Mistral Large 3",provider:"mistral",contextWindow:256000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.5,output:1.5}},{id:"devstral-2512",name:"Devstral 2",provider:"mistral",contextWindow:256000,capabilities:{vision:!1,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:0.4,output:2}},{id:"mistral-medium-2508",name:"Mistral Medium 3.1",provider:"mistral",contextWindow:128000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.4,output:2}},{id:"mistral-small-2506",name:"Mistral Small 3.2",provider:"mistral",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0}},{id:"mistral-large-latest",name:"Mistral Large",provider:"mistral",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:2,output:6}},{id:"mistral-medium-latest",name:"Mistral Medium",provider:"mistral",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0}},{id:"codestral-latest",name:"Codestral",provider:"mistral",contextWindow:256000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.2,output:0.6}},{id:"devstral-small-latest",name:"Devstral Small",provider:"mistral",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!0,streaming:!0}},{id:"magistral-medium-latest",name:"Magistral Medium",provider:"mistral",contextWindow:128000,capabilities:{vision:!1,tools:!0,reasoning:!0,streaming:!0}},{id:"pixtral-large-latest",name:"Pixtral Large",provider:"mistral",contextWindow:128000,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0}},{id:"mistral-small-latest",name:"Mistral Small",provider:"mistral",contextWindow:32000,capabilities:{vision:!1,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.2,output:0.6}},{id:"gemini-2.0-flash",name:"Gemini 2.0 Flash",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0},costPerMillion:{input:0.075,output:0.3}},{id:"gemini-2.5-pro-preview-06-05",name:"Gemini 2.5 Pro",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:1.25,output:10}},{id:"gemini-2.5-flash-preview-05-20",name:"Gemini 2.5 Flash",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0},costPerMillion:{input:0.15,output:0.6}},{id:"gemini-3.1-pro-preview",name:"Gemini 3.1 Pro",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0}},{id:"gemini-3.1-flash-lite-preview",name:"Gemini 3.1 Flash-Lite",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!0,streaming:!0}},{id:"gemini-3-flash-preview",name:"Gemini 3 Flash",provider:"gemini",contextWindow:1e6,capabilities:{vision:!0,tools:!0,reasoning:!1,streaming:!0}}];function T(z){return _.filter((N)=>N.provider===z)}function E(z){return _.find((N)=>N.id===z)}function x(z){return T(z==="claude"?"anthropic":z==="custom"?"openai":z).map((H)=>H.id)}function u(z){return W[z]}import{createAnthropic as F}from"@ai-sdk/anthropic";import{createGoogleGenerativeAI as w}from"@ai-sdk/google";import{createMistral as P}from"@ai-sdk/mistral";import{createOpenAI as I}from"@ai-sdk/openai";import{createOllama as D}from"ollama-ai-provider";class L{name;model;mode;config;transport;authMethod;apiVersion;customHeaders;cachedModel=null;constructor(z){this.name=z.provider,this.model=z.model??W[z.provider],this.mode=this.determineMode(z),this.config=z,this.transport=z.transport,this.authMethod=z.authMethod,this.apiVersion=z.apiVersion,this.customHeaders=z.customHeaders}getModel(){if(!this.cachedModel)this.cachedModel=this.createModel();return this.cachedModel}async listModels(){if(this.name==="ollama")return this.listOllamaModels();return T(this.name)}async validate(){if(this.name==="ollama")return this.validateOllama();if(this.mode==="byok"&&!this.config.apiKey)return{valid:!1,error:`API key required for ${this.name}`};if(this.mode==="managed"&&!this.config.proxyUrl&&!this.config.organizationId)return{valid:!1,error:"Managed mode requires proxyUrl or organizationId"};return{valid:!0}}determineMode(z){if(z.provider==="ollama")return"local";if(z.apiKey)return"byok";return"managed"}createModel(){let{baseUrl:z,proxyUrl:N,apiKey:H}=this.config,J=this.customHeaders;if(this.name==="ollama")return D({baseURL:z,headers:J})(this.model);if(this.mode==="managed"&&N)return I({baseURL:N,apiKey:H,headers:J})(this.model);switch(this.name){case"openai":return I({apiKey:H,headers:J})(this.model);case"anthropic":return F({apiKey:H,headers:J})(this.model);case"mistral":return P({apiKey:H,headers:J})(this.model);case"gemini":return w({apiKey:H,headers:J})(this.model);default:throw Error(`Unknown provider: ${this.name}`)}}async listOllamaModels(){try{let z=this.config.baseUrl??"http://localhost:11434",N=await fetch(`${z}/api/tags`);if(!N.ok)return T("ollama");return((await N.json()).models??[]).map((Q)=>({id:Q.name,name:Q.name,provider:"ollama",contextWindow:8000,capabilities:{vision:!1,tools:!1,reasoning:!1,streaming:!0}}))}catch{return T("ollama")}}async validateOllama(){try{let z=this.config.baseUrl??"http://localhost:11434",N=await fetch(`${z}/api/tags`);if(!N.ok)return{valid:!1,error:`Ollama server returned ${N.status}`};let J=(await N.json()).models??[];if(!J.some((X)=>X.name===this.model))return{valid:!1,error:`Model "${this.model}" not found. Available: ${J.map((X)=>X.name).join(", ")}`};return{valid:!0}}catch(z){return{valid:!1,error:`Cannot connect to Ollama at ${this.config.baseUrl??"http://localhost:11434"}: ${z instanceof Error?z.message:String(z)}`}}}}function B(z){return new L(z)}function b(){let z=process.env.CONTRACTSPEC_AI_PROVIDER??"openai",N=process.env.CONTRACTSPEC_AI_MODEL,H;switch(z){case"openai":H=process.env.OPENAI_API_KEY;break;case"anthropic":H=process.env.ANTHROPIC_API_KEY;break;case"mistral":H=process.env.MISTRAL_API_KEY;break;case"gemini":H=process.env.GOOGLE_API_KEY??process.env.GEMINI_API_KEY;break;case"ollama":break}let J=process.env.CONTRACTSPEC_AI_TRANSPORT,Q=process.env.CONTRACTSPEC_AI_API_VERSION;return B({provider:z,model:N,apiKey:H,baseUrl:process.env.OLLAMA_BASE_URL,proxyUrl:process.env.CONTRACTSPEC_AI_PROXY_URL,organizationId:process.env.CONTRACTSPEC_ORG_ID,transport:J,apiVersion:Q})}function p(){let z=[];z.push({provider:"ollama",available:!0,mode:"local",transports:["rest","sdk"],authMethods:[]});let N=process.env.OPENAI_API_KEY;z.push({provider:"openai",available:Boolean(N)||Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),mode:N?"byok":"managed",reason:!N?"Set OPENAI_API_KEY for BYOK mode":void 0,transports:["rest","sdk"],authMethods:["api-key"]});let H=process.env.ANTHROPIC_API_KEY;z.push({provider:"anthropic",available:Boolean(H)||Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),mode:H?"byok":"managed",reason:!H?"Set ANTHROPIC_API_KEY for BYOK mode":void 0,transports:["rest","sdk"],authMethods:["api-key"]});let J=process.env.MISTRAL_API_KEY;z.push({provider:"mistral",available:Boolean(J)||Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),mode:J?"byok":"managed",reason:!J?"Set MISTRAL_API_KEY for BYOK mode":void 0,transports:["rest","sdk"],authMethods:["api-key"]});let Q=process.env.GOOGLE_API_KEY??process.env.GEMINI_API_KEY;return z.push({provider:"gemini",available:Boolean(Q)||Boolean(process.env.CONTRACTSPEC_AI_PROXY_URL),mode:Q?"byok":"managed",reason:!Q?"Set GOOGLE_API_KEY for BYOK mode":void 0,transports:["rest","sdk"],authMethods:["api-key"]}),z}function l(z){let{store:N,fallbackModels:H,defaultConstraints:J}=z,Q=H??_;return{async select(X){let $=C(J,X.constraints);if(X.priorities?.length)return S(N,Q,X.priorities,$);let q=X.taskDimension??"reasoning";return O(N,Q,q,$)},async selectAndCreate(X){let $=await this.select(X);return{model:B({provider:$.providerKey,model:$.modelId}).getModel(),selection:$}}}}async function O(z,N,H,J){let{rankings:Q}=await z.listModelRankings({dimension:H,limit:50}),X=U(Q,N,J),$=X[0];if($){let q=$.dimensionScores[H]?.score??$.compositeScore;return{modelId:$.modelId,providerKey:$.providerKey,score:q,reason:`Top-ranked for "${H}" (score ${Math.round(q)})`,alternatives:X.slice(1,4).map((j)=>({modelId:j.modelId,providerKey:j.providerKey,score:j.dimensionScores[H]?.score??j.compositeScore}))}}return G(N,J,H)}async function S(z,N,H,J){let{rankings:Q}=await z.listModelRankings({limit:100}),X=U(Q,N,J);if(X.length===0){let Y=H.reduce((V,R)=>R.weight>V.weight?R:V).dimension;return G(N,J,Y)}let $=H.reduce((Y,V)=>Y+V.weight,0)||1,q=X.map((Y)=>{let V=0;for(let R of H){let A=Y.dimensionScores[R.dimension]?.score??0;V+=A*(R.weight/$)}return{ranking:Y,weightedScore:V}});q.sort((Y,V)=>V.weightedScore-Y.weightedScore);let j=q[0];if(!j){let Y=H.reduce((V,R)=>R.weight>V.weight?R:V).dimension;return G(N,J,Y)}let Z=H.map((Y)=>Y.dimension).join(", ");return{modelId:j.ranking.modelId,providerKey:j.ranking.providerKey,score:Math.round(j.weightedScore*100)/100,reason:`Multi-objective optimum across [${Z}]`,alternatives:q.slice(1,4).map((Y)=>({modelId:Y.ranking.modelId,providerKey:Y.ranking.providerKey,score:Math.round(Y.weightedScore*100)/100}))}}function U(z,N,H){return z.filter((J)=>{if(H.allowedProviders?.length){if(!H.allowedProviders.includes(J.providerKey))return!1}if(H.excludeModels?.length){if(H.excludeModels.includes(J.modelId))return!1}let Q=E(J.modelId)??N.find((X)=>X.id===J.modelId);if(!Q)return!0;if(H.minContextWindow&&Q.contextWindow<H.minContextWindow)return!1;if(H.maxCostPerMillionInput&&Q.costPerMillion){if(Q.costPerMillion.input>H.maxCostPerMillionInput)return!1}if(H.maxCostPerMillionOutput&&Q.costPerMillion){if(Q.costPerMillion.output>H.maxCostPerMillionOutput)return!1}if(H.requiredCapabilities?.length){for(let X of H.requiredCapabilities)if(!Q.capabilities[X])return!1}return!0})}function G(z,N,H){let J=z.filter((Z)=>Z.costPerMillion!=null),{allowedProviders:Q,excludeModels:X,minContextWindow:$,requiredCapabilities:q}=N;if(Q?.length)J=J.filter((Z)=>Q.includes(Z.provider));if(X?.length)J=J.filter((Z)=>!X.includes(Z.id));if($)J=J.filter((Z)=>Z.contextWindow>=$);if(q?.length)J=J.filter((Z)=>q.every((Y)=>Z.capabilities[Y]));if(J.length===0)J=z.slice(0,5);J.sort((Z,Y)=>{let V=Z.costPerMillion?(Z.costPerMillion.input+Z.costPerMillion.output)/2:999,R=Y.costPerMillion?(Y.costPerMillion.input+Y.costPerMillion.output)/2:999;return Y.contextWindow/1e5-R-(Z.contextWindow/1e5-V)});let j=J[0];if(!j)return{modelId:"unknown",providerKey:"openai",score:0,reason:`No eligible models found for "${H}"`,alternatives:[]};return{modelId:j.id,providerKey:j.provider,score:0,reason:`Fallback from catalog (no ranking data for "${H}")`,alternatives:J.slice(1,4).map((Z)=>({modelId:Z.id,providerKey:Z.provider,score:0}))}}function C(z,N){if(!z)return N??{};if(!N)return z;return{...z,...N}}export{l as createModelSelector};