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
@@ -12,8 +12,9 @@ const errorUtils_1 = require("../../shared/adapters/errorUtils");
12
12
  const systemMessageUtils_1 = require("../../shared/adapters/systemMessageUtils");
13
13
  const LlamaCppServerClient_1 = require("./LlamaCppServerClient");
14
14
  const config_1 = require("../config");
15
+ const parser_1 = require("../../prompting/parser");
16
+ const logprobsUtils_1 = require("../../shared/adapters/logprobsUtils");
15
17
  const defaultLogger_1 = require("../../logging/defaultLogger");
16
- const logger = (0, defaultLogger_1.createDefaultLogger)();
17
18
  /**
18
19
  * Client adapter for llama.cpp server integration
19
20
  *
@@ -34,7 +35,7 @@ const logger = (0, defaultLogger_1.createDefaultLogger)();
34
35
  * ```typescript
35
36
  * // Create adapter for local server
36
37
  * const adapter = new LlamaCppClientAdapter({
37
- * baseURL: 'http://localhost:8080',
38
+ * baseURL: 'http://127.0.0.1:8080',
38
39
  * checkHealth: true
39
40
  * });
40
41
  *
@@ -58,9 +59,13 @@ class LlamaCppClientAdapter {
58
59
  constructor(config) {
59
60
  this.cachedModelCapabilities = null;
60
61
  this.detectionAttempted = false;
61
- this.baseURL = config?.baseURL || 'http://localhost:8080';
62
+ // 127.0.0.1 rather than localhost: on Windows, localhost resolves to IPv6 (::1)
63
+ // first, and when llama-server listens on IPv4 only, every fresh connection waits
64
+ // ~2s for the IPv6 attempt to time out (~9x per-request slowdown measured).
65
+ this.baseURL = config?.baseURL || 'http://127.0.0.1:8080';
62
66
  this.checkHealth = config?.checkHealth || false;
63
67
  this.serverClient = new LlamaCppServerClient_1.LlamaCppServerClient(this.baseURL);
68
+ this.logger = config?.logger ?? (0, defaultLogger_1.createDefaultLogger)();
64
69
  }
65
70
  /**
66
71
  * Gets model capabilities by detecting the loaded GGUF model
@@ -81,10 +86,10 @@ class LlamaCppClientAdapter {
81
86
  }
82
87
  // Attempt detection
83
88
  try {
84
- logger.debug(`Detecting model capabilities from llama.cpp server at ${this.baseURL}`);
89
+ this.logger.debug(`Detecting model capabilities from llama.cpp server at ${this.baseURL}`);
85
90
  const { data } = await this.serverClient.getModels();
86
91
  if (!data || data.length === 0) {
87
- logger.warn('No models loaded in llama.cpp server');
92
+ this.logger.warn('No models loaded in llama.cpp server');
88
93
  this.detectionAttempted = true;
89
94
  return null;
90
95
  }
@@ -94,15 +99,15 @@ class LlamaCppClientAdapter {
94
99
  this.cachedModelCapabilities = capabilities;
95
100
  this.detectionAttempted = true;
96
101
  if (capabilities) {
97
- logger.debug(`Cached model capabilities for: ${ggufFilename}`);
102
+ this.logger.debug(`Cached model capabilities for: ${ggufFilename}`);
98
103
  }
99
104
  else {
100
- logger.debug(`No known pattern matched for: ${ggufFilename}`);
105
+ this.logger.debug(`No known pattern matched for: ${ggufFilename}`);
101
106
  }
102
107
  return capabilities;
103
108
  }
104
109
  catch (error) {
105
- logger.warn('Failed to detect model capabilities:', error);
110
+ this.logger.warn('Failed to detect model capabilities:', error);
106
111
  this.detectionAttempted = true;
107
112
  return null;
108
113
  }
@@ -116,7 +121,7 @@ class LlamaCppClientAdapter {
116
121
  clearModelCache() {
117
122
  this.cachedModelCapabilities = null;
118
123
  this.detectionAttempted = false;
119
- logger.debug('Cleared model capabilities cache');
124
+ this.logger.debug('Cleared model capabilities cache');
120
125
  }
121
126
  /**
122
127
  * Sends a chat message to llama.cpp server
@@ -125,7 +130,7 @@ class LlamaCppClientAdapter {
125
130
  * @param apiKey - Not used for llama.cpp (local server), but kept for interface compatibility
126
131
  * @returns Promise resolving to success or failure response
127
132
  */
