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
@@ -38,4 +38,10 @@ export declare class ModelResolver {
38
38
  * @returns Resolved model info and settings or error response
39
39
  */
40
40
  resolve(options: ModelSelectionOptions): Promise<ModelResolution>;
41
+ /**
42
+ * Asks the llama.cpp adapter which GGUF model the server has loaded and returns
43
+ * its detected capabilities (cached inside the adapter). Returns undefined when
44
+ * the server is unreachable or the model is not recognized.
45
+ */
46
+ private detectLlamaCppCapabilities;
41
47
  }
@@ -36,7 +36,7 @@ class ModelResolver {
36
36
  }
37
37
  };
38
38
  }
39
- const modelInfo = (0, config_1.getModelById)(preset.modelId, preset.providerId);
39
+ let modelInfo = (0, config_1.getModelById)(preset.modelId, preset.providerId);
40
40
  if (!modelInfo) {
41
41
  return {
42
42
  error: {
@@ -51,6 +51,14 @@ class ModelResolver {
51
51
  }
52
52
  };
53
53
  }
54
+ // Overlay detected GGUF capabilities for llama.cpp presets (the server decides
55
+ // which model is actually loaded, regardless of the preset's modelId)
56
+ if (preset.providerId === 'llamacpp') {
57
+ const detected = await this.detectLlamaCppCapabilities();
58
+ if (detected) {
59
+ modelInfo = { ...modelInfo, ...detected };
60
+ }
61
+ }
54
62
  // Merge preset settings with user settings
55
63
  const settings = {
56
64
  ...preset.settings,
@@ -94,24 +102,36 @@ class ModelResolver {
94
102
  };
95
103
  }
96
104
  let modelInfo = (0, config_1.getModelById)(options.modelId, options.providerId);
97
- if (!modelInfo) {
105
+ // For llamacpp, try to detect the loaded GGUF model's capabilities from the
106
+ // adapter. This runs even when the registry lookup succeeded (the documented
107
+ // `modelId: 'llamacpp'` usage matches the generic registry entry): the server
108
+ // decides which model is actually loaded, so detected capabilities and vendor
109
+ // default settings must overlay whatever the registry says.
110
+ let detectedCapabilities;
111
+ if (options.providerId === 'llamacpp') {
112
+ detectedCapabilities = await this.detectLlamaCppCapabilities();
113
+ }
114
+ if (modelInfo) {
115
+ if (detectedCapabilities) {
116
+ // Overlay detected capabilities onto the registry entry (detection wins:
117
+ // it reflects the model actually loaded on the server)
118
+ modelInfo = { ...modelInfo, ...detectedCapabilities };
119
+ }
120
+ }
121
+ else {
98
122
  // Check if provider allows unknown models
99
123
  const provider = (0, config_1.getProviderById)(options.providerId);
100
- // For llamacpp, try to detect capabilities from the adapter's cache
101
- let detectedCapabilities;
102
- if (options.providerId === 'llamacpp') {
103
- try {
104
- const adapter = this.adapterRegistry.getAdapter('llamacpp');
105
- // Check if adapter has the getModelCapabilities method
106
- if (adapter && typeof adapter.getModelCapabilities === 'function') {
107
- const capabilities = await adapter.getModelCapabilities();
108
- detectedCapabilities = capabilities || undefined;
109
- }
110
- }
111
- catch (error) {
112
- this.logger.warn('Failed to detect GGUF model capabilities:', error);
113
- // Continue with fallback
114
- }
124
+ // Unknown OpenRouter models: assume reasoning-capable (optimistic). OpenRouter
125
+ // ignores the reasoning param for models that don't support it, and rejecting
126
+ // here would block reasoning on all unregistered OpenRouter models.
127
+ if (options.providerId === 'openrouter' && !detectedCapabilities) {
128
+ detectedCapabilities = {
129
+ reasoning: {
130
+ supported: true,
131
+ enabledByDefault: false,
132
+ canDisable: true,
133
+ },
134
+ };
115
135
  }
116
136
  if (provider?.allowUnknownModels) {
117
137
  // Flexible provider (e.g., llamacpp) - silent fallback with detected capabilities
@@ -131,5 +151,25 @@ class ModelResolver {
131
151
  settings: options.settings
132
152
  };
133
153
  }
154
+ /**
155
+ * Asks the llama.cpp adapter which GGUF model the server has loaded and returns
156
+ * its detected capabilities (cached inside the adapter). Returns undefined when
157
+ * the server is unreachable or the model is not recognized.
158
+ */
159
+ async detectLlamaCppCapabilities() {
160
+ try {
161
+ const adapter = this.adapterRegistry.getAdapter('llamacpp');
162
+ // Check if adapter has the getModelCapabilities method
163
+ if (adapter && typeof adapter.getModelCapabilities === 'function') {
164
+ const capabilities = await adapter.getModelCapabilities();
165
+ return capabilities || undefined;
166
+ }
167
+ }
168
+ catch (error) {
169
+ this.logger.warn('Failed to detect GGUF model capabilities:', error);
170
+ // Continue with fallback
171
+ }
172
+ return undefined;
173
+ }
134
174
  }
135
175
  exports.ModelResolver = ModelResolver;
@@ -1,8 +1,16 @@
1
1
  import type { LLMChatRequest, LLMChatRequestWithPreset, LLMFailureResponse, LLMSettings, ModelInfo, StructuredOutputSettings } from "../types";
2
+ import type { Logger } from "../../logging/types";
2
3
  /**
3
4
  * Validates LLM requests including structure, messages, and settings
4
5
  */
5
6
  export declare class RequestValidator {
7
+ private logger;
8
+ /**
9
+ * Creates a new RequestValidator
10
+ *
11
+ * @param logger Optional logger instance
12
+ */
13
+ constructor(logger?: Logger);
6
14
  /**
7
15
  * Validates basic LLM request structure
8
16
  *
@@ -2,12 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestValidator = void 0;
4
4
  const defaultLogger_1 = require("../../logging/defaultLogger");
5
- const logger = (0, defaultLogger_1.createDefaultLogger)();
6
5
  const config_1 = require("../config");
7
6
  /**
8
7
  * Validates LLM requests including structure, messages, and settings
9
8
  */
10
9
  class RequestValidator {
10
+ /**
11
+ * Creates a new RequestValidator
12
+ *
13
+ * @param logger Optional logger instance
14
+ */
15
+ constructor(logger) {
16
+ this.logger = logger ?? (0, defaultLogger_1.createDefaultLogger)();
17
+ }
11
18
  /**
12
19
  * Validates basic LLM request structure
13
20
  *
@@ -156,7 +163,7 @@ class RequestValidator {
156
163
  }
157
164
  // Warn (but don't error) if strict mode requested but not supported
158
165
  if (structuredOutput.strict !== false && modelInfo.structuredOutput.strictMode === false) {
159
- logger.warn(`Model ${request.modelId} does not support strict mode for structured output. ` +
166
+ this.logger.warn(`Model ${request.modelId} does not support strict mode for structured output. ` +
160
167
  `Schema validation will be client-side only.`);
161
168
  }
162
169
  }
@@ -14,7 +14,7 @@ export declare class SettingsManager {
14
14
  * @param requestSettings - Settings from the request
15
15
  * @returns Complete settings object with all required fields
16
16
  */
17
- mergeSettingsForModel(modelId: string, providerId: ApiProviderId, requestSettings?: Partial<LLMSettings>): Required<LLMSettings>;
17
+ mergeSettingsForModel(modelId: string, providerId: ApiProviderId, requestSettings?: Partial<LLMSettings>, modelInfo?: ModelInfo): Required<LLMSettings>;
18
18
  /**
19
19
  * Filters out unsupported parameters based on model and provider configuration
20
20
  *
@@ -18,9 +18,20 @@ class SettingsManager {
18
18
  * @param requestSettings - Settings from the request
19
19
  * @returns Complete settings object with all required fields
20
20
  */
21
- mergeSettingsForModel(modelId, providerId, requestSettings) {
22
- // Get model-specific defaults
23
- const modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId);
21
+ mergeSettingsForModel(modelId, providerId, requestSettings, modelInfo) {
22
+ // Get model-specific defaults (threading the resolved ModelInfo lets detected
23
+ // GGUF/unknown models contribute capabilities and vendor default settings)
24
+ let modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId, modelInfo);
25
+ // Apply the reasoning-mode default overlay when reasoning is effectively on for
26
+ // this request (either explicitly requested or enabled by model default).
27
+ // Per-key precedence: request > reasoningDefaultSettings > defaultSettings.
28
+ const reasoningOn = requestSettings?.reasoning?.enabled ?? modelDefaults.reasoning?.enabled;
29
+ if (reasoningOn === true && modelInfo?.reasoningDefaultSettings) {
30
+ modelDefaults = {
31
+ ...modelDefaults,
32
+ ...modelInfo.reasoningDefaultSettings,
33
+ };
34
+ }
24
35
  // Merge with user-provided settings (user settings take precedence)
25
36
  const mergedSettings = {
26
37
  temperature: requestSettings?.temperature ?? modelDefaults.temperature,
@@ -29,6 +40,10 @@ class SettingsManager {
29
40
  stopSequences: requestSettings?.stopSequences ?? modelDefaults.stopSequences,
30
41
  frequencyPenalty: requestSettings?.frequencyPenalty ?? modelDefaults.frequencyPenalty,
31
42
  presencePenalty: requestSettings?.presencePenalty ?? modelDefaults.presencePenalty,
43
+ topK: requestSettings?.topK ?? modelDefaults.topK,
44
+ minP: requestSettings?.minP ?? modelDefaults.minP,
45
+ repeatPenalty: requestSettings?.repeatPenalty ?? modelDefaults.repeatPenalty,
46
+ seed: requestSettings?.seed ?? modelDefaults.seed,
32
47
  user: requestSettings?.user ?? modelDefaults.user,
33
48
  supportsSystemMessage: requestSettings?.supportsSystemMessage ??
34
49
  modelDefaults.supportsSystemMessage,
@@ -48,6 +63,14 @@ class SettingsManager {
48
63
  },
49
64
  openRouterProvider: requestSettings?.openRouterProvider ?? modelDefaults.openRouterProvider,
50
65
  structuredOutput: requestSettings?.structuredOutput ?? modelDefaults.structuredOutput,
66
+ logprobs: requestSettings?.logprobs ?? modelDefaults.logprobs,
67
+ topLogprobs: requestSettings?.topLogprobs ?? modelDefaults.topLogprobs,
68
+ llamacpp: requestSettings?.llamacpp || modelDefaults.llamacpp
69
+ ? {
70
+ ...modelDefaults.llamacpp,
71
+ ...requestSettings?.llamacpp,
72
+ }
73
+ : undefined,
51
74
  };
52
75
  // Log the final settings for debugging
53
76
  this.logger.debug(`Merged settings for ${providerId}/${modelId}:`, {
@@ -121,12 +144,19 @@ class SettingsManager {
121
144
  'stopSequences',
122
145
  'frequencyPenalty',
123
146
  'presencePenalty',
147
+ 'topK',
148
+ 'minP',
149
+ 'repeatPenalty',
150
+ 'seed',
124
151
  'user',
125
152
  'supportsSystemMessage',
126
153
  'geminiSafetySettings',
127
154
  'reasoning',
128
155
  'thinkingTagFallback',
129
- 'structuredOutput'
156
+ 'structuredOutput',
157
+ 'logprobs',
158
+ 'topLogprobs',
159
+ 'llamacpp'
130
160
  ];
131
161
  // Check each setting field
132
162
  for (const [key, value] of Object.entries(settings)) {
@@ -166,6 +196,64 @@ class SettingsManager {
166
196
  continue;
167
197
  }
168
198
  }
199
+ if (key === 'topK') {
200
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
201
+ this.logger.warn(`Invalid topK value in template: ${value}. Must be a non-negative integer.`);
202
+ continue;
203
+ }
204
+ }
205
+ if (key === 'minP') {
206
+ if (typeof value !== 'number' || value < 0 || value > 1) {
207
+ this.logger.warn(`Invalid minP value in template: ${value}. Must be a number between 0 and 1.`);
208
+ continue;
209
+ }
210
+ }
211
+ if (key === 'repeatPenalty') {
212
+ if (typeof value !== 'number' || value <= 0) {
213
+ this.logger.warn(`Invalid repeatPenalty value in template: ${value}. Must be a positive number.`);
214
+ continue;
215
+ }
216
+ }
217
+ if (key === 'seed') {
218
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
219
+ this.logger.warn(`Invalid seed value in template: ${value}. Must be an integer.`);
220
+ continue;
221
+ }
222
+ }
223
+ if (key === 'logprobs' && typeof value !== 'boolean') {
224
+ this.logger.warn(`Invalid logprobs value in template. Must be a boolean.`);
225
+ continue;
226
+ }
227
+ if (key === 'topLogprobs') {
228
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > 20) {
229
+ this.logger.warn(`Invalid topLogprobs value in template: ${value}. Must be an integer between 0 and 20.`);
230
+ continue;
231
+ }
232
+ }
233
+ if (key === 'llamacpp' && typeof value === 'object' && value !== null) {
234
+ const llamacppValidated = {};
235
+ const v = value;
236
+ if ('grammar' in v && typeof v.grammar !== 'string') {
237
+ this.logger.warn(`Invalid llamacpp.grammar value in template. Must be a string.`);
238
+ }
239
+ else if ('grammar' in v) {
240
+ llamacppValidated.grammar = v.grammar;
241
+ }
242
+ if ('chatTemplateKwargs' in v) {
243
+ if (typeof v.chatTemplateKwargs !== 'object' ||
244
+ v.chatTemplateKwargs === null ||
245
+ Array.isArray(v.chatTemplateKwargs)) {
246
+ this.logger.warn(`Invalid llamacpp.chatTemplateKwargs value in template. Must be an object.`);
247
+ }
248
+ else {
249
+ llamacppValidated.chatTemplateKwargs = v.chatTemplateKwargs;
250
+ }
251
+ }
252
+ if (Object.keys(llamacppValidated).length > 0) {
253
+ validated.llamacpp = llamacppValidated;
254
+ }
255
+ continue;
256
+ }
169
257
  if (key === 'user' && typeof value !== 'string') {
170
258
  this.logger.warn(`Invalid user value in template. Must be a string.`);
171
259
  continue;
@@ -284,6 +284,32 @@ export interface LLMSettings {
284
284
  frequencyPenalty?: number;
285
285
  /** Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far */
286
286
  presencePenalty?: number;
287
+ /**
288
+ * Limits sampling to the K most likely tokens (integer >= 0; 0 disables top-k filtering).
289
+ * Supported by: Anthropic (top_k), Gemini (topK), llama.cpp (top_k), OpenRouter (top_k).
290
+ * Not supported by OpenAI or Mistral (stripped automatically).
291
+ */
292
+ topK?: number;
293
+ /**
294
+ * Minimum probability threshold relative to the most likely token (0.0 to 1.0; 0 disables).
295
+ * Note: llama.cpp's server default is 0.05, which matches no vendor recommendation —
296
+ * detected GGUF models get an explicit 0 by default.
297
+ * Supported by: llama.cpp (min_p), OpenRouter (min_p). Stripped for other providers.
298
+ */
299
+ minP?: number;
300
+ /**
301
+ * Multiplicative repetition penalty over prompt + output tokens (1.0 = disabled).
302
+ * Distinct from presencePenalty (additive, output-only). For Qwen models prefer
303
+ * presencePenalty and keep repeatPenalty at 1.0 (vendor guidance).
304
+ * Supported by: llama.cpp (repeat_penalty), OpenRouter (repetition_penalty). Stripped elsewhere.
305
+ */
306
+ repeatPenalty?: number;
307
+ /**
308
+ * Seed for (best-effort) deterministic sampling. Integer; llama.cpp treats -1 as random.
309
+ * Supported by: OpenAI (beta; ignored by reasoning models), Gemini, Mistral (randomSeed),
310
+ * llama.cpp, OpenRouter. Not supported by Anthropic (stripped automatically).
311
+ */
312
+ seed?: number;
287
313
  /** A unique identifier representing your end-user, which can help monitor and detect abuse */
288
314
  user?: string;
289
315
  /** Whether the LLM supports system message (almost all LLMs do nowadays) */
@@ -328,6 +354,57 @@ export interface LLMSettings {
328
354
  * @see StructuredOutputSettings
329
355
  */
330
356
  structuredOutput?: StructuredOutputSettings;
357
+ /**
358
+ * Request per-token log probabilities on the response.
359
+ * Supported by: llama.cpp, OpenAI, OpenRouter (pass-through; model-dependent).
360
+ * Stripped for Anthropic/Gemini/Mistral. Results land on `choice.logprobs`.
361
+ */
362
+ logprobs?: boolean;
363
+ /**
364
+ * Number of most-likely alternatives to return per token (0-20).
365
+ * Requires `logprobs: true`.
366
+ */
367
+ topLogprobs?: number;
368
+ /**
369
+ * llama.cpp-specific settings.
370
+ * Only used when providerId is 'llamacpp'; ignored by other adapters.
371
+ * @see LlamaCppSettings
372
+ */
373
+ llamacpp?: LlamaCppSettings;
374
+ }
375
+ /**
376
+ * llama.cpp-specific request settings
377
+ */
378
+ export interface LlamaCppSettings {
379
+ /**
380
+ * Raw GBNF grammar string for constrained decoding.
381
+ * Mutually exclusive with `structuredOutput` (llama-server rejects both:
382
+ * "Either 'json_schema' or 'grammar' can be specified, but not both").
383
+ * Caveat: current llama.cpp builds may not apply the grammar while thinking
384
+ * is active — prefer grammar with reasoning disabled.
385
+ */
386
+ grammar?: string;
387
+ /**
388
+ * Extra chat-template kwargs forwarded verbatim to llama-server (requires --jinja).
389
+ * Merged over any library-derived kwargs (e.g. the reasoning toggle's
390
+ * enable_thinking), so explicit values here always win.
391
+ */
392
+ chatTemplateKwargs?: Record<string, string | number | boolean>;
393
+ }
394
+ /**
395
+ * Per-token log probability entry (OpenAI-compatible shape, also produced by
396
+ * llama.cpp's chat completions endpoint).
397
+ */
398
+ export interface TokenLogprob {
399
+ /** The generated token text */
400
+ token: string;
401
+ /** Log probability of the token */
402
+ logprob: number;
403
+ /** Most-likely alternative tokens at this position (when topLogprobs requested) */
404
+ topLogprobs?: Array<{
405
+ token: string;
406
+ logprob: number;
407
+ }>;
331
408
  }
332
409
  /**
333
410
  * Request structure for chat completion
@@ -361,6 +438,8 @@ export interface LLMChoice {
361
438
  reasoning?: string;
362
439
  /** Provider-specific reasoning details that need to be preserved */
363
440
  reasoning_details?: any;
441
+ /** Per-token log probabilities (when settings.logprobs was requested and supported) */
442
+ logprobs?: TokenLogprob[];
364
443
  /**
365
444
  * Parsed JSON content when structuredOutput is enabled and autoParse is true.
366
445
  * Contains the parsed object/array from the JSON response.
@@ -400,6 +479,10 @@ export interface LLMError {
400
479
  code?: string | number;
401
480
  type?: string;
402
481
  param?: string;
482
+ /** HTTP status code from the provider, when available */
483
+ status?: number;
484
+ /** Provider-suggested wait before retrying in ms (from a Retry-After header) */
485
+ retryAfterMs?: number;
403
486
  providerError?: any;
404
487
  }
405
488
  /**
@@ -482,6 +565,50 @@ export interface ModelInfo {
482
565
  unsupportedParameters?: (keyof LLMSettings)[];
483
566
  /** Structured output capabilities */
484
567
  structuredOutput?: ModelStructuredOutputCapabilities;
568
+ /**
569
+ * Model-specific default settings (e.g. vendor-recommended sampling parameters).
570
+ * Merged below request settings: DEFAULT < provider < MODEL_DEFAULT_SETTINGS <
571
+ * defaultSettings < request. Used heavily by detected GGUF models, whose vendors
572
+ * publish sampling profiles that differ from llama.cpp's server defaults.
573
+ */
574
+ defaultSettings?: Partial<LLMSettings>;
575
+ /**
576
+ * Additional default-settings overlay applied only when reasoning/thinking is
577
+ * active for the request (e.g. Qwen recommends a different sampling profile in
578
+ * thinking mode). Per-key precedence: request > reasoningDefaultSettings > defaultSettings.
579
+ */
580
+ reasoningDefaultSettings?: Partial<LLMSettings>;
581
+ /**
582
+ * Local-model (llama.cpp) reasoning-toggle and output-cleanup metadata.
583
+ * Battle-tested constants sourced from real chat-template behavior.
584
+ */
585
+ localReasoning?: LocalReasoningMetadata;
586
+ }
587
+ /**
588
+ * Metadata describing how a local (GGUF/llama.cpp) model's chat template handles
589
+ * thinking mode, and how to clean its output.
590
+ */
591
+ export interface LocalReasoningMetadata {
592
+ /**
593
+ * Name of the chat-template kwarg that toggles thinking (e.g. "enable_thinking").
594
+ * When set, the llama.cpp adapter sends `chat_template_kwargs: { [toggleKwarg]: <bool> }`
595
+ * derived from `settings.reasoning.enabled` (explicitly false when reasoning is not
596
+ * requested). Requires the server to run with `--jinja`. Omit for models whose
597
+ * thinking cannot be toggled (always-on reasoning models).
598
+ */
599
+ toggleKwarg?: string;
600
+ /**
601
+ * Exact prefix some chat templates inject into response content when thinking is
602
+ * disabled (e.g. Qwen's "<think>\n\n</think>\n\n"). Stripped verbatim (exact
603
+ * startsWith match) from message content before the response is returned.
604
+ */
605
+ nothinkPrefix?: string;
606
+ /**
607
+ * Open/close marker pair used to extract a reasoning trace from message content
608
+ * when the server does not populate `reasoning_content` (model/template dependent).
609
+ * Only fully-closed pairs are extracted.
610
+ */
611
+ markers?: [string, string];
485
612
  }
486
613
  /**
487
614
  * IPC channel names for LLM operations
@@ -8,5 +8,5 @@
8
8
  */
9
9
  export { renderTemplate } from './template';
10
10
  export { countTokens, getSmartPreview, extractRandomVariables } from './content';
11
- export { parseStructuredContent, extractInitialTaggedContent, parseRoleTags, parseTemplateWithMetadata } from './parser';
11
+ export { parseStructuredContent, extractInitialTaggedContent, extractMarkerDelimitedContent, parseRoleTags, parseTemplateWithMetadata } from './parser';
12
12
  export type { TemplateMetadata } from './parser';
@@ -8,7 +8,7 @@
8
8
  * - Response parsing for extracting structured data from LLM outputs
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.parseTemplateWithMetadata = exports.parseRoleTags = exports.extractInitialTaggedContent = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = void 0;
11
+ exports.parseTemplateWithMetadata = exports.parseRoleTags = exports.extractMarkerDelimitedContent = exports.extractInitialTaggedContent = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = void 0;
12
12
  // Template rendering
13
13
  var template_1 = require("./template");
14
14
  Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
@@ -21,5 +21,6 @@ Object.defineProperty(exports, "extractRandomVariables", { enumerable: true, get
21
21
  var parser_1 = require("./parser");
22
22
  Object.defineProperty(exports, "parseStructuredContent", { enumerable: true, get: function () { return parser_1.parseStructuredContent; } });
23
23
  Object.defineProperty(exports, "extractInitialTaggedContent", { enumerable: true, get: function () { return parser_1.extractInitialTaggedContent; } });
24
+ Object.defineProperty(exports, "extractMarkerDelimitedContent", { enumerable: true, get: function () { return parser_1.extractMarkerDelimitedContent; } });
24
25
  Object.defineProperty(exports, "parseRoleTags", { enumerable: true, get: function () { return parser_1.parseRoleTags; } });
25
26
  Object.defineProperty(exports, "parseTemplateWithMetadata", { enumerable: true, get: function () { return parser_1.parseTemplateWithMetadata; } });
@@ -59,6 +59,29 @@ export declare function extractInitialTaggedContent(content: string, tagName: st
59
59
  extracted: string | null;
60
60
  remaining: string;
61
61
  };
62
+ /**
63
+ * Extracts content delimited by an arbitrary open/close marker pair from anywhere
64
+ * in a string.
65
+ *
66
+ * Unlike {@link extractInitialTaggedContent}, the markers are free-form strings
67
+ * (not derived from an XML tag name) and may appear anywhere in the content —
68
+ * useful for local-model thinking traces whose markers aren't XML-shaped
69
+ * (e.g. Gemma 4's `<|channel>thought` … `<channel|>`).
70
+ *
71
+ * Only a fully-closed pair is extracted: if either marker is missing, the
72
+ * original content is returned untouched and `extracted` is null. Extracted
73
+ * content that is empty after trimming also yields null.
74
+ *
75
+ * @param content The string to parse.
76
+ * @param openMarker The opening marker (exact match).
77
+ * @param closeMarker The closing marker (exact match, searched after the opener).
78
+ * @returns The content with the delimited block removed (leading whitespace
79
+ * trimmed), and the extracted inner text (or null).
80
+ */
81
+ export declare function extractMarkerDelimitedContent(content: string, openMarker: string, closeMarker: string): {
82
+ content: string;
83
+ extracted: string | null;
84
+ };
62
85
  /**
63
86
  * Parses role tags from a template string without rendering variables.
64
87
  *
@@ -8,6 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.parseStructuredContent = parseStructuredContent;
10
10
  exports.extractInitialTaggedContent = extractInitialTaggedContent;
11
+ exports.extractMarkerDelimitedContent = extractMarkerDelimitedContent;
11
12
  exports.parseRoleTags = parseRoleTags;
12
13
  exports.parseTemplateWithMetadata = parseTemplateWithMetadata;
13
14
  const defaultLogger_1 = require("../logging/defaultLogger");
@@ -84,6 +85,38 @@ function extractInitialTaggedContent(content, tagName) {
84
85
  // If the tag pattern is not found at the start, return the original content.
85
86
  return { extracted: null, remaining: content };
86
87
  }
88
+ /**
89
+ * Extracts content delimited by an arbitrary open/close marker pair from anywhere
90
+ * in a string.
91
+ *
92
+ * Unlike {@link extractInitialTaggedContent}, the markers are free-form strings
93
+ * (not derived from an XML tag name) and may appear anywhere in the content —
94
+ * useful for local-model thinking traces whose markers aren't XML-shaped
95
+ * (e.g. Gemma 4's `<|channel>thought` … `<channel|>`).
96
+ *
97
+ * Only a fully-closed pair is extracted: if either marker is missing, the
98
+ * original content is returned untouched and `extracted` is null. Extracted
99
+ * content that is empty after trimming also yields null.
100
+ *
101
+ * @param content The string to parse.
102
+ * @param openMarker The opening marker (exact match).
103
+ * @param closeMarker The closing marker (exact match, searched after the opener).
104
+ * @returns The content with the delimited block removed (leading whitespace
105
+ * trimmed), and the extracted inner text (or null).
106
+ */
107
+ function extractMarkerDelimitedContent(content, openMarker, closeMarker) {
108
+ const start = content.indexOf(openMarker);
109
+ if (start === -1) {
110
+ return { content, extracted: null };
111
+ }
112
+ const end = content.indexOf(closeMarker, start + openMarker.length);
113
+ if (end === -1) {
114
+ return { content, extracted: null };
115
+ }
116
+ const inner = content.substring(start + openMarker.length, end).trim();
117
+ const cleaned = (content.substring(0, start) + content.substring(end + closeMarker.length)).trimStart();
118
+ return { content: cleaned, extracted: inner || null };
119
+ }
87
120
  /**
88
121
  * Parses role tags from a template string without rendering variables.
89
122
  *
@@ -7,7 +7,22 @@ export interface MappedErrorDetails {
7
7
  errorMessage: string;
8
8
  errorType: string;
9
9
  status?: number;
10
+ /** Provider-suggested wait before retrying (parsed from a Retry-After header) */
11
+ retryAfterMs?: number;
10
12
  }
13
+ /**
14
+ * Parses a Retry-After header value (delta-seconds or HTTP-date) into milliseconds.
15
+ *
16
+ * @param value - The raw header value
17
+ * @returns Milliseconds to wait, or undefined when unparseable/negative
18
+ */
19
+ export declare function parseRetryAfterMs(value: string | null | undefined): number | undefined;
20
+ /**
21
+ * Extracts a Retry-After value in ms from an SDK error object, tolerating the
22
+ * different places SDKs keep response headers (openai/anthropic `error.headers`
23
+ * as a Headers instance or plain object; Speakeasy-style `error.rawResponse`).
24
+ */
25
+ export declare function extractRetryAfterMs(error: any): number | undefined;
11
26
  /**
12
27
  * Maps common error patterns to standardized error codes and types
13
28
  *