genai-lite 0.2.0 → 0.3.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 (59) hide show
  1. package/README.md +508 -30
  2. package/dist/config/presets.json +121 -17
  3. package/dist/index.d.ts +3 -3
  4. package/dist/index.js +4 -3
  5. package/dist/llm/LLMService.createMessages.test.d.ts +4 -0
  6. package/dist/llm/LLMService.createMessages.test.js +364 -0
  7. package/dist/llm/LLMService.d.ts +49 -47
  8. package/dist/llm/LLMService.js +208 -303
  9. package/dist/llm/LLMService.original.d.ts +147 -0
  10. package/dist/llm/LLMService.original.js +656 -0
  11. package/dist/llm/LLMService.prepareMessage.test.d.ts +1 -0
  12. package/dist/llm/LLMService.prepareMessage.test.js +303 -0
  13. package/dist/llm/LLMService.sendMessage.preset.test.d.ts +1 -0
  14. package/dist/llm/LLMService.sendMessage.preset.test.js +153 -0
  15. package/dist/llm/LLMService.test.js +275 -0
  16. package/dist/llm/clients/AnthropicClientAdapter.js +64 -10
  17. package/dist/llm/clients/AnthropicClientAdapter.test.js +11 -1
  18. package/dist/llm/clients/GeminiClientAdapter.js +70 -11
  19. package/dist/llm/clients/GeminiClientAdapter.test.js +125 -1
  20. package/dist/llm/clients/MockClientAdapter.js +9 -3
  21. package/dist/llm/clients/MockClientAdapter.test.js +11 -1
  22. package/dist/llm/clients/OpenAIClientAdapter.js +26 -10
  23. package/dist/llm/clients/OpenAIClientAdapter.test.js +11 -1
  24. package/dist/llm/config.js +117 -2
  25. package/dist/llm/config.test.js +17 -0
  26. package/dist/llm/services/AdapterRegistry.d.ts +59 -0
  27. package/dist/llm/services/AdapterRegistry.js +113 -0
  28. package/dist/llm/services/AdapterRegistry.test.d.ts +1 -0
  29. package/dist/llm/services/AdapterRegistry.test.js +239 -0
  30. package/dist/llm/services/ModelResolver.d.ts +35 -0
  31. package/dist/llm/services/ModelResolver.js +116 -0
  32. package/dist/llm/services/ModelResolver.test.d.ts +1 -0
  33. package/dist/llm/services/ModelResolver.test.js +158 -0
  34. package/dist/llm/services/PresetManager.d.ts +27 -0
  35. package/dist/llm/services/PresetManager.js +50 -0
  36. package/dist/llm/services/PresetManager.test.d.ts +1 -0
  37. package/dist/llm/services/PresetManager.test.js +210 -0
  38. package/dist/llm/services/RequestValidator.d.ts +31 -0
  39. package/dist/llm/services/RequestValidator.js +122 -0
  40. package/dist/llm/services/RequestValidator.test.d.ts +1 -0
  41. package/dist/llm/services/RequestValidator.test.js +159 -0
  42. package/dist/llm/services/SettingsManager.d.ts +32 -0
  43. package/dist/llm/services/SettingsManager.js +223 -0
  44. package/dist/llm/services/SettingsManager.test.d.ts +1 -0
  45. package/dist/llm/services/SettingsManager.test.js +266 -0
  46. package/dist/llm/types.d.ts +107 -0
  47. package/dist/prompting/builder.d.ts +4 -0
  48. package/dist/prompting/builder.js +12 -61
  49. package/dist/prompting/content.js +3 -9
  50. package/dist/prompting/index.d.ts +2 -3
  51. package/dist/prompting/index.js +4 -5
  52. package/dist/prompting/parser.d.ts +80 -0
  53. package/dist/prompting/parser.js +133 -0
  54. package/dist/prompting/parser.test.js +348 -0
  55. package/dist/prompting/template.d.ts +8 -0
  56. package/dist/prompting/template.js +89 -6
  57. package/dist/prompting/template.test.js +116 -0
  58. package/package.json +3 -2
  59. package/src/config/presets.json +122 -17
