genai-lite 0.8.2 → 0.9.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +210 -207
  3. package/dist/adapters/image/GenaiElectronImageAdapter.d.ts +1 -0
  4. package/dist/adapters/image/GenaiElectronImageAdapter.js +5 -5
  5. package/dist/adapters/image/OpenAIImageAdapter.d.ts +1 -0
  6. package/dist/adapters/image/OpenAIImageAdapter.js +4 -4
  7. package/dist/config/llm-presets.json +24 -0
  8. package/dist/image/ImageService.js +2 -0
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.js +6 -1
  11. package/dist/llm/LLMService.d.ts +30 -1
  12. package/dist/llm/LLMService.js +43 -5
  13. package/dist/llm/clients/AnthropicClientAdapter.d.ts +6 -2
  14. package/dist/llm/clients/AnthropicClientAdapter.js +26 -11
  15. package/dist/llm/clients/GeminiClientAdapter.d.ts +6 -2
  16. package/dist/llm/clients/GeminiClientAdapter.js +21 -9
  17. package/dist/llm/clients/LlamaCppClientAdapter.d.ts +8 -4
  18. package/dist/llm/clients/LlamaCppClientAdapter.js +131 -28
  19. package/dist/llm/clients/MistralClientAdapter.d.ts +6 -2
  20. package/dist/llm/clients/MistralClientAdapter.js +22 -11
  21. package/dist/llm/clients/MockClientAdapter.d.ts +2 -2
  22. package/dist/llm/clients/MockClientAdapter.js +36 -19
  23. package/dist/llm/clients/OpenAIClientAdapter.d.ts +6 -2
  24. package/dist/llm/clients/OpenAIClientAdapter.js +33 -9
  25. package/dist/llm/clients/OpenRouterClientAdapter.d.ts +6 -2
  26. package/dist/llm/clients/OpenRouterClientAdapter.js +87 -16
  27. package/dist/llm/clients/types.d.ts +18 -1
  28. package/dist/llm/clients/types.js +4 -0
  29. package/dist/llm/config.d.ts +9 -2
  30. package/dist/llm/config.js +697 -33
  31. package/dist/llm/services/ModelResolver.d.ts +6 -0
  32. package/dist/llm/services/ModelResolver.js +57 -17
  33. package/dist/llm/services/RequestValidator.d.ts +8 -0
  34. package/dist/llm/services/RequestValidator.js +9 -2
  35. package/dist/llm/services/SettingsManager.d.ts +1 -1
  36. package/dist/llm/services/SettingsManager.js +92 -4
  37. package/dist/llm/types.d.ts +127 -0
  38. package/dist/prompting/index.d.ts +1 -1
  39. package/dist/prompting/index.js +2 -1
  40. package/dist/prompting/parser.d.ts +23 -0
  41. package/dist/prompting/parser.js +33 -0
  42. package/dist/shared/adapters/errorUtils.d.ts +15 -0
  43. package/dist/shared/adapters/errorUtils.js +69 -1
  44. package/dist/shared/adapters/logprobsUtils.d.ts +13 -0
  45. package/dist/shared/adapters/logprobsUtils.js +33 -0
  46. package/dist/shared/services/AdapterRegistry.js +3 -1
  47. package/dist/shared/services/withRetry.d.ts +50 -0
  48. package/dist/shared/services/withRetry.js +78 -0
  49. package/dist/types/image.d.ts +2 -0
  50. package/package.json +59 -59
  51. package/src/config/image-presets.json +212 -212
  52. package/src/config/llm-presets.json +623 -599
@@ -44,7 +44,8 @@ exports.ADAPTER_CONFIGS = {
44
44
  baseURL: process.env.ANTHROPIC_API_BASE_URL || undefined,
45
45
  },
46
46
  llamacpp: {
47
- baseURL: process.env.LLAMACPP_API_BASE_URL || 'http://localhost:8080',
47
+ // 127.0.0.1 (not localhost) avoids a ~2s/request IPv6-fallback stall on Windows
48
+ baseURL: process.env.LLAMACPP_API_BASE_URL || 'http://127.0.0.1:8080',
48
49
  },