128
- async sendMessage(request, apiKey) {
133
+ async sendMessage(request, apiKey, options) {
129
134
  try {
130
135
  // Optional health check before making request
131
136
  if (this.checkHealth) {
@@ -145,7 +150,7 @@ class LlamaCppClientAdapter {
145
150
  }
146
151
  }
147
152
  catch (healthError) {
148
- logger.warn('Health check failed, proceeding with request anyway:', healthError);
153
+ this.logger.warn('Health check failed, proceeding with request anyway:', healthError);
149
154
  }
150
155
  }
151
156
  // Initialize OpenAI client with llama.cpp base URL
@@ -153,6 +158,7 @@ class LlamaCppClientAdapter {
153
158
  const openai = new openai_1.default({
154
159
  apiKey: apiKey || 'not-needed',
155
160
  baseURL: `${this.baseURL}/v1`,
161
+ maxRetries: 0, // retries are owned by the unified LLMService retry layer
156
162
  });
157
163
  // Format messages for OpenAI-compatible API
158
164
  const messages = this.formatMessages(request);
@@ -172,6 +178,20 @@ class LlamaCppClientAdapter {
172
178
  ...(request.settings.presencePenalty !== 0 && {
173
179
  presence_penalty: request.settings.presencePenalty,
174
180
  }),
181
+ ...(request.settings.seed !== undefined && {
182
+ seed: request.settings.seed,
183
+ }),
184
+ // llama.cpp-native sampling params (not in the OpenAI SDK types, but the
185
+ // SDK sends extra body fields as-is and llama-server reads them top-level)
186
+ ...(request.settings.topK !== undefined && {
187
+ top_k: request.settings.topK,
188
+ }),
189
+ ...(request.settings.minP !== undefined && {
190
+ min_p: request.settings.minP,
191
+ }),
192
+ ...(request.settings.repeatPenalty !== undefined && {
193
+ repeat_penalty: request.settings.repeatPenalty,
194
+ }),
175
195
  };
176
196
  // Handle structured output configuration for llama.cpp
177
197
  // llama.cpp uses response_format with type: 'json_object' and optional schema
@@ -182,27 +202,83 @@ class LlamaCppClientAdapter {
182
202
  schema: so.schema,
183
203
  };
184
204
  }
185
- logger.debug(`llama.cpp API parameters:`, {
205
+ // Reasoning toggle for detected hybrid GGUF models (Qwen 3.x, Gemma 4, ...).
206
+ // The chat template's thinking flag is sent explicitly — false unless reasoning
207
+ // was requested — so hybrid models don't silently burn thinking tokens.
208
+ // Requires llama-server running with --jinja; servers without it ignore the kwarg.
209
+ const detectedCaps = await this.getModelCapabilities();
210
+ const localReasoning = detectedCaps?.localReasoning;
211
+ const derivedKwargs = {};
212
+ if (localReasoning?.toggleKwarg) {
213
+ derivedKwargs[localReasoning.toggleKwarg] =
214
+ request.settings.reasoning?.enabled === true;
215
+ }
216
+ // User-supplied chat-template kwargs (escape hatch) win over derived ones
217
+ const chatTemplateKwargs = {
218
+ ...derivedKwargs,
219
+ ...(request.settings.llamacpp?.chatTemplateKwargs || {}),
220
+ };
221
+ if (Object.keys(chatTemplateKwargs).length > 0) {
222
+ // llama-server rejects assistant prefill together with enable_thinking=true
223
+ // (HTTP 400: "Assistant response prefill is incompatible with enable_thinking.")
224
+ // Fail fast with a clear message instead of surfacing the raw server error.
225
+ const thinkingKwarg = localReasoning?.toggleKwarg ?? 'enable_thinking';
226
+ const effectiveThinking = chatTemplateKwargs[thinkingKwarg] === true;
227
+ const lastMessage = messages[messages.length - 1];
228
+ if (effectiveThinking && lastMessage?.role === 'assistant') {
229
+ return {
230
+ provider: request.providerId,
231
+ model: request.modelId,
232
+ error: {
233
+ message: 'llama.cpp does not support assistant prefill (a trailing assistant message) ' +
234
+ 'while thinking is enabled. Remove the trailing assistant message or disable reasoning.',
235
+ code: types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR,
236
+ type: 'invalid_request_error',
237
+ },
238
+ object: 'error',
239
+ };
240
+ }
241
+ completionParams.chat_template_kwargs = chatTemplateKwargs;
242
+ }
243
+ // GBNF grammar for constrained decoding (validated as mutually exclusive
244
+ // with structuredOutput upstream)
245
+ if (request.settings.llamacpp?.grammar) {
246
+ completionParams.grammar = request.settings.llamacpp.grammar;
247
+ }
248
+ // Per-token log probabilities (OpenAI-compatible request/response shape)
249
+ if (request.settings.logprobs === true) {
250
+ completionParams.logprobs = true;
251
+ if (request.settings.topLogprobs !== undefined) {
252
+ completionParams.top_logprobs = request.settings.topLogprobs;
253
+ }
254
+ }
255
+ this.logger.debug(`llama.cpp API parameters:`, {
186
256
  baseURL: this.baseURL,
187
257
  model: completionParams.model,
188
258
  temperature: completionParams.temperature,
189
259
  max_tokens: completionParams.max_tokens,
190
260
  top_p: completionParams.top_p,
191
261
  });
192
- logger.info(`Making llama.cpp API call for model: ${request.modelId}`);
262
+ this.logger.info(`Making llama.cpp API call for model: ${request.modelId}`);
193
263
  // Make the API call
194
- const completion = await openai.chat.completions.create(completionParams);
264
+ const transportOptions = {
265
+ ...(options?.signal && { signal: options.signal }),
266
+ ...(options?.timeoutMs !== undefined && { timeout: options.timeoutMs }),
267
+ };
268
+ const completion = Object.keys(transportOptions).length > 0
269
+ ? await openai.chat.completions.create(completionParams, transportOptions)
270
+ : await openai.chat.completions.create(completionParams);
195
271
  // Type guard to ensure we have a non-streaming response
196
272
  if ('id' in completion && 'choices' in completion) {
197
- logger.info(`llama.cpp API call successful, response ID: ${completion.id}`);
198
- return this.createSuccessResponse(completion, request);
273
+ this.logger.info(`llama.cpp API call successful, response ID: ${completion.id}`);
274
+ return this.createSuccessResponse(completion, request, detectedCaps);
199
275
  }
200
276
  else {
201
277
  throw new Error('Unexpected streaming response from llama.cpp server');
202
278
  }
203
279
  }
204
280
  catch (error) {
205
- logger.error("llama.cpp API error:", error);
281
+ this.logger.error("llama.cpp API error:", error);
206
282
  // Clear cache on connection errors so we re-detect on next request
207
283
  const errorMessage = error?.message || String(error);
208
284
  if (errorMessage.includes("ECONNREFUSED") ||
@@ -296,7 +372,7 @@ class LlamaCppClientAdapter {
296
372
  const modifiedIndex = (0, systemMessageUtils_1.prependSystemToFirstUserMessage)(simpleMessages, combinedSystemContent, request.settings.systemMessageFallback);
297
373
  if (modifiedIndex !== -1) {
298
374
  messages[modifiedIndex].content = simpleMessages[modifiedIndex].content;
299
- logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
375
+ this.logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
300
376
  }
301
377
  }
302
378
  }
@@ -309,36 +385,62 @@ class LlamaCppClientAdapter {
309
385
  * @param request - Original request for context
310
386
  * @returns Standardized LLM response
311
387
  */
312
- createSuccessResponse(completion, request) {
388
+ createSuccessResponse(completion, request, detectedCaps) {
313
389
  const choice = completion.choices[0];
314
390
  if (!choice || !choice.message) {
315
391
  throw new Error("No valid choices in llama.cpp completion response");
316
392
  }
317
- // Extract reasoning content if available
318
- // llama.cpp returns reasoning in reasoning_content field when using --reasoning-format
319
- let reasoning;
320
- if (choice.message.reasoning_content) {
321
- reasoning = choice.message.reasoning_content;
322
- }
393
+ const local = detectedCaps?.localReasoning;
394
+ // Whether thinking is active for this request: explicitly requested, or the
395
+ // detected model reasons unconditionally (always-on models like GPT-OSS,
396
+ // Thinking-2507 checkpoints, DeepSeek R1).
397
+ const reasoningActive = request.settings.reasoning?.enabled === true ||
398
+ detectedCaps?.reasoning?.enabledByDefault === true ||
399
+ detectedCaps?.reasoning?.canDisable === false;
400
+ const excludeReasoning = request.settings.reasoning?.exclude === true;
323
401
  return {
324
402
  id: completion.id,
325
403
  provider: request.providerId,
326
404
  model: completion.model || request.modelId,
327
405
  created: completion.created,
328
406
  choices: completion.choices.map((c) => {
407
+ let content = c.message.content || "";
408
+ // Strip the template-injected "nothink" prefix (some chat templates leak an
409
+ // empty think block into content when thinking is disabled). Exact match.
410
+ if (local?.nothinkPrefix && content.startsWith(local.nothinkPrefix)) {
411
+ content = content.slice(local.nothinkPrefix.length);
412
+ }
329
413
  const mappedChoice = {
330
414
  message: {
331
415
  role: "assistant",
332
- content: c.message.content || "",
416
+ content,
333
417
  },
334
418
  finish_reason: c.finish_reason,
335
419
  index: c.index,
336
420
  };
337
- // Include reasoning if available and not excluded
421
+ // Two-tier reasoning extraction:
422
+ // 1. Prefer the server-separated reasoning_content field (populated when
423
+ // llama-server's --reasoning-format handling recognizes the template).
424
+ // 2. Fall back to marker extraction from content — reasoning_content
425
+ // population is model/template-dependent, not guaranteed.
338
426
  const messageReasoning = c.message.reasoning_content;
339
- if (messageReasoning && request.settings.reasoning && !request.settings.reasoning.exclude) {
427
+ if (messageReasoning && request.settings.reasoning && !excludeReasoning) {
340
428
  mappedChoice.reasoning = messageReasoning;
341
429
  }
430
+ else if (!messageReasoning && reasoningActive && local?.markers) {
431
+ const { content: cleaned, extracted } = (0, parser_1.extractMarkerDelimitedContent)(content, local.markers[0], local.markers[1]);
432
+ if (extracted) {
433
+ mappedChoice.message.content = cleaned;
434
+ if (!excludeReasoning) {
435
+ mappedChoice.reasoning = extracted;
436
+ }
437
+ }
438
+ }
439
+ // Per-token log probabilities (OpenAI-compatible shape)
440
+ const logprobs = (0, logprobsUtils_1.mapOpenAIChatLogprobs)(c.logprobs);
441
+ if (logprobs) {
442
+ mappedChoice.logprobs = logprobs;
443
+ }
342
444
  return mappedChoice;
343
445
  }),
344
446
  usage: completion.usage
@@ -389,6 +491,7 @@ class LlamaCppClientAdapter {
389
491
  code: mappedError.errorCode,
390
492
  type: mappedError.errorType,
391
493
  ...(mappedError.status && { status: mappedError.status }),
494
+ ...(mappedError.retryAfterMs !== undefined && { retryAfterMs: mappedError.retryAfterMs }),
392
495
  providerError: error,
393
496
  },
394
497
  object: "error",
@@ -1,11 +1,14 @@
1
1
  import type { LLMResponse, LLMFailureResponse } from "../types";
2
- import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
2
+ import type { ILLMClientAdapter, InternalLLMChatRequest, AdapterRequestOptions } from "./types";
3
+ import type { Logger } from "../../logging/types";
3
4
  /**
4
5
  * Configuration options for MistralClientAdapter
5
6
  */
6
7
  export interface MistralClientConfig {
7
8
  /** Base URL of the Mistral API (default: https://api.mistral.ai) */
8
9
  baseURL?: string;
10
+ /** Logger instance for adapter logging */
11
+ logger?: Logger;
9
12
  }
10
13
  /**
11
14
  * Client adapter for Mistral AI API integration
@@ -35,6 +38,7 @@ export interface MistralClientConfig {
35
38
  */
36
39
  export declare class MistralClientAdapter implements ILLMClientAdapter {
37
40
  private baseURL;
41
+ private logger;
38
42
  /**
39
43
  * Creates a new Mistral client adapter
40
44
  *
@@ -48,7 +52,7 @@ export declare class MistralClientAdapter implements ILLMClientAdapter {
48
52
  * @param apiKey - The Mistral API key
49
53
  * @returns Promise resolving to success or failure response
50
54
  */
51
- sendMessage(request: InternalLLMChatRequest, apiKey: string): Promise<LLMResponse | LLMFailureResponse>;
55
+ sendMessage(request: InternalLLMChatRequest, apiKey: string, options?: AdapterRequestOptions): Promise<LLMResponse | LLMFailureResponse>;
52
56
  /**
53
57
  * Validates Mistral API key format
54
58
  *
@@ -8,7 +8,6 @@ const types_1 = require("./types");
8
8
  const errorUtils_1 = require("../../shared/adapters/errorUtils");
9
9
  const systemMessageUtils_1 = require("../../shared/adapters/systemMessageUtils");
10
10
  const defaultLogger_1 = require("../../logging/defaultLogger");
11
- const logger = (0, defaultLogger_1.createDefaultLogger)();
12
11
  /**
13
12
  * Client adapter for Mistral AI API integration
14
13
  *
@@ -43,6 +42,7 @@ class MistralClientAdapter {
43
42
  */
44
43
  constructor(config) {
45
44
  this.baseURL = config?.baseURL || process.env.MISTRAL_API_BASE_URL || 'https://api.mistral.ai';
45
+ this.logger = config?.logger ?? (0, defaultLogger_1.createDefaultLogger)();
46
46
  }
47
47
  /**
48
48
  * Sends a chat message to Mistral API
@@ -51,23 +51,24 @@ class MistralClientAdapter {
51
51
  * @param apiKey - The Mistral API key
52
52
  * @returns Promise resolving to success or failure response
53
53
  */
54
- async sendMessage(request, apiKey) {
54
+ async sendMessage(request, apiKey, options) {
55
55
  try {
56
- // Initialize Mistral client
56
+ // Initialize Mistral client (Speakeasy SDK retry default is "none" — retries
57
+ // are owned by the unified LLMService retry layer)
57
58
  const mistral = new mistralai_1.Mistral({
58
59
  apiKey,
59
60
  serverURL: this.baseURL !== 'https://api.mistral.ai' ? this.baseURL : undefined,
60
61
  });
61
62
  // Format messages for Mistral API
62
63
  const messages = this.formatMessages(request);
63
- logger.debug(`Mistral API parameters:`, {
64
+ this.logger.debug(`Mistral API parameters:`, {
64
65
  baseURL: this.baseURL,
65
66
  model: request.modelId,
66
67
  temperature: request.settings.temperature,
67
68
  max_tokens: request.settings.maxTokens,
68
69
  top_p: request.settings.topP,
69
70
  });
70
- logger.info(`Making Mistral API call for model: ${request.modelId}`);
71
+ this.logger.info(`Making Mistral API call for model: ${request.modelId}`);
71
72
  // Build request options
72
73
  const requestOptions = {
73
74
  model: request.modelId,
@@ -78,19 +79,28 @@ class MistralClientAdapter {
78
79
  ...(request.settings.stopSequences.length > 0 && {
79
80
  stop: request.settings.stopSequences,
80
81
  }),
82
+ ...(request.settings.seed !== undefined && {
83
+ randomSeed: request.settings.seed,
84
+ }),
81
85
  // Note: Mistral does not support frequency_penalty or presence_penalty
82
86
  };
83
87
  // Handle structured output configuration for Mistral
84
88
  // Mistral only supports json_object mode, no schema validation
85
89
  if (request.settings.structuredOutput?.schema && request.settings.structuredOutput.enabled !== false) {
86
90
  requestOptions.responseFormat = { type: 'json_object' };
87
- logger.warn(`Mistral does not support JSON schema validation. ` +
91
+ this.logger.warn(`Mistral does not support JSON schema validation. ` +
88
92
  `Using json_object mode - schema validation will be client-side only.`);
89
93
  }
90
- // Make the API call
91
- const completion = await mistral.chat.complete(requestOptions);
94
+ // Make the API call (per-request transport options: timeout + abort signal)
95
+ const transportOptions = {
96
+ ...(options?.timeoutMs !== undefined && { timeoutMs: options.timeoutMs }),
97
+ ...(options?.signal && { fetchOptions: { signal: options.signal } }),
98
+ };
99
+ const completion = Object.keys(transportOptions).length > 0
100
+ ? await mistral.chat.complete(requestOptions, transportOptions)
101
+ : await mistral.chat.complete(requestOptions);
92
102
  if (completion && completion.choices && completion.choices.length > 0) {
93
- logger.info(`Mistral API call successful, response ID: ${completion.id}`);
103
+ this.logger.info(`Mistral API call successful, response ID: ${completion.id}`);
94
104
  return this.createSuccessResponse(completion, request);
95
105
  }
96
106
  else {
@@ -98,7 +108,7 @@ class MistralClientAdapter {
98
108
  }
99
109
  }
100
110
  catch (error) {
101
- logger.error("Mistral API error:", error);
111
+ this.logger.error("Mistral API error:", error);
102
112
  return this.createErrorResponse(error, request);
103
113
  }
104
114
  }
@@ -170,7 +180,7 @@ class MistralClientAdapter {
170
180
  // Model doesn't support system messages - prepend to first user message
171
181
  const modifiedIndex = (0, systemMessageUtils_1.prependSystemToFirstUserMessage)(messages, combinedSystemContent, request.settings.systemMessageFallback);
172
182
  if (modifiedIndex !== -1) {
173
- logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
183
+ this.logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
174
184
  }
175
185
  }
176
186
  }
@@ -239,6 +249,7 @@ class MistralClientAdapter {
239
249
  code: mappedError.errorCode,
240
250
  type: mappedError.errorType,
241
251
  ...(mappedError.status && { status: mappedError.status }),
252
+ ...(mappedError.retryAfterMs !== undefined && { retryAfterMs: mappedError.retryAfterMs }),
242
253
  providerError: error,
243
254
  },
244
255
  object: "error",
@@ -1,5 +1,5 @@
1
1
  import type { LLMResponse, LLMFailureResponse, ApiProviderId } from "../types";
2
- import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
2
+ import type { ILLMClientAdapter, InternalLLMChatRequest, AdapterRequestOptions } from "./types";
3
3
  /**
4
4
  * Mock client adapter for testing LLM functionality
5
5
  *
@@ -20,7 +20,7 @@ export declare class MockClientAdapter implements ILLMClientAdapter {
20
20
  * @param apiKey - The API key (ignored for mock)
21
21
  * @returns Promise resolving to mock response
22
22
  */
23
- sendMessage(request: InternalLLMChatRequest, apiKey: string): Promise<LLMResponse | LLMFailureResponse>;
23
+ sendMessage(request: InternalLLMChatRequest, apiKey: string, options?: AdapterRequestOptions): Promise<LLMResponse | LLMFailureResponse>;
24
24
  /**
25
25
  * Validates API key format (always returns true for mock)
26
26
  */
@@ -25,7 +25,20 @@ class MockClientAdapter {
25
25
  * @param apiKey - The API key (ignored for mock)
26
26
  * @returns Promise resolving to mock response
27
27
  */
28
- async sendMessage(request, apiKey) {
28
+ async sendMessage(request, apiKey, options) {
29
+ // Honor abort signals like a real adapter would
30
+ if (options?.signal?.aborted) {
31
+ return {
32
+ provider: request.providerId,
33
+ model: request.modelId,
34
+ error: {
35
+ message: "Request was aborted",
36
+ code: types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED,
37
+ type: "abort_error",
38
+ },
39
+ object: "error",
40
+ };
41
+ }
29
42
  // Simulate network delay
30
43
  await this.simulateDelay(100, 500);
31
44
  try {
@@ -213,15 +226,19 @@ class MockClientAdapter {
213
226
  * Generates a response that shows current settings
214
227
  */
215
228
  generateSettingsTestResponse(settings) {
216
- return `Current mock settings:
217
- - Temperature: ${settings.temperature}
218
- - Max Tokens: ${settings.maxTokens}
219
- - Top P: ${settings.topP}
229
+ return `Current mock settings:
230
+ - Temperature: ${settings.temperature}
231
+ - Max Tokens: ${settings.maxTokens}
232
+ - Top P: ${settings.topP}
220
233
  - Stop Sequences: ${settings.stopSequences.length > 0
221
234
  ? settings.stopSequences.join(", ")
222
- : "none"}
223
- - Frequency Penalty: ${settings.frequencyPenalty}
224
- - Presence Penalty: ${settings.presencePenalty}
235
+ : "none"}
236
+ - Frequency Penalty: ${settings.frequencyPenalty}
237
+ - Presence Penalty: ${settings.presencePenalty}
238
+ - Top K: ${settings.topK ?? "not set"}
239
+ - Min P: ${settings.minP ?? "not set"}
240
+ - Repeat Penalty: ${settings.repeatPenalty ?? "not set"}
241
+ - Seed: ${settings.seed ?? "not set"}
225
242
  - User: ${settings.user || "not set"}`;
226
243
  }
227
244
  /**
@@ -285,17 +302,17 @@ class MockClientAdapter {
285
302
  * Generates a longer mock response for testing
286
303
  */
287
304
  generateLongResponse() {
288
- return `This is a detailed mock response from the ${this.providerId} adapter.
289
-
290
- I can simulate various types of responses based on your input. Here are some features:
291
-
292
- 1. **Error Simulation**: Include phrases like "error_rate_limit" to test error handling
293
- 2. **Variable Length**: Request "long" responses to test token limits
294
- 3. **Code Generation**: Ask about "programming" to get mock code snippets
295
- 4. **Conversational**: Simple greetings work too
296
-
297
- The mock adapter is useful for testing the LLM integration without making real API calls. It simulates realistic response times, token usage, and various error conditions that you might encounter with real LLM providers.
298
-
305
+ return `This is a detailed mock response from the ${this.providerId} adapter.
306
+
307
+ I can simulate various types of responses based on your input. Here are some features:
308
+
309
+ 1. **Error Simulation**: Include phrases like "error_rate_limit" to test error handling
310
+ 2. **Variable Length**: Request "long" responses to test token limits
311
+ 3. **Code Generation**: Ask about "programming" to get mock code snippets
312
+ 4. **Conversational**: Simple greetings work too
313
+
314
+ The mock adapter is useful for testing the LLM integration without making real API calls. It simulates realistic response times, token usage, and various error conditions that you might encounter with real LLM providers.
315
+
299
316
  This response demonstrates how the adapter can generate longer content while still respecting the maxTokens parameter if specified in the request settings.`;
300
317
  }
301
318
  /**
@@ -1,5 +1,6 @@
1
1
  import type { LLMResponse, LLMFailureResponse } from "../types";
2
- import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
2
+ import type { ILLMClientAdapter, InternalLLMChatRequest, AdapterRequestOptions } from "./types";
3
+ import type { Logger } from "../../logging/types";
3
4
  /**
4
5
  * Client adapter for OpenAI API integration
5
6
  *
@@ -11,14 +12,17 @@ import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
11
12
  */
12
13
  export declare class OpenAIClientAdapter implements ILLMClientAdapter {
13
14
  private baseURL?;
15
+ private logger;
14
16
  /**
15
17
  * Creates a new OpenAI client adapter
16
18
  *
17
19
  * @param config Optional configuration for the adapter
18
20
  * @param config.baseURL Custom base URL for OpenAI-compatible APIs
21
+ * @param config.logger Custom logger instance
19
22
  */
20
23
  constructor(config?: {
21
24
  baseURL?: string;
25
+ logger?: Logger;
22
26
  });
23
27
  /**
24
28
  * Sends a chat message to OpenAI's API
@@ -27,7 +31,7 @@ export declare class OpenAIClientAdapter implements ILLMClientAdapter {
27
31
  * @param apiKey - The decrypted OpenAI API key
28
32
  * @returns Promise resolving to success or failure response
29
33
  */
30
- sendMessage(request: InternalLLMChatRequest, apiKey: string): Promise<LLMResponse | LLMFailureResponse>;
34
+ sendMessage(request: InternalLLMChatRequest, apiKey: string, options?: AdapterRequestOptions): Promise<LLMResponse | LLMFailureResponse>;
31
35
  /**
32
36
  * Validates OpenAI API key format
33
37
  *