@@ -1,14 +1,17 @@
1
1
  "use strict";
2
2
  // AI Summary: Main process service for LLM operations, integrating with ApiKeyProvider for secure key access.
3
3
  // Orchestrates LLM requests through provider-specific client adapters with proper error handling.
4
- var __importDefault = (this && this.__importDefault) || function (mod) {
5
- return (mod && mod.__esModule) ? mod : { "default": mod };
6
- };
7
4
  Object.defineProperty(exports, "__esModule", { value: true });
8
5
  exports.LLMService = void 0;
9
- const MockClientAdapter_1 = require("./clients/MockClientAdapter");
10
6
  const config_1 = require("./config");
11
- const presets_json_1 = __importDefault(require("../config/presets.json"));
7
+ const template_1 = require("../prompting/template");
8
+ const parser_1 = require("../prompting/parser");
9
+ // Import the extracted services
10
+ const PresetManager_1 = require("./services/PresetManager");
11
+ const AdapterRegistry_1 = require("./services/AdapterRegistry");
12
+ const RequestValidator_1 = require("./services/RequestValidator");
13
+ const SettingsManager_1 = require("./services/SettingsManager");
14
+ const ModelResolver_1 = require("./services/ModelResolver");
12
15
  /**
13
16
  * Main process service for LLM operations
14
17
  *
@@ -23,55 +26,12 @@ const presets_json_1 = __importDefault(require("../config/presets.json"));
23
26
  class LLMService {
24
27
  constructor(getApiKey, options = {}) {
25
28
  this.getApiKey = getApiKey;
26
- this.clientAdapters = new Map();
27
- this.mockClientAdapter = new MockClientAdapter_1.MockClientAdapter();
28
- // Initialize presets based on mode
29
- const finalPresets = new Map();
30
- const customPresets = options.presets || [];
31
- const mode = options.presetMode || 'extend';
32
- if (mode === 'replace') {
33
- // Replace Mode: Only use custom presets.
34
- for (const preset of customPresets) {
35
- finalPresets.set(preset.id, preset);
36
- }
37
- }
38
- else {
39
- // Extend Mode: Load defaults first, then add/override.
40
- for (const preset of presets_json_1.default) {
41
- finalPresets.set(preset.id, preset);
42
- }
43
- for (const preset of customPresets) {
44
- finalPresets.set(preset.id, preset);
45
- }
46
- }
47
- this.presets = Array.from(finalPresets.values());
48
- // Dynamically register client adapters based on configuration
49
- let registeredCount = 0;
50
- const successfullyRegisteredProviders = [];
51
- for (const provider of config_1.SUPPORTED_PROVIDERS) {
52
- const AdapterClass = config_1.ADAPTER_CONSTRUCTORS[provider.id];
53
- if (AdapterClass) {
54
- try {
55
- const adapterConfig = config_1.ADAPTER_CONFIGS[provider.id];
56
- const adapterInstance = new AdapterClass(adapterConfig);
57
- this.registerClientAdapter(provider.id, adapterInstance);
58
- registeredCount++;
59
- successfullyRegisteredProviders.push(provider.id);
60
- }
61
- catch (error) {
62
- console.error(`LLMService: Failed to instantiate adapter for provider '${provider.id}'. This provider will use the mock adapter. Error:`, error);
63
- }
64
- }
65
- else {
66
- console.warn(`LLMService: No adapter constructor found for supported provider '${provider.id}'. This provider will use the mock adapter as a fallback.`);
67
- }
68
- }
69
- if (registeredCount > 0) {
70
- console.log(`LLMService: Initialized with ${registeredCount} dynamically registered adapter(s) for: ${successfullyRegisteredProviders.join(", ")}.`);
71
- }
72
- else {
73
- console.log(`LLMService: No real adapters were dynamically registered. All providers will use the mock adapter.`);
74
- }
29
+ // Initialize services
30
+ this.presetManager = new PresetManager_1.PresetManager(options.presets, options.presetMode);
31
+ this.adapterRegistry = new AdapterRegistry_1.AdapterRegistry();
32
+ this.requestValidator = new RequestValidator_1.RequestValidator();
33
+ this.settingsManager = new SettingsManager_1.SettingsManager();
34
+ this.modelResolver = new ModelResolver_1.ModelResolver(this.presetManager);
75
35
  }
76
36
  /**
77
37
  * Gets list of supported LLM providers
@@ -91,11 +51,11 @@ class LLMService {
91
51
  async getModels(providerId) {
92
52
  console.log(`LLMService.getModels called for provider: ${providerId}`);
93
53
  // Validate provider exists
94
- if (!(0, config_1.isProviderSupported)(providerId)) {
54
+ const models = (0, config_1.getModelsByProvider)(providerId);
55
+ if (models.length === 0) {
95
56
  console.warn(`Requested models for unsupported provider: ${providerId}`);
96
57
  return [];
97
58
  }
98
- const models = (0, config_1.getModelsByProvider)(providerId);
99
59
  console.log(`Found ${models.length} models for provider: ${providerId}`);
100
60
  return [...models]; // Return a copy to prevent external modification
101
61
  }
@@ -106,146 +66,145 @@ class LLMService {
106
66
  * @returns Promise resolving to either success or failure response
107
67
  */