49
50
  openrouter: {
50
51
  baseURL: process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1',
@@ -63,6 +64,10 @@ exports.DEFAULT_LLM_SETTINGS = {
63
64
  stopSequences: [],
64
65
  frequencyPenalty: 0.0,
65
66
  presencePenalty: 0.0,
67
+ topK: undefined, // No universal default; provider support varies, filtered when undefined
68
+ minP: undefined, // No universal default; explicit per-model defaults for detected GGUF models
69
+ repeatPenalty: undefined, // No universal default; explicit per-model defaults for detected GGUF models
70
+ seed: undefined, // No universal default; deterministic sampling is opt-in
66
71
  supportsSystemMessage: true,
67
72
  systemMessageFallback: {
68
73
  format: 'xml',
@@ -89,6 +94,9 @@ exports.DEFAULT_LLM_SETTINGS = {
89
94
  },
90
95
  openRouterProvider: undefined, // Optional, only used with OpenRouter provider
91
96
  structuredOutput: undefined, // Optional, enables JSON schema-constrained output
97
+ logprobs: undefined, // Optional, per-token log probabilities (llama.cpp/OpenAI/OpenRouter)
98
+ topLogprobs: undefined, // Optional, number of alternatives per token
99
+ llamacpp: undefined, // Optional, llama.cpp-specific settings (grammar, chatTemplateKwargs)
92
100
  };
93
101
  /**
94
102
  * Per-provider default setting overrides
@@ -123,19 +131,23 @@ exports.SUPPORTED_PROVIDERS = [
123
131
  {
124
132
  id: "openai",
125
133
  name: "OpenAI",
126
- unsupportedParameters: ["frequencyPenalty"],
134
+ unsupportedParameters: ["frequencyPenalty", "topK", "minP", "repeatPenalty"],
127
135
  },
128
136
  {
129
137
  id: "anthropic",
130
138
  name: "Anthropic",
139
+ unsupportedParameters: ["seed", "minP", "repeatPenalty", "logprobs", "topLogprobs"],
131
140
  },
132
141
  {
133
142
  id: "gemini",
134
143
  name: "Google Gemini",
144
+ // Gemini has its own logprobs mechanism with a different shape; not mapped yet
145
+ unsupportedParameters: ["minP", "repeatPenalty", "logprobs", "topLogprobs"],
135
146
  },
136
147
  {
137
148
  id: "mistral",
138
149
  name: "Mistral AI",
150
+ unsupportedParameters: ["topK", "minP", "repeatPenalty", "logprobs", "topLogprobs"],
139
151
  },
140
152
  {
141
153
  id: "llamacpp",
@@ -153,16 +165,241 @@ exports.SUPPORTED_PROVIDERS = [
153
165
  allowUnknownModels: true, // Test provider accepts any model
154
166
  },
155
167
  ];
168
+ // ---------------------------------------------------------------------------
169
+ // Shared vendor sampling profiles and reasoning-toggle metadata for GGUF models.
170
+ // Sampling values follow vendor model cards; llama.cpp's own server defaults
171
+ // (temperature 0.8, top_k 40, min_p 0.05) match no vendor's recommendation, so
172
+ // detected models set them explicitly. See docs/dev/adding-models-and-providers.md.
173
+ // ---------------------------------------------------------------------------
174
+ const HYBRID_REASONING = {
175
+ supported: true,
176
+ enabledByDefault: false,
177
+ canDisable: true,
178
+ };
179
+ const ALWAYS_ON_REASONING = {
180
+ supported: true,
181
+ enabledByDefault: true,
182
+ canDisable: false,
183
+ };
184
+ const THINK_MARKERS = ["<think>", "</think>"];
185
+ /** Qwen chat templates: enable_thinking flag; template injects an empty think block when disabled */
186
+ const QWEN_LOCAL_REASONING = {
187
+ toggleKwarg: "enable_thinking",
188
+ nothinkPrefix: "<think>\n\n</think>\n\n",
189
+ markers: THINK_MARKERS,
190
+ };
191
+ /** Gemma 4 chat template: enable_thinking flag; harmony-style thought-channel markers */
192
+ const GEMMA4_LOCAL_REASONING = {
193
+ toggleKwarg: "enable_thinking",
194
+ nothinkPrefix: "<|channel>thought\n<channel|>",
195
+ markers: ["<|channel>thought", "<channel|>"],
196
+ };
197
+ // Vendor sampling profiles (temperature / topP / topK / minP / repeatPenalty)
198
+ const QWEN_NONTHINKING_SAMPLING = {
199
+ temperature: 0.7, topP: 0.8, topK: 20, minP: 0, repeatPenalty: 1.0,
200
+ };
201
+ const QWEN3_THINKING_SAMPLING = {
202
+ temperature: 0.6, topP: 0.95, topK: 20, minP: 0, repeatPenalty: 1.0,
203
+ };
204
+ const QWEN35_THINKING_SAMPLING = {
205
+ temperature: 1.0, topP: 0.95, topK: 20, minP: 0, repeatPenalty: 1.0,
206
+ };
207
+ const GEMMA_SAMPLING = {
208
+ temperature: 1.0, topP: 0.95, topK: 64, minP: 0, repeatPenalty: 1.0,
209
+ };
210
+ const GPT_OSS_SAMPLING = {
211
+ temperature: 1.0, topP: 1.0, topK: 0, minP: 0, repeatPenalty: 1.0,
212
+ };
213
+ const MINISTRAL_INSTRUCT_SAMPLING = {
214
+ temperature: 0.15, minP: 0, repeatPenalty: 1.0,
215
+ };
216
+ const MINISTRAL_REASONING_SAMPLING = {
217
+ temperature: 0.7, topP: 0.95, minP: 0, repeatPenalty: 1.0,
218
+ };
219
+ const GRANITE_SAMPLING = {
220
+ temperature: 0.7, topP: 0.95, topK: 0, minP: 0, repeatPenalty: 1.0,
221
+ };
222
+ const LLAMA32_SAMPLING = {
223
+ temperature: 0.6, topP: 0.9, topK: 0, minP: 0, repeatPenalty: 1.0,
224
+ };
156
225
  /**
157
226
  * Known GGUF model patterns for capability detection
158
227
  *
159
228
  * Order matters: more specific patterns should come before generic ones.
160
229
  * First matching pattern wins.
161
230
  *
162
- * Example: "Qwen3-0.6B-0522" should be before "Qwen3-0.6B"
231
+ * Ordering rules (see docs/dev/adding-models-and-providers.md):
232
+ * - Specific before generic: "qwen3-4b-instruct-2507" before "qwen3-4b"
233
+ * - Newer family names before older substrings they contain: "qwen3.5" contains no
234
+ * "qwen3-" (the dot breaks the match) but is listed first anyway for clarity
235
+ * - Quantization agnostic: never embed Q4_K_M/Q8_0 in patterns
163
236
  */
164
237
  exports.KNOWN_GGUF_MODELS = [
165
- // Qwen 3 Series - All support thinking/reasoning
238
+ // --- Qwen 3.5 (hybrid thinking; enable_thinking toggle) ---
239
+ {
240
+ pattern: "qwen3.5-2b",
241
+ name: "Qwen 3.5 2B",
242
+ description: "Qwen 3.5 2B hybrid-thinking model",
243
+ capabilities: {
244
+ maxTokens: 8192,
245
+ contextWindow: 131072,
246
+ supportsImages: false,
247
+ supportsPromptCache: false,
248
+ reasoning: { ...HYBRID_REASONING },
249
+ localReasoning: QWEN_LOCAL_REASONING,
250
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
251
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
252
+ },
253
+ },
254
+ {
255
+ pattern: "qwen3.5-4b",
256
+ name: "Qwen 3.5 4B",
257
+ description: "Qwen 3.5 4B hybrid-thinking model",
258
+ capabilities: {
259
+ maxTokens: 8192,
260
+ contextWindow: 131072,
261
+ supportsImages: false,
262
+ supportsPromptCache: false,
263
+ reasoning: { ...HYBRID_REASONING },
264
+ localReasoning: QWEN_LOCAL_REASONING,
265
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
266
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
267
+ },
268
+ },
269
+ {
270
+ pattern: "qwen3.5-9b",
271
+ name: "Qwen 3.5 9B",
272
+ description: "Qwen 3.5 9B hybrid-thinking model",
273
+ capabilities: {
274
+ maxTokens: 16384,
275
+ contextWindow: 131072,
276
+ supportsImages: false,
277
+ supportsPromptCache: false,
278
+ reasoning: { ...HYBRID_REASONING },
279
+ localReasoning: QWEN_LOCAL_REASONING,
280
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
281
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
282
+ },
283
+ },
284
+ {
285
+ pattern: "qwen3.5",
286
+ name: "Qwen 3.5",
287
+ description: "Qwen 3.5 hybrid-thinking model (size not recognized)",
288
+ capabilities: {
289
+ maxTokens: 8192,
290
+ contextWindow: 131072,
291
+ supportsImages: false,
292
+ supportsPromptCache: false,
293
+ reasoning: { ...HYBRID_REASONING },
294
+ localReasoning: QWEN_LOCAL_REASONING,
295
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
296
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
297
+ },
298
+ },
299
+ // --- Qwen 3.6 (hybrid thinking; same profiles as 3.5 per family reuse) ---
300
+ {
301
+ pattern: "qwen3.6-27b",
302
+ name: "Qwen 3.6 27B",
303
+ description: "Qwen 3.6 27B hybrid-thinking model",
304
+ capabilities: {
305
+ maxTokens: 16384,
306
+ contextWindow: 131072,
307
+ supportsImages: false,
308
+ supportsPromptCache: false,
309
+ reasoning: { ...HYBRID_REASONING },
310
+ localReasoning: QWEN_LOCAL_REASONING,
311
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
312
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
313
+ },
314
+ },
315
+ {
316
+ pattern: "qwen3.6-35b",
317
+ name: "Qwen 3.6 35B-A3B",
318
+ description: "Qwen 3.6 35B MoE (3B active) hybrid-thinking model",
319
+ capabilities: {
320
+ maxTokens: 16384,
321
+ contextWindow: 131072,
322
+ supportsImages: false,
323
+ supportsPromptCache: false,
324
+ reasoning: { ...HYBRID_REASONING },
325
+ localReasoning: QWEN_LOCAL_REASONING,
326
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
327
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
328
+ },
329
+ },
330
+ {
331
+ pattern: "qwen3.6",
332
+ name: "Qwen 3.6",
333
+ description: "Qwen 3.6 hybrid-thinking model (size not recognized)",
334
+ capabilities: {
335
+ maxTokens: 8192,
336
+ contextWindow: 131072,
337
+ supportsImages: false,
338
+ supportsPromptCache: false,
339
+ reasoning: { ...HYBRID_REASONING },
340
+ localReasoning: QWEN_LOCAL_REASONING,
341
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
342
+ reasoningDefaultSettings: QWEN35_THINKING_SAMPLING,
343
+ },
344
+ },
345
+ // --- Qwen 3 2507 refreshes (must precede the base qwen3-* size patterns) ---
346
+ // Instruct-2507 checkpoints are non-thinking; the enable_thinking:false kwarg and
347
+ // nothink stripping are safe no-ops there (template has no think injection).
348
+ {
349
+ pattern: "qwen3-4b-instruct-2507",
350
+ name: "Qwen 3 4B Instruct 2507",
351
+ description: "Qwen 3 4B Instruct-2507 (non-thinking checkpoint)",
352
+ capabilities: {
353
+ maxTokens: 8192,
354
+ contextWindow: 131072,
355
+ supportsImages: false,
356
+ supportsPromptCache: false,
357
+ localReasoning: QWEN_LOCAL_REASONING,
358
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
359
+ },
360
+ },
361
+ {
362
+ pattern: "qwen3-4b-thinking-2507",
363
+ name: "Qwen 3 4B Thinking 2507",
364
+ description: "Qwen 3 4B Thinking-2507 (thinking-only checkpoint)",
365
+ capabilities: {
366
+ maxTokens: 16384,
367
+ contextWindow: 131072,
368
+ supportsImages: false,
369
+ supportsPromptCache: false,
370
+ reasoning: { ...ALWAYS_ON_REASONING },
371
+ localReasoning: { markers: THINK_MARKERS },
372
+ defaultSettings: QWEN3_THINKING_SAMPLING,
373
+ },
374
+ },
375
+ {
376
+ pattern: "qwen3-30b-a3b-instruct-2507",
377
+ name: "Qwen 3 30B-A3B Instruct 2507",
378
+ description: "Qwen 3 30B-A3B Instruct-2507 (non-thinking checkpoint)",
379
+ capabilities: {
380
+ maxTokens: 16384,
381
+ contextWindow: 131072,
382
+ supportsImages: false,
383
+ supportsPromptCache: false,
384
+ localReasoning: QWEN_LOCAL_REASONING,
385
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
386
+ },
387
+ },
388
+ {
389
+ pattern: "qwen3-30b-a3b-thinking-2507",
390
+ name: "Qwen 3 30B-A3B Thinking 2507",
391
+ description: "Qwen 3 30B-A3B Thinking-2507 (thinking-only checkpoint)",
392
+ capabilities: {
393
+ maxTokens: 16384,
394
+ contextWindow: 131072,
395
+ supportsImages: false,
396
+ supportsPromptCache: false,
397
+ reasoning: { ...ALWAYS_ON_REASONING },
398
+ localReasoning: { markers: THINK_MARKERS },
399
+ defaultSettings: QWEN3_THINKING_SAMPLING,
400
+ },
401
+ },
402
+ // --- Qwen 3 Series (original hybrid checkpoints; enable_thinking toggle) ---
166
403
  {
167
404
  pattern: "qwen3-30b",
168
405
  name: "Qwen 3 30B",
@@ -173,11 +410,12 @@ exports.KNOWN_GGUF_MODELS = [
173
410
  supportsImages: false,
174
411
  supportsPromptCache: false,
175
412
  reasoning: {
176
- supported: true,
177
- enabledByDefault: false,
178
- canDisable: true,
413
+ ...HYBRID_REASONING,
179
414
  maxBudget: 38912,
180
415
  },
416
+ localReasoning: QWEN_LOCAL_REASONING,
417
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
418
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
181
419
  },
182
420
  },
183
421
  {
@@ -190,11 +428,12 @@ exports.KNOWN_GGUF_MODELS = [
190
428
  supportsImages: false,
191
429
  supportsPromptCache: false,
192
430
  reasoning: {
193
- supported: true,
194
- enabledByDefault: false,
195
- canDisable: true,
431
+ ...HYBRID_REASONING,
196
432
  maxBudget: 38912,
197
433
  },
434
+ localReasoning: QWEN_LOCAL_REASONING,
435
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
436
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
198
437
  },
199
438
  },
200
439
  {
@@ -207,11 +446,12 @@ exports.KNOWN_GGUF_MODELS = [
207
446
  supportsImages: false,
208
447
  supportsPromptCache: false,
209
448
  reasoning: {
210
- supported: true,
211
- enabledByDefault: false,
212
- canDisable: true,
449
+ ...HYBRID_REASONING,
213
450
  maxBudget: 38912,
214
451
  },
452
+ localReasoning: QWEN_LOCAL_REASONING,
453
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
454
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
215
455
  },
216
456
  },
217
457
  {
@@ -224,11 +464,12 @@ exports.KNOWN_GGUF_MODELS = [
224
464
  supportsImages: false,
225
465
  supportsPromptCache: false,
226
466
  reasoning: {
227
- supported: true,
228
- enabledByDefault: false,
229
- canDisable: true,
467
+ ...HYBRID_REASONING,
230
468
  maxBudget: 38912,
231
469
  },
470
+ localReasoning: QWEN_LOCAL_REASONING,
471
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
472
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
232
473
  },
233
474
  },
234
475
  {
@@ -241,11 +482,12 @@ exports.KNOWN_GGUF_MODELS = [
241
482
  supportsImages: false,
242
483
  supportsPromptCache: false,
243
484
  reasoning: {
244
- supported: true,
245
- enabledByDefault: false,
246
- canDisable: true,
485
+ ...HYBRID_REASONING,
247
486
  maxBudget: 30720,
248
487
  },
488
+ localReasoning: QWEN_LOCAL_REASONING,
489
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
490
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
249
491
  },
250
492
  },
251
493
  {
@@ -258,15 +500,304 @@ exports.KNOWN_GGUF_MODELS = [
258
500
  supportsImages: false,
259
501
  supportsPromptCache: false,
260
502
  reasoning: {
261
- supported: true,
262
- enabledByDefault: false,
263
- canDisable: true,
503
+ ...HYBRID_REASONING,
264
504
  maxBudget: 30720,
265
505
  },
506
+ localReasoning: QWEN_LOCAL_REASONING,
507
+ defaultSettings: QWEN_NONTHINKING_SAMPLING,
508
+ reasoningDefaultSettings: QWEN3_THINKING_SAMPLING,
509
+ },
510
+ },
511
+ // --- Gemma 4 (hybrid thinking, supports system messages) ---
512
+ // Note: enable_thinking:true is best-effort on Gemma 4 — the chat-template flag
513
+ // activates the thought channel unreliably. enable_thinking:false works reliably.
514
+ {
515
+ pattern: "gemma-4-e2b",
516
+ name: "Gemma 4 E2B",
517
+ description: "Gemma 4 E2B (2.3B effective) hybrid-thinking model",
518
+ capabilities: {
519
+ maxTokens: 8192,
520
+ contextWindow: 32768,
521
+ supportsImages: false,
522
+ supportsPromptCache: false,
523
+ supportsSystemMessage: true,
524
+ reasoning: { ...HYBRID_REASONING },
525
+ localReasoning: GEMMA4_LOCAL_REASONING,
526
+ defaultSettings: GEMMA_SAMPLING,
527
+ },
528
+ },
529
+ {
530
+ pattern: "gemma-4-e4b",
531
+ name: "Gemma 4 E4B",
532
+ description: "Gemma 4 E4B (4.5B effective) hybrid-thinking model",
533
+ capabilities: {
534
+ maxTokens: 8192,
535
+ contextWindow: 32768,
536
+ supportsImages: false,
537
+ supportsPromptCache: false,
538
+ supportsSystemMessage: true,
539
+ reasoning: { ...HYBRID_REASONING },
540
+ localReasoning: GEMMA4_LOCAL_REASONING,
541
+ defaultSettings: GEMMA_SAMPLING,
542
+ },
543
+ },
544
+ {
545
+ pattern: "gemma-4-26b-a4b",
546
+ name: "Gemma 4 26B-A4B",
547
+ description: "Gemma 4 26B MoE (~4B active) hybrid-thinking model",
548
+ capabilities: {
549
+ maxTokens: 16384,
550
+ contextWindow: 131072,
551
+ supportsImages: false,
552
+ supportsPromptCache: false,
553
+ supportsSystemMessage: true,
554
+ reasoning: { ...HYBRID_REASONING },
555
+ localReasoning: GEMMA4_LOCAL_REASONING,
556
+ defaultSettings: GEMMA_SAMPLING,
557
+ },
558
+ },
559
+ {
560
+ pattern: "gemma-4-31b",
561
+ name: "Gemma 4 31B",
562
+ description: "Gemma 4 31B dense hybrid-thinking model (256K context)",
563
+ capabilities: {
564
+ maxTokens: 16384,
565
+ contextWindow: 262144,
566
+ supportsImages: false,
567
+ supportsPromptCache: false,
568
+ supportsSystemMessage: true,
569
+ reasoning: { ...HYBRID_REASONING },
570
+ localReasoning: GEMMA4_LOCAL_REASONING,
571
+ defaultSettings: GEMMA_SAMPLING,
572
+ },
573
+ },
574
+ {
575
+ pattern: "gemma-4",
576
+ name: "Gemma 4",
577
+ description: "Gemma 4 hybrid-thinking model (size not recognized)",
578
+ capabilities: {
579
+ maxTokens: 8192,
580
+ contextWindow: 32768,
581
+ supportsImages: false,
582
+ supportsPromptCache: false,
583
+ supportsSystemMessage: true,
584
+ reasoning: { ...HYBRID_REASONING },
585
+ localReasoning: GEMMA4_LOCAL_REASONING,
586
+ defaultSettings: GEMMA_SAMPLING,
587
+ },
588
+ },
589
+ // --- Gemma 3 (no thinking, no system-message role) ---
590
+ {
591
+ pattern: "gemma-3-1b",
592
+ name: "Gemma 3 1B",
593
+ description: "Gemma 3 1B instruction-tuned model",
594
+ capabilities: {
595
+ maxTokens: 8192,
596
+ contextWindow: 32768,
597
+ supportsImages: false,
598
+ supportsPromptCache: false,
599
+ supportsSystemMessage: false,
600
+ defaultSettings: GEMMA_SAMPLING,
601
+ },
602
+ },
603
+ {
604
+ pattern: "gemma-3-4b",
605
+ name: "Gemma 3 4B",
606
+ description: "Gemma 3 4B instruction-tuned model",
607
+ capabilities: {
608
+ maxTokens: 8192,
609
+ contextWindow: 131072,
610
+ supportsImages: false,
611
+ supportsPromptCache: false,
612
+ supportsSystemMessage: false,
613
+ defaultSettings: GEMMA_SAMPLING,
614
+ },
615
+ },
616
+ {
617
+ pattern: "gemma-3-12b",
618
+ name: "Gemma 3 12B",
619
+ description: "Gemma 3 12B instruction-tuned model",
620
+ capabilities: {
621
+ maxTokens: 8192,
622
+ contextWindow: 131072,
623
+ supportsImages: false,
624
+ supportsPromptCache: false,
625
+ supportsSystemMessage: false,
626
+ defaultSettings: GEMMA_SAMPLING,
627
+ },
628
+ },
629
+ {
630
+ pattern: "gemma-3-27b",
631
+ name: "Gemma 3 27B",
632
+ description: "Gemma 3 27B instruction-tuned model",
633
+ capabilities: {
634
+ maxTokens: 8192,
635
+ contextWindow: 131072,
636
+ supportsImages: false,
637
+ supportsPromptCache: false,
638
+ supportsSystemMessage: false,
639
+ defaultSettings: GEMMA_SAMPLING,
640
+ },
641
+ },
642
+ {
643
+ pattern: "gemma-3",
644
+ name: "Gemma 3",
645
+ description: "Gemma 3 instruction-tuned model (size not recognized)",
646
+ capabilities: {
647
+ maxTokens: 8192,
648
+ contextWindow: 32768,
649
+ supportsImages: false,
650
+ supportsPromptCache: false,
651
+ supportsSystemMessage: false,
652
+ defaultSettings: GEMMA_SAMPLING,
653
+ },
654
+ },
655
+ // --- GPT-OSS (harmony format; reasoning always on, cannot be disabled) ---
656
+ {
657
+ pattern: "gpt-oss-120b",
658
+ name: "GPT-OSS 120B",
659
+ description: "OpenAI GPT-OSS 120B reasoning-native model (harmony format)",
660
+ capabilities: {
661
+ maxTokens: 16384,
662
+ contextWindow: 131072,
663
+ supportsImages: false,
664
+ supportsPromptCache: false,
665
+ reasoning: { ...ALWAYS_ON_REASONING },
666
+ defaultSettings: GPT_OSS_SAMPLING,
667
+ },
668
+ },
669
+ {
670
+ pattern: "gpt-oss-20b",
671
+ name: "GPT-OSS 20B",
672
+ description: "OpenAI GPT-OSS 20B reasoning-native model (harmony format)",
673
+ capabilities: {
674
+ maxTokens: 16384,
675
+ contextWindow: 131072,
676
+ supportsImages: false,
677
+ supportsPromptCache: false,
678
+ reasoning: { ...ALWAYS_ON_REASONING },
679
+ defaultSettings: GPT_OSS_SAMPLING,
680
+ },
681
+ },
682
+ {
683
+ pattern: "gpt-oss",
684
+ name: "GPT-OSS",
685
+ description: "OpenAI GPT-OSS reasoning-native model (size not recognized)",
686
+ capabilities: {
687
+ maxTokens: 16384,
688
+ contextWindow: 131072,
689
+ supportsImages: false,
690
+ supportsPromptCache: false,
691
+ reasoning: { ...ALWAYS_ON_REASONING },
692
+ defaultSettings: GPT_OSS_SAMPLING,
693
+ },
694
+ },
695
+ // --- Ministral 3 (Reasoning variants before Instruct; Reasoning is a separate
696
+ // checkpoint, not a toggle) ---
697
+ {
698
+ pattern: "ministral-3-3b-reasoning",
699
+ name: "Ministral 3 3B Reasoning",
700
+ description: "Ministral 3 3B Reasoning variant (always-on thinking)",
701
+ capabilities: {
702
+ maxTokens: 16384,
703
+ contextWindow: 131072,
704
+ supportsImages: false,
705
+ supportsPromptCache: false,
706
+ reasoning: { ...ALWAYS_ON_REASONING },
707
+ localReasoning: { markers: THINK_MARKERS },
708
+ defaultSettings: MINISTRAL_REASONING_SAMPLING,
709
+ },
710
+ },
711
+ {
712
+ pattern: "ministral-3-8b-reasoning",
713
+ name: "Ministral 3 8B Reasoning",
714
+ description: "Ministral 3 8B Reasoning variant (always-on thinking)",
715
+ capabilities: {
716
+ maxTokens: 16384,
717
+ contextWindow: 131072,
718
+ supportsImages: false,
719
+ supportsPromptCache: false,
720
+ reasoning: { ...ALWAYS_ON_REASONING },
721
+ localReasoning: { markers: THINK_MARKERS },
722
+ defaultSettings: MINISTRAL_REASONING_SAMPLING,
723
+ },
724
+ },
725
+ {
726
+ pattern: "ministral-3-14b-reasoning",
727
+ name: "Ministral 3 14B Reasoning",
728
+ description: "Ministral 3 14B Reasoning variant (always-on thinking)",
729
+ capabilities: {
730
+ maxTokens: 16384,
731
+ contextWindow: 131072,
732
+ supportsImages: false,
733
+ supportsPromptCache: false,
734
+ reasoning: { ...ALWAYS_ON_REASONING },
735
+ localReasoning: { markers: THINK_MARKERS },
736
+ defaultSettings: MINISTRAL_REASONING_SAMPLING,
737
+ },
738
+ },
739
+ {
740
+ pattern: "ministral-3",
741
+ name: "Ministral 3",
742
+ description: "Ministral 3 Instruct model (thinking disabled via template flag)",
743
+ capabilities: {
744
+ maxTokens: 8192,
745
+ contextWindow: 131072,
746
+ supportsImages: false,
747
+ supportsPromptCache: false,
748
+ // Template supports a thinking flag but the Instruct variant is not a
749
+ // reasoning model; the adapter keeps enable_thinking:false.
750
+ localReasoning: {
751
+ toggleKwarg: "enable_thinking",
752
+ markers: THINK_MARKERS,
753
+ },
754
+ defaultSettings: MINISTRAL_INSTRUCT_SAMPLING,
755
+ },
756
+ },
757
+ // --- Granite 4.1 (no thinking) ---
758
+ {
759
+ pattern: "granite-4.1",
760
+ name: "Granite 4.1",
761
+ description: "IBM Granite 4.1 instruction-tuned model",
762
+ capabilities: {
763
+ maxTokens: 8192,
764
+ contextWindow: 131072,
765
+ supportsImages: false,
766
+ supportsPromptCache: false,
767
+ defaultSettings: GRANITE_SAMPLING,
768
+ },
769
+ },
770
+ // --- DeepSeek R1 (reasoning always on) ---
771
+ {
772
+ pattern: "deepseek-r1",
773
+ name: "DeepSeek R1",
774
+ description: "DeepSeek R1 reasoning model (always-on thinking)",
775
+ capabilities: {
776
+ maxTokens: 16384,
777
+ contextWindow: 131072,
778
+ supportsImages: false,
779
+ supportsPromptCache: false,
780
+ reasoning: { ...ALWAYS_ON_REASONING },
781
+ localReasoning: { markers: THINK_MARKERS },
782
+ defaultSettings: {
783
+ temperature: 0.6, topP: 0.95, minP: 0, repeatPenalty: 1.0,
784
+ },
785
+ },
786
+ },
787
+ // --- Llama 3.2 (no thinking) ---
788
+ {
789
+ pattern: "llama-3.2",
790
+ name: "Llama 3.2",
791
+ description: "Meta Llama 3.2 instruction-tuned model",
792
+ capabilities: {
793
+ maxTokens: 8192,
794
+ contextWindow: 131072,
795
+ supportsImages: false,
796
+ supportsPromptCache: false,
797
+ defaultSettings: LLAMA32_SAMPLING,
266
798
  },
267
799
  },
268
800
  // Add more model patterns here as needed
269
- // DeepSeek, Llama, etc.
270
801
  ];
271
802
  /**
272
803
  * Detects model capabilities from GGUF filename
@@ -635,6 +1166,61 @@ exports.SUPPORTED_MODELS = [
635
1166
  notes: "Gemma models do not support JSON mode via Google's API",
636
1167
  },
637
1168
  },
1169
+ // Google Gemma 4 Models (Open weights, free via Gemini API)
1170
+ // Note: Unlike Gemma 3, Gemma 4 supports a native system role
1171
+ // Note: Reasoning left unsupported on the cloud entries — the Gemini API exposes
1172
+ // no thinking toggle for Gemma (the <|think|> system-token mechanism is not modeled)
1173
+ {
1174
+ id: "gemma-4-4b-it",
1175
+ name: "Gemma 4 4B",
1176
+ providerId: "gemini",
1177
+ contextWindow: 32768,
1178
+ inputPrice: 0.0,
1179
+ outputPrice: 0.0,
1180
+ description: "Google's Gemma 4 4B open model with system-message support (free via Gemini API)",
1181
+ maxTokens: 8192,
1182
+ supportsImages: false,
1183
+ supportsPromptCache: false,
1184
+ supportsSystemMessage: true,
1185
+ structuredOutput: {
1186
+ supported: false,
1187
+ notes: "Gemma models do not support JSON mode via Google's API",
1188
+ },
1189
+ },
1190
+ {
1191
+ id: "gemma-4-26b-a4b-it",
1192
+ name: "Gemma 4 26B-A4B",
1193
+ providerId: "gemini",
1194
+ contextWindow: 131072,
1195
+ inputPrice: 0.0,
1196
+ outputPrice: 0.0,
1197
+ description: "Google's Gemma 4 26B MoE (~4B active) open model (free via Gemini API)",
1198
+ maxTokens: 8192,
1199
+ supportsImages: false,
1200
+ supportsPromptCache: false,
1201
+ supportsSystemMessage: true,
1202
+ structuredOutput: {
1203
+ supported: false,
1204
+ notes: "Gemma models do not support JSON mode via Google's API",
1205
+ },
1206
+ },
1207
+ {
1208
+ id: "gemma-4-31b-it",
1209
+ name: "Gemma 4 31B",
1210
+ providerId: "gemini",
1211
+ contextWindow: 262144,
1212
+ inputPrice: 0.0,
1213
+ outputPrice: 0.0,
1214
+ description: "Google's Gemma 4 31B dense open model with 256K context (free via Gemini API)",
1215
+ maxTokens: 8192,
1216
+ supportsImages: false,
1217
+ supportsPromptCache: false,
1218
+ supportsSystemMessage: true,
1219
+ structuredOutput: {
1220
+ supported: false,
1221
+ notes: "Gemma models do not support JSON mode via Google's API",
1222
+ },
1223
+ },
638
1224
  // OpenAI Models - GPT-5 Series
639
1225
  // Note: GPT-5 models do not support temperature or topP parameters
640
1226
  {
@@ -649,7 +1235,7 @@ exports.SUPPORTED_MODELS = [
649
1235
  supportsImages: true,
650
1236
  supportsPromptCache: true,
651
1237
  cacheReadsPrice: 0.4375,
652
- unsupportedParameters: ["temperature", "topP"],
1238
+ unsupportedParameters: ["temperature", "topP", "seed"],
653
1239
  reasoning: {
654
1240
  supported: true,
655
1241
  enabledByDefault: false,
@@ -673,7 +1259,7 @@ exports.SUPPORTED_MODELS = [
673
1259
  supportsImages: true,
674
1260
  supportsPromptCache: true,
675
1261
  cacheReadsPrice: 0.3125,
676
- unsupportedParameters: ["temperature", "topP"],
1262
+ unsupportedParameters: ["temperature", "topP", "seed"],
677
1263
  reasoning: {
678
1264
  supported: true,
679
1265
  enabledByDefault: false,
@@ -697,7 +1283,7 @@ exports.SUPPORTED_MODELS = [
697
1283
  supportsImages: true,
698
1284
  supportsPromptCache: true,
699
1285
  cacheReadsPrice: 0.0625,
700
- unsupportedParameters: ["temperature", "topP"],
1286
+ unsupportedParameters: ["temperature", "topP", "seed"],
701
1287
  reasoning: {
702
1288
  supported: true,
703
1289
  enabledByDefault: false,
@@ -721,7 +1307,7 @@ exports.SUPPORTED_MODELS = [
721
1307
  supportsImages: true,
722
1308
  supportsPromptCache: true,
723
1309
  cacheReadsPrice: 0.0125,
724
- unsupportedParameters: ["temperature", "topP"],
1310
+ unsupportedParameters: ["temperature", "topP", "seed"],
725
1311
  reasoning: {
726
1312
  supported: true,
727
1313
  enabledByDefault: false,
@@ -746,7 +1332,7 @@ exports.SUPPORTED_MODELS = [
746
1332
  supportsImages: true,
747
1333
  supportsPromptCache: true,
748
1334
  cacheReadsPrice: 0.275,
749
- unsupportedParameters: ["topP"],
1335
+ unsupportedParameters: ["topP", "seed"],
750
1336
  reasoning: {
751
1337
  supported: true,
752
1338
  enabledByDefault: true,
@@ -882,6 +1468,14 @@ exports.SUPPORTED_MODELS = [
882
1468
  maxTokens: 4096,
883
1469
  supportsImages: false,
884
1470
  supportsPromptCache: false,
1471
+ // Optimistic: the server decides which model is loaded, so reasoning requests on
1472
+ // the generic id are not rejected. When the loaded GGUF is recognized, detection
1473
+ // overlays the real capabilities; otherwise extraction degrades gracefully.
1474
+ reasoning: {
1475
+ supported: true,
1476
+ enabledByDefault: false,
1477
+ canDisable: true,
1478
+ },
885
1479
  structuredOutput: {
886
1480
  supported: true,
887
1481
  strictMode: true,
@@ -1018,22 +1612,32 @@ function createFallbackModelInfo(modelId, providerId, capabilities) {
1018
1612
  *
1019
1613
  * @param modelId - The model ID
1020
1614
  * @param providerId - The provider ID
1615
+ * @param resolvedModelInfo - Optional already-resolved ModelInfo (e.g. a detected GGUF
1616
+ * model's fallback info). When provided it is used instead of a registry lookup, so
1617
+ * capabilities and defaultSettings of dynamically-detected models flow into settings.
1021
1618
  * @returns Merged default settings with model-specific overrides applied
1022
1619
  */
1023
- function getDefaultSettingsForModel(modelId, providerId) {
1620
+ function getDefaultSettingsForModel(modelId, providerId, resolvedModelInfo) {
1024
1621
  // Base settings: global defaults, then provider-specific, then model-specific overrides
1025
1622
  const baseDefaults = { ...exports.DEFAULT_LLM_SETTINGS };
1026
1623
  const providerDefaults = exports.PROVIDER_DEFAULT_SETTINGS[providerId] || {};
1027
1624
  const modelDefaults = exports.MODEL_DEFAULT_SETTINGS[modelId] || {};
1028
- // Merge settings in order of precedence
1625
+ // Prefer the caller's resolved ModelInfo (covers detected GGUF/unknown models);
1626
+ // fall back to the static registry.
1627
+ const modelInfo = resolvedModelInfo ?? getModelById(modelId, providerId);
1628
+ // Merge settings in order of precedence (model-declared defaults sit above the
1629
+ // static per-model-ID table, below request settings)
1029
1630
  const mergedSettings = {
1030
1631
  ...baseDefaults,
1031
1632
  ...providerDefaults,
1032
1633
  ...modelDefaults,
1634
+ ...(modelInfo?.defaultSettings || {}),
1033
1635
  };
1034
- // Override maxTokens from ModelInfo if available
1035
- const modelInfo = getModelById(modelId, providerId);
1036
- if (modelInfo && modelInfo.maxTokens !== undefined) {
1636
+ // Override maxTokens from ModelInfo if available (unless the model's own
1637
+ // defaultSettings already chose one)
1638
+ if (modelInfo &&
1639
+ modelInfo.maxTokens !== undefined &&
1640
+ modelInfo.defaultSettings?.maxTokens === undefined) {
1037
1641
  mergedSettings.maxTokens = modelInfo.maxTokens;
1038
1642
  }
1039
1643
  // Handle reasoning settings based on model capabilities
@@ -1128,6 +1732,66 @@ function validateLLMSettings(settings) {
1128
1732
  errors.push("stopSequences must contain only non-empty strings");
1129
1733
  }
1130
1734
  }
1735
+ if (settings.topK !== undefined) {
1736
+ if (!Number.isInteger(settings.topK) || settings.topK < 0) {
1737
+ errors.push("topK must be a non-negative integer");
1738
+ }
1739
+ }
1740
+ if (settings.minP !== undefined) {
1741
+ if (typeof settings.minP !== "number" ||
1742
+ settings.minP < 0 ||
1743
+ settings.minP > 1) {
1744
+ errors.push("minP must be a number between 0 and 1");
1745
+ }
1746
+ }
1747
+ if (settings.repeatPenalty !== undefined) {
1748
+ if (typeof settings.repeatPenalty !== "number" ||
1749
+ settings.repeatPenalty <= 0) {
1750
+ errors.push("repeatPenalty must be a positive number");
1751
+ }
1752
+ }
1753
+ if (settings.seed !== undefined) {
1754
+ if (!Number.isInteger(settings.seed)) {
1755
+ errors.push("seed must be an integer");
1756
+ }
1757
+ }
1758
+ if (settings.logprobs !== undefined && typeof settings.logprobs !== "boolean") {
1759
+ errors.push("logprobs must be a boolean");
1760
+ }
1761
+ if (settings.topLogprobs !== undefined) {
1762
+ if (!Number.isInteger(settings.topLogprobs) ||
1763
+ settings.topLogprobs < 0 ||
1764
+ settings.topLogprobs > 20) {
1765
+ errors.push("topLogprobs must be an integer between 0 and 20");
1766
+ }
1767
+ }
1768
+ if (settings.llamacpp !== undefined) {
1769
+ if (typeof settings.llamacpp !== "object" || settings.llamacpp === null) {
1770
+ errors.push("llamacpp must be an object");
1771
+ }
1772
+ else {
1773
+ if (settings.llamacpp.grammar !== undefined &&
1774
+ typeof settings.llamacpp.grammar !== "string") {
1775
+ errors.push("llamacpp.grammar must be a string (GBNF grammar)");
1776
+ }
1777
+ if (settings.llamacpp.chatTemplateKwargs !== undefined) {
1778
+ const kwargs = settings.llamacpp.chatTemplateKwargs;
1779
+ if (typeof kwargs !== "object" || kwargs === null || Array.isArray(kwargs)) {
1780
+ errors.push("llamacpp.chatTemplateKwargs must be an object");
1781
+ }
1782
+ else if (Object.values(kwargs).some((v) => !["string", "number", "boolean"].includes(typeof v))) {
1783
+ errors.push("llamacpp.chatTemplateKwargs values must be strings, numbers, or booleans");
1784
+ }
1785
+ }
1786
+ // llama-server rejects requests carrying both a raw grammar and a JSON schema:
1787
+ // "Either 'json_schema' or 'grammar' can be specified, but not both"
1788
+ if (settings.llamacpp.grammar &&
1789
+ settings.structuredOutput?.schema &&
1790
+ settings.structuredOutput.enabled !== false) {
1791
+ errors.push("llamacpp.grammar and structuredOutput are mutually exclusive (llama-server rejects both together)");
1792
+ }
1793
+ }
1794
+ }
1131
1795
  if (settings.user !== undefined && typeof settings.user !== "string") {
1132
1796
  errors.push("user must be a string");
1133
1797
  }