genai-lite 0.6.1 → 0.7.2

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 (38) hide show
  1. package/README.md +21 -0
  2. package/dist/adapters/image/GenaiElectronImageAdapter.js +6 -4
  3. package/dist/adapters/image/OpenAIImageAdapter.js +5 -3
  4. package/dist/config/llm-presets.json +40 -44
  5. package/dist/image/ImageService.d.ts +1 -0
  6. package/dist/image/ImageService.js +13 -10
  7. package/dist/index.d.ts +6 -0
  8. package/dist/index.js +11 -1
  9. package/dist/llm/LLMService.d.ts +6 -0
  10. package/dist/llm/LLMService.js +20 -17
  11. package/dist/llm/clients/AnthropicClientAdapter.js +36 -15
  12. package/dist/llm/clients/GeminiClientAdapter.js +36 -12
  13. package/dist/llm/clients/LlamaCppClientAdapter.js +43 -23
  14. package/dist/llm/clients/MistralClientAdapter.d.ts +94 -0
  15. package/dist/llm/clients/MistralClientAdapter.js +239 -0
  16. package/dist/llm/clients/OpenAIClientAdapter.js +36 -17
  17. package/dist/llm/clients/OpenRouterClientAdapter.d.ts +103 -0
  18. package/dist/llm/clients/OpenRouterClientAdapter.js +285 -0
  19. package/dist/llm/config.js +75 -32
  20. package/dist/llm/services/ModelResolver.d.ts +3 -1
  21. package/dist/llm/services/ModelResolver.js +5 -3
  22. package/dist/llm/services/SettingsManager.d.ts +3 -0
  23. package/dist/llm/services/SettingsManager.js +29 -20
  24. package/dist/llm/types.d.ts +79 -0
  25. package/dist/logging/defaultLogger.d.ts +35 -0
  26. package/dist/logging/defaultLogger.js +94 -0
  27. package/dist/logging/index.d.ts +2 -0
  28. package/dist/logging/index.js +7 -0
  29. package/dist/logging/types.d.ts +23 -0
  30. package/dist/logging/types.js +2 -0
  31. package/dist/prompting/parser.js +4 -1
  32. package/dist/shared/adapters/systemMessageUtils.d.ts +162 -0
  33. package/dist/shared/adapters/systemMessageUtils.js +172 -0
  34. package/dist/shared/services/AdapterRegistry.d.ts +4 -1
  35. package/dist/shared/services/AdapterRegistry.js +12 -9
  36. package/dist/types/image.d.ts +5 -0
  37. package/package.json +2 -1
  38. package/src/config/llm-presets.json +40 -44
package/README.md CHANGED
@@ -14,6 +14,7 @@ A lightweight, portable Node.js/TypeScript library providing a unified interface
14
14
  - 🛡️ **Provider Normalization** - Consistent responses across different AI APIs
15
15
  - 🎨 **Configurable Model Presets** - Built-in presets with full customization options
16
16
  - 🎭 **Template Engine** - Sophisticated templating with conditionals and variable substitution
17
+ - 📊 **Configurable Logging** - Debug mode, custom loggers (pino, winston), and silent mode for tests
17
18
 
18
19
  ## Installation
19
20
 