108
68
  async sendMessage(request) {
109
- console.log(`LLMService.sendMessage called for provider: ${request.providerId}, model: ${request.modelId}`);
69
+ console.log(`LLMService.sendMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
110
70
  try {
111
- // Validate provider
112
- if (!(0, config_1.isProviderSupported)(request.providerId)) {
113
- console.warn(`Unsupported provider in sendMessage: ${request.providerId}`);
114
- return {
115
- provider: request.providerId,
116
- model: request.modelId,
117
- error: {
118
- message: `Unsupported provider: ${request.providerId}. Supported providers: ${config_1.SUPPORTED_PROVIDERS.map((p) => p.id).join(", ")}`,
119
- code: "UNSUPPORTED_PROVIDER",
120
- type: "validation_error",
121
- },
122
- object: "error",
123
- };
124
- }
125
- // Validate model
126
- if (!(0, config_1.isModelSupported)(request.modelId, request.providerId)) {
127
- const availableModels = (0, config_1.getModelsByProvider)(request.providerId).map((m) => m.id);
128
- console.warn(`Unsupported model ${request.modelId} for provider ${request.providerId}. Available: ${availableModels.join(", ")}`);
129
- return {
130
- provider: request.providerId,
131
- model: request.modelId,
132
- error: {
133
- message: `Unsupported model: ${request.modelId} for provider: ${request.providerId}. Available models: ${availableModels.join(", ")}`,
134
- code: "UNSUPPORTED_MODEL",
135
- type: "validation_error",
136
- },
137
- object: "error",
138
- };
139
- }
140
- // Get model info for additional validation or settings
141
- const modelInfo = (0, config_1.getModelById)(request.modelId, request.providerId);
142
- if (!modelInfo) {
143
- // This shouldn't happen if validation above passed, but defensive programming
144
- console.error(`Model info not found for validated model: ${request.modelId}`);
145
- return {
146
- provider: request.providerId,
147
- model: request.modelId,
148
- error: {
149
- message: `Internal error: Model configuration not found for ${request.modelId}`,
150
- code: "MODEL_CONFIG_ERROR",
151
- type: "internal_error",
152
- },
153
- object: "error",
154
- };
71
+ // Resolve model information from preset or direct IDs
72
+ const resolved = this.modelResolver.resolve(request);
73
+ if (resolved.error) {
74
+ return resolved.error;
155
75
  }
76
+ const { providerId, modelId, modelInfo, settings: resolvedSettings } = resolved;
77
+ // Create a proper LLMChatRequest with resolved values
78
+ const resolvedRequest = {
79
+ ...request,
80
+ providerId: providerId,
81
+ modelId: modelId,
82
+ };
156
83
  // Validate basic request structure
157
- const structureValidationResult = this.validateRequestStructure(request);
84
+ const structureValidationResult = this.requestValidator.validateRequestStructure(resolvedRequest);
158
85
  if (structureValidationResult) {
159
86
  return structureValidationResult;
160
87
  }
161
- // Validate settings if provided
162
- if (request.settings) {
163
- const settingsValidationErrors = (0, config_1.validateLLMSettings)(request.settings);
164
- if (settingsValidationErrors.length > 0) {
165
- return {
166
- provider: request.providerId,
167
- model: request.modelId,
168
- error: {
169
- message: `Invalid settings: ${settingsValidationErrors.join(", ")}`,
170
- code: "INVALID_SETTINGS",
171
- type: "validation_error",
172
- },
173
- object: "error",
174
- };
88
+ // Validate settings if provided
89
+ const combinedSettings = { ...resolvedSettings, ...request.settings };
90
+ if (combinedSettings) {
91
+ const settingsValidation = this.requestValidator.validateSettings(combinedSettings, providerId, modelId);
92
+ if (settingsValidation) {
93
+ return settingsValidation;
175
94
  }
176
95
  }
177
96
  // Apply model-specific defaults and merge with user settings
178
- const finalSettings = this.mergeSettingsForModel(request.modelId, request.providerId, request.settings);
179
- // Filter out unsupported parameters based on model and provider configuration
180
- let filteredSettings = { ...finalSettings }; // Create a mutable copy
181
- // Get provider info for parameter filtering (modelInfo is already available from earlier validation)
182
- const providerInfo = (0, config_1.getProviderById)(request.providerId);
183
- const paramsToExclude = new Set();
184
- // Add provider-level exclusions
185
- if (providerInfo?.unsupportedParameters) {
186
- providerInfo.unsupportedParameters.forEach((param) => paramsToExclude.add(param));
97
+ const finalSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, combinedSettings);
98
+ // Validate reasoning settings for model capabilities
99
+ const reasoningValidation = this.requestValidator.validateReasoningSettings(modelInfo, finalSettings.reasoning, resolvedRequest);
100
+ if (reasoningValidation) {
101
+ return reasoningValidation;
187
102
  }
188
- // Add model-level exclusions (these will be added to any provider-level ones)
189
- if (modelInfo?.unsupportedParameters) {
190
- modelInfo.unsupportedParameters.forEach((param) => paramsToExclude.add(param));
191
- }
192
- if (paramsToExclude.size > 0) {
193
- console.log(`LLMService: Potential parameters to exclude for provider '${request.providerId}', model '${request.modelId}':`, Array.from(paramsToExclude));
103
+ // Get provider info for parameter filtering
104
+ const providerInfo = (0, config_1.getProviderById)(providerId);
105
+ if (!providerInfo) {
106
+ return {
107
+ provider: providerId,
108
+ model: modelId,
109
+ error: {
110
+ message: `Provider information not found: ${providerId}`,
111
+ code: "PROVIDER_ERROR",
112
+ type: "server_error",
113
+ },
114
+ object: "error",
115
+ };
194
116
  }
195
- paramsToExclude.forEach((param) => {
196
- // Check if the parameter key actually exists in filteredSettings before trying to delete
197
- // (it might have been undefined initially and thus not part of finalSettings depending on merge logic)
198
- // Using 'in' operator is robust for checking presence of properties, including those from prototype chain.
199
- // For direct properties of an object, hasOwnProperty is more specific.
200
- // Given finalSettings is Required<LLMSettings>, all keys should be present, potentially as undefined.
201
- if (param in filteredSettings) {
202
- console.log(`LLMService: Removing excluded parameter '${String(param)}' for provider '${request.providerId}', model '${request.modelId}'. Value was:`, filteredSettings[param]);
203
- delete filteredSettings[param]; // Cast to allow deletion
204
- }
205
- else {
206
- // This case should ideally not happen if finalSettings truly is Required<LLMSettings>
207
- // and mergeSettingsForModel ensures all keys are present (even if undefined).
208
- console.log(`LLMService: Parameter '${String(param)}' marked for exclusion was not found in settings for provider '${request.providerId}', model '${request.modelId}'.`);
209
- }
210
- });
117
+ // Filter out unsupported parameters
118
+ const filteredSettings = this.settingsManager.filterUnsupportedParameters(finalSettings, modelInfo, providerInfo);
211
119
  const internalRequest = {
212
- ...request,
120
+ ...resolvedRequest,
213
121
  settings: filteredSettings,
214
122
  };
215
123
  console.log(`Processing LLM request with (potentially filtered) settings:`, {
216
- provider: request.providerId,
217
- model: request.modelId,
124
+ provider: providerId,
125
+ model: modelId,
218
126
  settings: filteredSettings,
219
- messageCount: request.messages.length,
127
+ messageCount: resolvedRequest.messages.length,
220
128
  });
221
- console.log(`Processing LLM request: ${request.messages.length} messages, model: ${request.modelId}`);
222
129
  // Get client adapter
223
- const clientAdapter = this.getClientAdapter(request.providerId);
130
+ const clientAdapter = this.adapterRegistry.getAdapter(providerId);
224
131
  // Use ApiKeyProvider to get the API key and make the request
225
132
  try {
226
- const apiKey = await this.getApiKey(request.providerId);
133
+ const apiKey = await this.getApiKey(providerId);
227
134
  if (!apiKey) {
228
135
  return {
229
- provider: request.providerId,
230
- model: request.modelId,
136
+ provider: providerId,
137
+ model: modelId,
231
138
  error: {
232
- message: `API key for provider '${request.providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`,
139
+ message: `API key for provider '${providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`,
233
140
  code: "API_KEY_ERROR",
234
141
  type: "authentication_error",
235
142
  },
236
143
  object: "error",
237
144
  };
238
145
  }
239
- console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${request.providerId}`);
146
+ console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${providerId}`);
240
147
  const result = await clientAdapter.sendMessage(internalRequest, apiKey);
241
- console.log(`LLM request completed successfully for model: ${request.modelId}`);
148
+ // Post-process for thinking extraction
149
+ if (result.object === 'chat.completion' && internalRequest.settings.thinkingExtraction?.enabled) {
150
+ const settings = internalRequest.settings.thinkingExtraction;
151
+ const tagName = settings.tag || 'thinking';
152
+ // Step 1: Resolve the effective onMissing strategy
153
+ let effectiveOnMissing = settings.onMissing || 'auto';
154
+ if (effectiveOnMissing === 'auto') {
155
+ // Check if native reasoning is active
156
+ const isNativeReasoningActive = modelInfo.reasoning?.supported === true &&
157
+ (internalRequest.settings.reasoning?.enabled === true ||
158
+ modelInfo.reasoning?.enabledByDefault === true ||
159
+ modelInfo.reasoning?.canDisable === false); // Always-on models
160
+ effectiveOnMissing = isNativeReasoningActive ? 'ignore' : 'error';
161
+ }
162
+ // Step 2: Process the response
163
+ const choice = result.choices[0];
164
+ if (choice?.message?.content) {
165
+ const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
166
+ if (extracted !== null) {
167
+ console.log(`Extracted <${tagName}> block from response.`);
168
+ // Handle the edge case: append to existing reasoning if present.
169
+ const existingReasoning = choice.reasoning || '';
170
+ const separator = existingReasoning ? '\n\n' : '';
171
+ // Add a comment to indicate the source of this reasoning block.
172
+ const newReasoning = `<!-- Extracted by genai-lite from <${tagName}> tag -->\n${extracted}`;
173
+ // Update the choice object
174
+ choice.reasoning = `${existingReasoning}${separator}${newReasoning}`;
175
+ choice.message.content = remaining;
176
+ }
177
+ else {
178
+ // Tag was not found, enforce the effective strategy
179
+ if (effectiveOnMissing === 'error') {
180
+ return {
181
+ provider: providerId,
182
+ model: modelId,
183
+ error: {
184
+ message: `The model (${modelId}) response was expected to start with a <${tagName}> tag but it was not found. ` +
185
+ `This is enforced because the model does not have native reasoning active. ` +
186
+ `Either ensure your prompt instructs the model to use <${tagName}> tags, or enable native reasoning if supported.`,
187
+ code: "MISSING_EXPECTED_TAG",
188
+ type: "validation_error",
189
+ },
190
+ object: "error",
191
+ };
192
+ }
193
+ else if (effectiveOnMissing === 'warn') {
194
+ console.warn(`Expected <${tagName}> tag was not found in the response from model ${modelId}.`);
195
+ }
196
+ // If 'ignore', do nothing
197
+ }
198
+ }
199
+ }
200
+ console.log(`LLM request completed successfully for model: ${modelId}`);
242
201
  return result;