@@ -112,6 +113,7 @@ Comprehensive documentation is available in the **[`genai-lite-docs`](./genai-li
112
113
 
113
114
  ### Utilities & Advanced
114
115
  - **[Prompting Utilities](./genai-lite-docs/prompting-utilities.md)** - Template engine, token counting, content parsing
116
+ - **[Logging](./genai-lite-docs/logging.md)** - Configure logging and debugging
115
117
  - **[TypeScript Reference](./genai-lite-docs/typescript-reference.md)** - Type definitions
116
118
 
117
119
  ### Provider Reference
@@ -154,6 +156,25 @@ const llmService = new LLMService(myKeyProvider);
154
156
 
155
157
  See **[Core Concepts](./genai-lite-docs/core-concepts.md#api-key-management)** for detailed examples including Electron integration.
156
158
 
159
+ ## Logging Configuration
160
+
161
+ Control logging verbosity via environment variable or service options:
162
+
163
+ ```bash
164
+ # Environment variable (applies to all services)
165
+ export GENAI_LITE_LOG_LEVEL=debug # Options: silent, error, warn, info, debug
166
+ ```
167
+
168
+ ```typescript
169
+ // Per-service configuration
170
+ const llmService = new LLMService(fromEnvironment, {
171
+ logLevel: 'debug', // Override env var
172
+ logger: customPinoLogger // Inject pino/winston/etc.
173
+ });
174
+ ```
175
+
176
+ See **[Logging](./genai-lite-docs/logging.md)** for custom logger integration and testing patterns.
177
+
157
178
  ## Example Applications
158
179
 
159
180
  The library includes two complete demo applications showcasing all features:
@@ -18,6 +18,8 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.GenaiElectronImageAdapter = void 0;
20
20
  const errorUtils_1 = require("../../shared/adapters/errorUtils");
21
+ const defaultLogger_1 = require("../../logging/defaultLogger");
22
+ const logger = (0, defaultLogger_1.createDefaultLogger)();
21
23
  /**
22
24
  * Adapter for genai-electron's local diffusion image generation
23
25
  */
@@ -44,7 +46,7 @@ class GenaiElectronImageAdapter {
44
46
  try {
45
47
  // Build request payload
46
48
  const payload = this.buildRequestPayload(resolvedPrompt, request, settings);
47
- console.log(`GenaiElectron Image API: Starting generation`, {
49
+ logger.debug(`GenaiElectron Image API: Starting generation`, {
48
50
  prompt: resolvedPrompt.substring(0, 100),
49
51
  count: payload.count,
50
52
  dimensions: `${payload.width}x${payload.height}`,
@@ -52,15 +54,15 @@ class GenaiElectronImageAdapter {
52
54
  });
53
55
  // Start generation (returns immediately with ID)
54
56
  const generationId = await this.startGeneration(payload);
55
- console.log(`GenaiElectron Image API: Generation started with ID: ${generationId}`);
57
+ logger.info(`GenaiElectron Image API: Generation started with ID: ${generationId}`);
56
58
  // Poll for completion
57
59
  const result = await this.pollForCompletion(generationId, settings.diffusion?.onProgress);
58
- console.log(`GenaiElectron Image API: Generation complete (${result.timeTaken}ms)`);
60
+ logger.info(`GenaiElectron Image API: Generation complete (${result.timeTaken}ms)`);
59
61
  // Convert to ImageGenerationResponse
60
62
  return this.convertToResponse(result, request);
61
63
  }
62
64
  catch (error) {
63
- console.error('GenaiElectron Image API error:', error);
65
+ logger.error('GenaiElectron Image API error:', error);
64
66
  throw this.handleError(error, request);
65
67
  }
66
68
  }
@@ -20,6 +20,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.OpenAIImageAdapter = void 0;
21
21
  const openai_1 = __importDefault(require("openai"));
22
22
  const errorUtils_1 = require("../../shared/adapters/errorUtils");
23
+ const defaultLogger_1 = require("../../logging/defaultLogger");
24
+ const logger = (0, defaultLogger_1.createDefaultLogger)();
23
25
  /**
24
26
  * Prompt length limits per model
25
27
  */
@@ -94,7 +96,7 @@ class OpenAIImageAdapter {
94
96
  // dall-e-2/dall-e-3: use traditional parameters
95
97
  this.addDalleParams(params, settings);
96
98
  }
97
- console.log(`OpenAI Image API call for model: ${request.modelId}`, {
99
+ logger.debug(`OpenAI Image API call for model: ${request.modelId}`, {
98
100
  model: params.model,
99
101
  promptLength: resolvedPrompt.length,
100
102
  n: params.n,
@@ -105,12 +107,12 @@ class OpenAIImageAdapter {
105
107
  if (!response.data || response.data.length === 0) {
106
108
  throw new Error('OpenAI API returned no images in response');
107
109
  }
108
- console.log(`OpenAI Image API call successful, generated ${response.data.length} images`);
110
+ logger.info(`OpenAI Image API call successful, generated ${response.data.length} images`);
109
111
  // Process response
110
112
  return await this.processResponse(response, request, isGptImageModel);
111
113
  }
112
114
  catch (error) {
113
- console.error('OpenAI Image API error:', error);
115
+ logger.error('OpenAI Image API error:', error);
114
116
  throw this.handleError(error, request);
115
117
  }
116
118
  }
@@ -369,50 +369,6 @@
369
369
  }
370
370
  }
371
371
  },
372
- {
373
- "id": "google-gemini-2.0-flash-default",
374
- "displayName": "Google - Gemini 2.0 Flash",
375
- "description": "Default preset for Gemini 2.0 Flash.",
376
- "providerId": "gemini",
377
- "modelId": "gemini-2.0-flash",
378
- "settings": {
379
- "temperature": 0.7,
380
- "geminiSafetySettings": [
381
- { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE" },
382
- {
383
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
384
- "threshold": "BLOCK_NONE"
385
- },
386
- {
387
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
388
- "threshold": "BLOCK_NONE"
389
- },
390
- { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" }
391
- ]
392
- }
393
- },
394
- {
395
- "id": "google-gemini-2.0-flash-lite-default",
396
- "displayName": "Google - Gemini 2.0 Flash Lite",
397
- "description": "Default preset for Gemini 2.0 Flash Lite.",
398
- "providerId": "gemini",
399
- "modelId": "gemini-2.0-flash-lite",
400
- "settings": {
401
- "temperature": 0.7,
402
- "geminiSafetySettings": [
403
- { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE" },
404
- {
405
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
406
- "threshold": "BLOCK_NONE"
407
- },
408
- {
409
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
410
- "threshold": "BLOCK_NONE"
411
- },
412
- { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" }
413
- ]
414
- }
415
- },
416
372
  {
417
373
  "id": "google-gemma-3-27b-default",
418
374
  "displayName": "Google - Gemma 3 27B (Free)",
@@ -598,5 +554,45 @@
598
554
  "settings": {
599
555
  "temperature": 0.7
600
556
  }
557
+ },
558
+ {
559
+ "id": "openrouter-gemma-3-27b-free",
560
+ "displayName": "OpenRouter - Gemma 3 27B (Free)",
561
+ "description": "Google's Gemma 3 27B via OpenRouter free tier.",
562
+ "providerId": "openrouter",
563
+ "modelId": "google/gemma-3-27b-it:free",
564
+ "settings": {
565
+ "temperature": 0.7
566
+ }
567
+ },
568
+ {
569
+ "id": "openrouter-mistral-small-3.1-free",
570
+ "displayName": "OpenRouter - Mistral Small 3.1 (Free)",
571
+ "description": "Mistral Small 3.1 24B via OpenRouter free tier.",
572
+ "providerId": "openrouter",
573
+ "modelId": "mistralai/mistral-small-3.1-24b-instruct:free",
574
+ "settings": {
575
+ "temperature": 0.7
576
+ }
577
+ },
578
+ {
579
+ "id": "mistral-small-default",
580
+ "displayName": "Mistral - Small",
581
+ "description": "Cost-effective Mistral Small model for general tasks.",
582
+ "providerId": "mistral",
583
+ "modelId": "mistral-small-latest",
584
+ "settings": {
585
+ "temperature": 0.7
586
+ }
587
+ },
588
+ {
589
+ "id": "mistral-codestral-default",
590
+ "displayName": "Mistral - Codestral",
591
+ "description": "Specialized model for code generation with lower temperature.",
592
+ "providerId": "mistral",
593
+ "modelId": "codestral-2501",
594
+ "settings": {
595
+ "temperature": 0.3
596
+ }
601
597
  }
602
598
  ]
@@ -11,6 +11,7 @@ import type { ImageGenerationRequest, ImageGenerationRequestWithPreset, ImageGen
11
11
  */
12
12
  export declare class ImageService {
13
13
  private getApiKey;
14
+ private logger;
14
15
  private presetManager;
15
16
  private adapterRegistry;
16
17
  private requestValidator;
@@ -10,6 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ImageService = void 0;
13
+ const defaultLogger_1 = require("../logging/defaultLogger");
13
14
  const config_1 = require("./config");
14
15
  const image_presets_json_1 = __importDefault(require("../config/image-presets.json"));
15
16
  const PresetManager_1 = require("../shared/services/PresetManager");
@@ -28,13 +29,15 @@ const defaultImagePresets = image_presets_json_1.default;
28
29
  class ImageService {
29
30
  constructor(getApiKey, options = {}) {
30
31
  this.getApiKey = getApiKey;
32
+ // Initialize logger - custom logger takes precedence over logLevel
33
+ this.logger = options.logger ?? (0, defaultLogger_1.createDefaultLogger)(options.logLevel);
31
34
  // Initialize helper services
32
35
  this.presetManager = new PresetManager_1.PresetManager(defaultImagePresets, options.presets, options.presetMode);
33
36
  // Initialize adapter registry with fallback
34
37
  this.adapterRegistry = new AdapterRegistry_1.AdapterRegistry({
35
38
  supportedProviders: config_1.SUPPORTED_IMAGE_PROVIDERS,
36
39
  fallbackAdapter: new MockImageAdapter_1.MockImageAdapter(),
37
- });
40
+ }, this.logger);
38
41
  // Register OpenAI adapter
39
42
  const openaiConfig = config_1.IMAGE_ADAPTER_CONFIGS['openai-images'];
40
43
  const openaiBaseURL = options.baseUrls?.['openai-images'] || openaiConfig.baseURL;
@@ -58,7 +61,7 @@ class ImageService {
58
61
  this.requestValidator = new ImageRequestValidator_1.ImageRequestValidator();
59
62
  this.settingsResolver = new ImageSettingsResolver_1.ImageSettingsResolver();
60
63
  this.modelResolver = new ImageModelResolver_1.ImageModelResolver(this.presetManager);
61
- console.log('ImageService: Initialized with OpenAI Images and genai-electron adapters');
64
+ this.logger.debug('ImageService: Initialized with OpenAI Images and genai-electron adapters');
62
65
  }
63
66
  /**
64
67
  * Generates images based on the request
@@ -67,7 +70,7 @@ class ImageService {
67
70
  * @returns Promise resolving to response or error
68
71
  */
69
72
  async generateImage(request) {
70
- console.log('ImageService.generateImage called');
73
+ this.logger.info('ImageService.generateImage called');
71
74
  try {
72
75
  // Resolve model information
73
76
  const resolved = this.modelResolver.resolve(request);
@@ -114,18 +117,18 @@ class ImageService {
114
117
  };
115
118
  }
116
119
  // Generate images
117
- console.log(`ImageService: Calling adapter for provider: ${providerId}`);
120
+ this.logger.info(`ImageService: Calling adapter for provider: ${providerId}`);
118
121
  const response = await adapter.generate({
119
122
  request: fullRequest,
120
123
  resolvedPrompt,
121
124
  settings: resolvedSettings,
122
125
  apiKey,
123
126
  });
124
- console.log('ImageService: Image generation completed successfully');
127
+ this.logger.info('ImageService: Image generation completed successfully');
125
128
  return response;
126
129
  }
127
130
  catch (error) {
128
- console.error('ImageService: Error during image generation:', error);
131
+ this.logger.error('ImageService: Error during image generation:', error);
129
132
  return {
130
133
  object: 'error',
131
134
  providerId: providerId,
@@ -142,7 +145,7 @@ class ImageService {
142
145
  }
143
146
  }
144
147
  catch (error) {
145
- console.error('ImageService: Unexpected error:', error);
148
+ this.logger.error('ImageService: Unexpected error:', error);
146
149
  const req = request;
147
150
  return {
148
151
  object: 'error',
@@ -163,7 +166,7 @@ class ImageService {
163
166
  * @returns Promise resolving to array of provider information
164
167
  */
165
168
  async getProviders() {
166
- console.log('ImageService.getProviders called');
169
+ this.logger.debug('ImageService.getProviders called');
167
170
  return [...config_1.SUPPORTED_IMAGE_PROVIDERS];
168
171
  }
169
172
  /**
@@ -173,9 +176,9 @@ class ImageService {
173
176
  * @returns Promise resolving to array of model information
174
177
  */
175
178
  async getModels(providerId) {
176
- console.log(`ImageService.getModels called for provider: ${providerId}`);
179
+ this.logger.debug(`ImageService.getModels called for provider: ${providerId}`);
177
180
  const models = (0, config_1.getImageModelsByProvider)(providerId);
178
- console.log(`ImageService: Found ${models.length} models for provider: ${providerId}`);
181
+ this.logger.debug(`ImageService: Found ${models.length} models for provider: ${providerId}`);
179
182
  return [...models];
180
183
  }
181
184
  /**
package/dist/index.d.ts CHANGED
@@ -9,6 +9,10 @@ export { LlamaCppClientAdapter } from "./llm/clients/LlamaCppClientAdapter";
9
9
  export { LlamaCppServerClient } from "./llm/clients/LlamaCppServerClient";
10
10
  export type { LlamaCppClientConfig, } from "./llm/clients/LlamaCppClientAdapter";
11
11
  export type { LlamaCppHealthResponse, LlamaCppTokenizeResponse, LlamaCppDetokenizeResponse, LlamaCppEmbeddingResponse, LlamaCppInfillResponse, LlamaCppPropsResponse, LlamaCppMetricsResponse, LlamaCppSlot, LlamaCppSlotsResponse, LlamaCppModel, LlamaCppModelsResponse, } from "./llm/clients/LlamaCppServerClient";
12
+ export { OpenRouterClientAdapter } from "./llm/clients/OpenRouterClientAdapter";
13
+ export type { OpenRouterClientConfig } from "./llm/clients/OpenRouterClientAdapter";
14
+ export { MistralClientAdapter } from "./llm/clients/MistralClientAdapter";
15
+ export type { MistralClientConfig } from "./llm/clients/MistralClientAdapter";
12
16
  export { ImageService } from "./image/ImageService";
13
17
  export type { ImageProviderId, ImageMimeType, ImageResponseFormat, ImageQuality, ImageStyle, DiffusionSampler, ImageProgressStage, ImageProgressCallback, DiffusionSettings, OpenAISpecificSettings, ImageGenerationSettings, ResolvedImageGenerationSettings, ImageUsage, GeneratedImage, ImageGenerationRequestBase, ImageGenerationRequest, ImageGenerationRequestWithPreset, ImageGenerationResponse, ImageFailureResponse, ImageProviderCapabilities, ImageModelInfo, ImageProviderInfo, ImagePreset, ImageProviderAdapterConfig, ImageProviderAdapter, ImageServiceOptions, CreatePromptResult, } from "./types/image";
14
18
  export { renderTemplate } from "./prompting/template";
@@ -17,3 +21,5 @@ export { parseStructuredContent, parseRoleTags, extractInitialTaggedContent, par
17
21
  export type { TemplateMetadata } from "./prompting/parser";
18
22
  export { createFallbackModelInfo, detectGgufCapabilities, KNOWN_GGUF_MODELS } from "./llm/config";
19
23
  export type { GgufModelPattern } from "./llm/config";
24
+ export type { Logger, LogLevel, LoggingConfig } from "./logging/types";
25
+ export { createDefaultLogger, DEFAULT_LOG_LEVEL, silentLogger } from "./logging/defaultLogger";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.KNOWN_GGUF_MODELS = exports.detectGgufCapabilities = exports.createFallbackModelInfo = exports.parseTemplateWithMetadata = exports.extractInitialTaggedContent = exports.parseRoleTags = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = exports.ImageService = exports.LlamaCppServerClient = exports.LlamaCppClientAdapter = exports.fromEnvironment = exports.LLMService = void 0;
17
+ exports.silentLogger = exports.DEFAULT_LOG_LEVEL = exports.createDefaultLogger = exports.KNOWN_GGUF_MODELS = exports.detectGgufCapabilities = exports.createFallbackModelInfo = exports.parseTemplateWithMetadata = exports.extractInitialTaggedContent = exports.parseRoleTags = exports.parseStructuredContent = exports.extractRandomVariables = exports.getSmartPreview = exports.countTokens = exports.renderTemplate = exports.ImageService = exports.MistralClientAdapter = exports.OpenRouterClientAdapter = exports.LlamaCppServerClient = exports.LlamaCppClientAdapter = exports.fromEnvironment = exports.LLMService = void 0;
18
18
  // --- LLM Service ---
19
19
  var LLMService_1 = require("./llm/LLMService");
20
20
  Object.defineProperty(exports, "LLMService", { enumerable: true, get: function () { return LLMService_1.LLMService; } });
@@ -30,6 +30,12 @@ var LlamaCppClientAdapter_1 = require("./llm/clients/LlamaCppClientAdapter");
30
30
  Object.defineProperty(exports, "LlamaCppClientAdapter", { enumerable: true, get: function () { return LlamaCppClientAdapter_1.LlamaCppClientAdapter; } });
31
31
  var LlamaCppServerClient_1 = require("./llm/clients/LlamaCppServerClient");
32
32
  Object.defineProperty(exports, "LlamaCppServerClient", { enumerable: true, get: function () { return LlamaCppServerClient_1.LlamaCppServerClient; } });
33
+ // --- OpenRouter Integration ---
34
+ var OpenRouterClientAdapter_1 = require("./llm/clients/OpenRouterClientAdapter");
35
+ Object.defineProperty(exports, "OpenRouterClientAdapter", { enumerable: true, get: function () { return OpenRouterClientAdapter_1.OpenRouterClientAdapter; } });
36
+ // --- Mistral Integration ---
37
+ var MistralClientAdapter_1 = require("./llm/clients/MistralClientAdapter");
38
+ Object.defineProperty(exports, "MistralClientAdapter", { enumerable: true, get: function () { return MistralClientAdapter_1.MistralClientAdapter; } });
33
39
  // --- Image Generation ---
34
40
  // Export Image Service
35
41
  var ImageService_1 = require("./image/ImageService");
@@ -50,3 +56,7 @@ var config_1 = require("./llm/config");
50
56
  Object.defineProperty(exports, "createFallbackModelInfo", { enumerable: true, get: function () { return config_1.createFallbackModelInfo; } });
51
57
  Object.defineProperty(exports, "detectGgufCapabilities", { enumerable: true, get: function () { return config_1.detectGgufCapabilities; } });
52
58
  Object.defineProperty(exports, "KNOWN_GGUF_MODELS", { enumerable: true, get: function () { return config_1.KNOWN_GGUF_MODELS; } });
59
+ var defaultLogger_1 = require("./logging/defaultLogger");
60
+ Object.defineProperty(exports, "createDefaultLogger", { enumerable: true, get: function () { return defaultLogger_1.createDefaultLogger; } });
61
+ Object.defineProperty(exports, "DEFAULT_LOG_LEVEL", { enumerable: true, get: function () { return defaultLogger_1.DEFAULT_LOG_LEVEL; } });
62
+ Object.defineProperty(exports, "silentLogger", { enumerable: true, get: function () { return defaultLogger_1.silentLogger; } });
@@ -1,4 +1,5 @@
1
1
  import type { ApiKeyProvider, PresetMode } from '../types';
2
+ import type { Logger, LogLevel } from '../logging/types';
2
3
  import type { LLMChatRequest, LLMChatRequestWithPreset, LLMResponse, LLMFailureResponse, ProviderInfo, ModelInfo, ApiProviderId, LLMSettings, ModelContext, LLMMessage } from "./types";
3
4
  import type { ModelPreset } from "../types/presets";
4
5
  export type { PresetMode };
@@ -10,6 +11,10 @@ export interface LLMServiceOptions {
10
11
  presets?: ModelPreset[];
11
12
  /** The strategy for integrating custom presets. Defaults to 'extend'. */
12
13
  presetMode?: PresetMode;
14
+ /** Log level for filtering messages. Defaults to GENAI_LITE_LOG_LEVEL env var or 'warn'. */
15
+ logLevel?: LogLevel;
16
+ /** Custom logger implementation. If provided, logLevel is ignored. */
17
+ logger?: Logger;
13
18
  }
14
19
  /**
15
20
  * Result from createMessages method
@@ -35,6 +40,7 @@ export interface CreateMessagesResult {
35
40
  */
36
41
  export declare class LLMService {
37
42
  private getApiKey;
43
+ private logger;
38
44
  private presetManager;
39
45
  private adapterRegistry;
40
46
  private requestValidator;
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  };
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.LLMService = void 0;
9
+ const defaultLogger_1 = require("../logging/defaultLogger");
9
10
  const config_1 = require("./config");
10
11
  const template_1 = require("../prompting/template");
11
12
  const parser_1 = require("../prompting/parser");
@@ -31,17 +32,19 @@ const ModelResolver_1 = require("./services/ModelResolver");
31
32
  class LLMService {
32
33
  constructor(getApiKey, options = {}) {
33
34
  this.getApiKey = getApiKey;
34
- // Initialize services
35
+ // Initialize logger - custom logger takes precedence over logLevel
36
+ this.logger = options.logger ?? (0, defaultLogger_1.createDefaultLogger)(options.logLevel);
37
+ // Initialize services with logger
35
38
  this.presetManager = new PresetManager_1.PresetManager(llm_presets_json_1.default, options.presets, options.presetMode);
36
39
  this.adapterRegistry = new AdapterRegistry_1.AdapterRegistry({
37
40
  supportedProviders: config_1.SUPPORTED_PROVIDERS,
38
41
  fallbackAdapter: new MockClientAdapter_1.MockClientAdapter(),
39
42
  adapterConstructors: config_1.ADAPTER_CONSTRUCTORS,
40
43
  adapterConfigs: config_1.ADAPTER_CONFIGS,
41
- });
44
+ }, this.logger);
42
45
  this.requestValidator = new RequestValidator_1.RequestValidator();
43
- this.settingsManager = new SettingsManager_1.SettingsManager();
44
- this.modelResolver = new ModelResolver_1.ModelResolver(this.presetManager, this.adapterRegistry);
46
+ this.settingsManager = new SettingsManager_1.SettingsManager(this.logger);
47
+ this.modelResolver = new ModelResolver_1.ModelResolver(this.presetManager, this.adapterRegistry, this.logger);
45
48
  }
46
49
  /**
47
50
  * Gets list of supported LLM providers
@@ -49,7 +52,7 @@ class LLMService {
49
52
  * @returns Promise resolving to array of provider information
50
53
  */
51
54
  async getProviders() {
52
- console.log("LLMService.getProviders called");
55
+ this.logger.debug("LLMService.getProviders called");
53
56
  return [...config_1.SUPPORTED_PROVIDERS]; // Return a copy to prevent external modification
54
57
  }
55
58
  /**
@@ -59,14 +62,14 @@ class LLMService {
59
62
  * @returns Promise resolving to array of model information
60
63
  */
61
64
  async getModels(providerId) {
62
- console.log(`LLMService.getModels called for provider: ${providerId}`);
65
+ this.logger.debug(`LLMService.getModels called for provider: ${providerId}`);
63
66
  // Validate provider exists
64
67
  const models = (0, config_1.getModelsByProvider)(providerId);
65
68
  if (models.length === 0) {
66
- console.warn(`Requested models for unsupported provider: ${providerId}`);
69
+ this.logger.warn(`Requested models for unsupported provider: ${providerId}`);
67
70
  return [];
68
71
  }
69
- console.log(`Found ${models.length} models for provider: ${providerId}`);
72
+ this.logger.debug(`Found ${models.length} models for provider: ${providerId}`);
70
73
  return [...models]; // Return a copy to prevent external modification
71
74
  }
72
75
  /**
@@ -76,7 +79,7 @@ class LLMService {
76
79
  * @returns Promise resolving to either success or failure response
77
80
  */
78
81
  async sendMessage(request) {
79
- console.log(`LLMService.sendMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
82
+ this.logger.info(`LLMService.sendMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
80
83
  try {
81
84
  // Resolve model information from preset or direct IDs
82
85
  const resolved = await this.modelResolver.resolve(request);
@@ -130,7 +133,7 @@ class LLMService {
130
133
  ...resolvedRequest,
131
134
  settings: filteredSettings,
132
135
  };
133
- console.log(`Processing LLM request with (potentially filtered) settings:`, {
136
+ this.logger.debug(`Processing LLM request with (potentially filtered) settings:`, {
134
137
  provider: providerId,
135
138
  model: modelId,
136
139
  settings: filteredSettings,
@@ -166,7 +169,7 @@ class LLMService {
166
169
  object: "error",
167
170
  };
168
171
  }
169
- console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
172
+ this.logger.info(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
170
173
  const result = await clientAdapter.sendMessage(internalRequest, apiKey);
171
174
  // Post-process for thinking tag fallback
172
175
  // This feature extracts reasoning from XML tags when native reasoning is not active.
@@ -186,7 +189,7 @@ class LLMService {
186
189
  const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
187
190
  if (extracted !== null) {
188
191
  // Success: thinking tag found
189
- console.log(`Extracted <${tagName}> block from response.`);
192
+ this.logger.debug(`Extracted <${tagName}> block from response.`);
190
193
  // Handle the edge case: append to existing reasoning if present (e.g., native reasoning + thinking tags)
191
194
  const existingReasoning = choice.reasoning || '';
192
195
  if (existingReasoning) {
@@ -234,11 +237,11 @@ class LLMService {
234
237
  }
235
238
  }
236
239
  }
237
- console.log(`LLM request completed successfully for model: ${modelId}`);
240
+ this.logger.info(`LLM request completed successfully for model: ${modelId}`);
238
241
  return result;
239
242
  }
240
243
  catch (error) {
241
- console.error("Error in LLMService.sendMessage:", error);
244
+ this.logger.error("Error in LLMService.sendMessage:", error);
242
245
  return {
243
246
  provider: providerId,
244
247
  model: modelId,
@@ -255,7 +258,7 @@ class LLMService {
255
258
  }
256
259
  }
257
260
  catch (error) {
258
- console.error("Error in LLMService.sendMessage (outer):", error);
261
+ this.logger.error("Error in LLMService.sendMessage (outer):", error);
259
262
  return {
260
263
  provider: request.providerId || request.presetId || 'unknown',
261
264
  model: request.modelId || request.presetId || 'unknown',
@@ -328,7 +331,7 @@ class LLMService {
328
331
  * ```
329
332
  */
330
333
  async createMessages(options) {
331
- console.log('LLMService.createMessages called');
334
+ this.logger.debug('LLMService.createMessages called');
332
335
  // NEW: Step 1 - Parse the template for metadata and content
333
336
  const { metadata, content: templateContent } = (0, parser_1.parseTemplateWithMetadata)(options.template);
334
337
  // Validate the settings from the template
@@ -345,7 +348,7 @@ class LLMService {
345
348
  });
346
349
  if (resolved.error) {
347
350
  // If resolution fails, proceed without model context
348
- console.warn('Model resolution failed, proceeding without model context:', resolved.error);
351
+ this.logger.warn('Model resolution failed, proceeding without model context:', resolved.error);
349
352
  }
350
353
  else {
351
354
  const { providerId, modelId, modelInfo, settings } = resolved;
@@ -9,6 +9,9 @@ exports.AnthropicClientAdapter = void 0;
9
9
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
10
10
  const types_1 = require("./types");
11
11
  const errorUtils_1 = require("../../shared/adapters/errorUtils");
12
+ const systemMessageUtils_1 = require("../../shared/adapters/systemMessageUtils");
13
+ const defaultLogger_1 = require("../../logging/defaultLogger");
14
+ const logger = (0, defaultLogger_1.createDefaultLogger)();
12
15
  /**
13
16
  * Client adapter for Anthropic API integration
14
17
  *
@@ -93,8 +96,8 @@ class AnthropicClientAdapter {
93
96
  };
94
97
  }
95
98
  }
96
- console.log(`Making Anthropic API call for model: ${request.modelId}`);
97
- console.log(`Anthropic API parameters:`, {
99
+ logger.info(`Making Anthropic API call for model: ${request.modelId}`);
100
+ logger.debug(`Anthropic API parameters:`, {
98
101
  model: messageParams.model,
99
102
  temperature: messageParams.temperature,
100
103
  max_tokens: messageParams.max_tokens,
@@ -105,12 +108,12 @@ class AnthropicClientAdapter {
105
108
  });
106
109
  // Make the API call
107
110
  const completion = await anthropic.messages.create(messageParams);
108
- console.log(`Anthropic API call successful, response ID: ${completion.id}`);
111
+ logger.info(`Anthropic API call successful, response ID: ${completion.id}`);
109
112
  // Convert to standardized response format
110
113
  return this.createSuccessResponse(completion, request);
111
114
  }
112
115
  catch (error) {
113
- console.error("Anthropic API error:", error);
116
+ logger.error("Anthropic API error:", error);
114
117
  return this.createErrorResponse(error, request);
115
118
  }
116
119
  }
@@ -142,18 +145,14 @@ class AnthropicClientAdapter {
142
145
  */
143
146
  formatMessagesForAnthropic(request) {
144
147
  const messages = [];
145
- let systemMessage = request.systemMessage;
148
+ const inlineSystemMessages = [];
149
+ // Check if model supports system messages
150
+ const supportsSystem = request.settings.supportsSystemMessage !== false;
146
151
  // Process conversation messages
147
152
  for (const message of request.messages) {
148
153
  if (message.role === "system") {
149
- // Anthropic handles system messages separately
150
- // If we already have a system message, append to it
151
- if (systemMessage) {
152
- systemMessage += "\n\n" + message.content;
153
- }
154
- else {
155
- systemMessage = message.content;
156
- }
154
+ // Collect inline system messages
155
+ inlineSystemMessages.push(message.content);
157
156
  }
158
157
  else if (message.role === "user") {
159
158
  messages.push({
@@ -168,10 +167,32 @@ class AnthropicClientAdapter {
168
167
  });
169
168
  }
170
169
  }
170
+ // Use shared utility to collect and combine system content
171
+ const { combinedSystemContent, useNativeSystemMessage } = (0, systemMessageUtils_1.collectSystemContent)(request.systemMessage, inlineSystemMessages, supportsSystem);
172
+ let systemMessage;
173
+ if (combinedSystemContent) {
174
+ if (useNativeSystemMessage) {
175
+ // Model supports system messages - use Anthropic's system parameter
176
+ systemMessage = combinedSystemContent;
177
+ }
178
+ else {
179
+ // Model doesn't support system messages - prepend to first user message
180
+ const simpleMessages = messages.map((m) => ({
181
+ role: m.role,
182
+ content: m.content,
183
+ }));
184
+ const modifiedIndex = (0, systemMessageUtils_1.prependSystemToFirstUserMessage)(simpleMessages, combinedSystemContent, request.settings.systemMessageFallback);
185
+ if (modifiedIndex !== -1) {
186
+ messages[modifiedIndex].content = simpleMessages[modifiedIndex].content;
187
+ logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
188
+ }
189
+ // Don't set systemMessage - it stays undefined
190
+ }
191
+ }
171
192
  // Anthropic requires messages to start with 'user' role
172
193
  // If the first message is not from user, we need to handle this
173
194
  if (messages.length > 0 && messages[0].role !== "user") {
174
- console.warn("Anthropic API requires first message to be from user. Adjusting message order.");
195
+ logger.warn("Anthropic API requires first message to be from user. Adjusting message order.");
175
196
  // Find the first user message and move it to the front, or create a default one
176
197
  const firstUserIndex = messages.findIndex((msg) => msg.role === "user");
177
198
  if (firstUserIndex > 0) {
@@ -213,7 +234,7 @@ class AnthropicClientAdapter {
213
234
  // If roles don't alternate properly, we might need to combine messages
214
235
  // or insert a placeholder. For now, we'll skip non-alternating messages
215
236
  // and log a warning.
216
- console.warn(`Skipping message with unexpected role: expected ${expectedRole}, got ${message.role}`);
237
+ logger.warn(`Skipping message with unexpected role: expected ${expectedRole}, got ${message.role}`);
217
238
  }
218
239
  }
219
240
  return cleanedMessages;