243
202
  }
244
203
  catch (error) {
245
204
  console.error("Error in LLMService.sendMessage:", error);
246
205
  return {
247
- provider: request.providerId,
248
- model: request.modelId,
206
+ provider: providerId,
207
+ model: modelId,
249
208
  error: {
250
209
  message: error instanceof Error
251
210
  ? error.message
@@ -261,8 +220,8 @@ class LLMService {
261
220
  catch (error) {
262
221
  console.error("Error in LLMService.sendMessage (outer):", error);
263
222
  return {
264
- provider: request.providerId,
265
- model: request.modelId,
223
+ provider: request.providerId || request.presetId || 'unknown',
224
+ model: request.modelId || request.presetId || 'unknown',
266
225
  error: {
267
226
  message: error instanceof Error
268
227
  ? error.message
@@ -276,121 +235,98 @@ class LLMService {
276
235
  }
277
236
  }
278
237
  /**
279
- * Validates basic LLM request structure
238
+ * Gets all configured model presets
280
239
  *
281
- * @param request - The request to validate
282
- * @returns LLMFailureResponse if validation fails, null if valid
240
+ * @returns Array of model presets
283
241
  */
284
- validateRequestStructure(request) {
285
- // Basic request structure validation
286
- if (!request.messages ||
287
- !Array.isArray(request.messages) ||
288
- request.messages.length === 0) {
289
- return {
290
- provider: request.providerId,
291
- model: request.modelId,
292
- error: {
293
- message: "Request must contain at least one message",
294
- code: "INVALID_REQUEST",
295
- type: "validation_error",
296
- },
297
- object: "error",
298
- };
299
- }
300
- // Validate message structure
301
- for (let i = 0; i < request.messages.length; i++) {
302
- const message = request.messages[i];
303
- if (!message.role || !message.content) {
304
- return {
305
- provider: request.providerId,
306
- model: request.modelId,
307
- error: {
308
- message: `Message at index ${i} must have both 'role' and 'content' properties`,
309
- code: "INVALID_MESSAGE",
310
- type: "validation_error",
311
- },
312
- object: "error",
313
- };
314
- }
315
- if (!["user", "assistant", "system"].includes(message.role)) {
316
- return {
317
- provider: request.providerId,
318
- model: request.modelId,
319
- error: {
320
- message: `Invalid message role '${message.role}' at index ${i}. Must be 'user', 'assistant', or 'system'`,
321
- code: "INVALID_MESSAGE_ROLE",
322
- type: "validation_error",
323
- },
324
- object: "error",
325
- };
326
- }
327
- }
328
- return null; // Request is valid
242
+ getPresets() {
243
+ return this.presetManager.getPresets();
329
244
  }
330
245
  /**
331
- * Merges request settings with model-specific and global defaults
246
+ * Creates messages from a template with role tags and model-aware variable substitution
332
247
  *
333
- * @param modelId - The model ID to get defaults for
334
- * @param providerId - The provider ID to get defaults for
335
- * @param requestSettings - Settings from the request
336
- * @returns Complete settings object with all required fields
337
- */
338
- mergeSettingsForModel(modelId, providerId, requestSettings) {
339
- // Get model-specific defaults
340
- const modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId);
341
- // Merge with user-provided settings (user settings take precedence)
342
- const mergedSettings = {
343
- temperature: requestSettings?.temperature ?? modelDefaults.temperature,
344
- maxTokens: requestSettings?.maxTokens ?? modelDefaults.maxTokens,
345
- topP: requestSettings?.topP ?? modelDefaults.topP,
346
- stopSequences: requestSettings?.stopSequences ?? modelDefaults.stopSequences,
347
- frequencyPenalty: requestSettings?.frequencyPenalty ?? modelDefaults.frequencyPenalty,
348
- presencePenalty: requestSettings?.presencePenalty ?? modelDefaults.presencePenalty,
349
- user: requestSettings?.user ?? modelDefaults.user,
350
- supportsSystemMessage: requestSettings?.supportsSystemMessage ??
351
- modelDefaults.supportsSystemMessage,
352
- geminiSafetySettings: requestSettings?.geminiSafetySettings ??
353
- modelDefaults.geminiSafetySettings,
354
- };
355
- // Log the final settings for debugging
356
- console.log(`Merged settings for ${providerId}/${modelId}:`, {
357
- temperature: mergedSettings.temperature,
358
- maxTokens: mergedSettings.maxTokens,
359
- topP: mergedSettings.topP,
360
- hasStopSequences: mergedSettings.stopSequences.length > 0,
361
- frequencyPenalty: mergedSettings.frequencyPenalty,
362
- presencePenalty: mergedSettings.presencePenalty,
363
- hasUser: !!mergedSettings.user,
364
- geminiSafetySettingsCount: mergedSettings.geminiSafetySettings.length,
365
- });
366
- return mergedSettings;
367
- }
368
- /**
369
- * Gets the appropriate client adapter for a provider
248
+ * This unified method combines the functionality of template rendering, model context
249
+ * injection, and role tag parsing into a single, intuitive API. It replaces the need
250
+ * to chain prepareMessage and buildMessagesFromTemplate for model-aware multi-turn prompts.
370
251
  *
371
- * @param providerId - The provider ID
372
- * @returns The client adapter to use
373
- */
374
- getClientAdapter(providerId) {
375
- // Check for registered real adapters first
376
- const registeredAdapter = this.clientAdapters.get(providerId);
377
- if (registeredAdapter) {
378
- console.log(`Using registered adapter for provider: ${providerId}`);
379
- return registeredAdapter;
380
- }
381
- // Fall back to mock adapter for unsupported providers
382
- console.log(`No real adapter found for ${providerId}, using mock adapter`);
383
- return this.mockClientAdapter;
384
- }
385
- /**
386
- * Registers a client adapter for a specific provider
252
+ * @param options Options for creating messages
253
+ * @returns Promise resolving to parsed messages and model context
387
254
  *
388
- * @param providerId - The provider ID
389
- * @param adapter - The client adapter implementation
255
+ * @example
256
+ * ```typescript
257
+ * const { messages } = await llm.createMessages({
258
+ * template: `
259
+ * <SYSTEM>You are a {{ thinking_enabled ? "thoughtful" : "helpful" }} assistant.</SYSTEM>
260
+ * <USER>Help me with {{ task }}</USER>
261
+ * `,
262
+ * variables: { task: 'understanding async/await' },
263
+ * presetId: 'openai-gpt-4.1-default'
264
+ * });
265
+ * ```
390
266
  */
391
- registerClientAdapter(providerId, adapter) {
392
- this.clientAdapters.set(providerId, adapter);
393
- console.log(`Registered client adapter for provider: ${providerId}`);
267
+ async createMessages(options) {
268
+ console.log('LLMService.createMessages called');
269
+ // NEW: Step 1 - Parse the template for metadata and content
270
+ const { metadata, content: templateContent } = (0, parser_1.parseTemplateWithMetadata)(options.template);
271
+ // Validate the settings from the template
272
+ const templateSettings = this.settingsManager.validateTemplateSettings(metadata.settings || {});
273
+ // Step 2: Get model context if model information is provided
274
+ let modelContext = null;
275
+ if (options.presetId || (options.providerId && options.modelId)) {
276
+ // Resolve model information
277
+ const resolved = this.modelResolver.resolve({
278
+ presetId: options.presetId,
279
+ providerId: options.providerId,
280
+ modelId: options.modelId
281
+ });
282
+ if (resolved.error) {
283
+ // If resolution fails, proceed without model context
284
+ console.warn('Model resolution failed, proceeding without model context:', resolved.error);
285
+ }
286
+ else {
287
+ const { providerId, modelId, modelInfo, settings } = resolved;
288
+ // Merge settings with model defaults
289
+ const mergedSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, settings || {});
290
+ // Create model context
291
+ modelContext = {
292
+ thinking_enabled: !!(modelInfo.reasoning?.supported &&
293
+ (mergedSettings.reasoning?.enabled === true ||
294
+ (modelInfo.reasoning?.enabledByDefault && mergedSettings.reasoning?.enabled !== false))),
295
+ thinking_available: !!modelInfo.reasoning?.supported,
296
+ model_id: modelId,
297
+ provider_id: providerId,
298
+ reasoning_effort: mergedSettings.reasoning?.effort,
299
+ reasoning_max_tokens: mergedSettings.reasoning?.maxTokens,
300
+ };
301
+ }
302
+ }
303
+ // Step 2: Combine variables with model context
304
+ // Model context comes first so user variables can override
305
+ const allVariables = {
306
+ ...(modelContext || {}),
307
+ ...options.variables
308
+ };
309
+ // Step 3: Render the template with all variables
310
+ let renderedTemplate;
311
+ try {
312
+ // Use templateContent which is the template without the <META> block
313
+ renderedTemplate = (0, template_1.renderTemplate)(templateContent, allVariables);
314
+ }
315
+ catch (error) {
316
+ throw new Error(`Template rendering failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
317
+ }
318
+ // Step 4: Parse role tags from the rendered template
319
+ const parsedMessages = (0, parser_1.parseRoleTags)(renderedTemplate);
320
+ // Step 5: Convert to LLMMessage format
321
+ const messages = parsedMessages.map(({ role, content }) => ({
322
+ role: role,
323
+ content
324
+ }));
325
+ return {
326
+ messages,
327
+ modelContext,
328
+ settings: templateSettings // NEW: Add the extracted settings
329
+ };
394
330
  }
395
331
  /**
396
332
  * Gets information about registered adapters
@@ -398,15 +334,7 @@ class LLMService {
398
334
  * @returns Map of provider IDs to adapter info
399
335
  */
400
336
  getRegisteredAdapters() {
401
- const adapterInfo = new Map();
402
- for (const [providerId, adapter] of this.clientAdapters.entries()) {
403
- adapterInfo.set(providerId, {
404
- providerId,
405
- hasAdapter: true,
406
- adapterInfo: adapter.getAdapterInfo?.() || { name: "Unknown Adapter" },
407
- });
408
- }
409
- return adapterInfo;
337
+ return this.adapterRegistry.getRegisteredAdapters();
410
338
  }
411
339
  /**
412
340
  * Gets a summary of available providers and their adapter status
@@ -414,30 +342,7 @@ class LLMService {
414
342
  * @returns Summary of provider availability
415
343
  */
416
344
  getProviderSummary() {
417
- const availableProviders = [];
418
- const unavailableProviders = [];
419
- for (const provider of config_1.SUPPORTED_PROVIDERS) {
420
- if (this.clientAdapters.has(provider.id)) {
421
- availableProviders.push(provider.id);
422
- }
423
- else {
424
- unavailableProviders.push(provider.id);
425
- }
426
- }
427
- return {
428
- totalProviders: config_1.SUPPORTED_PROVIDERS.length,
429
- providersWithAdapters: availableProviders.length,
430
- availableProviders,
431
- unavailableProviders,
432
- };
433
- }
434
- /**
435
- * Gets all configured model presets
436
- *
437
- * @returns Array of model presets
438
- */
439
- getPresets() {
440
- return [...this.presets]; // Return a copy to prevent external modification
345
+ return this.adapterRegistry.getProviderSummary();
441
346
  }
442
347
  }
443
348
  exports.LLMService = LLMService;