genai-lite 0.1.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.
@@ -0,0 +1,410 @@
1
+ "use strict";
2
+ // AI Summary: Main process service for LLM operations, integrating with ApiKeyProvider for secure key access.
3
+ // Orchestrates LLM requests through provider-specific client adapters with proper error handling.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.LLMService = void 0;
6
+ const MockClientAdapter_1 = require("./clients/MockClientAdapter");
7
+ const config_1 = require("./config");
8
+ /**
9
+ * Main process service for LLM operations
10
+ *
11
+ * This service:
12
+ * - Manages LLM provider client adapters
13
+ * - Integrates with ApiKeyServiceMain for secure API key access
14
+ * - Validates requests and applies default settings
15
+ * - Routes requests to appropriate provider adapters
16
+ * - Handles errors and provides standardized responses
17
+ */
18
+ class LLMService {
19
+ constructor(getApiKey) {
20
+ this.getApiKey = getApiKey;
21
+ this.clientAdapters = new Map();
22
+ this.mockClientAdapter = new MockClientAdapter_1.MockClientAdapter();
23
+ // Dynamically register client adapters based on configuration
24
+ let registeredCount = 0;
25
+ const successfullyRegisteredProviders = [];
26
+ for (const provider of config_1.SUPPORTED_PROVIDERS) {
27
+ const AdapterClass = config_1.ADAPTER_CONSTRUCTORS[provider.id];
28
+ if (AdapterClass) {
29
+ try {
30
+ const adapterConfig = config_1.ADAPTER_CONFIGS[provider.id];
31
+ const adapterInstance = new AdapterClass(adapterConfig);
32
+ this.registerClientAdapter(provider.id, adapterInstance);
33
+ registeredCount++;
34
+ successfullyRegisteredProviders.push(provider.id);
35
+ }
36
+ catch (error) {
37
+ console.error(`LLMService: Failed to instantiate adapter for provider '${provider.id}'. This provider will use the mock adapter. Error:`, error);
38
+ }
39
+ }
40
+ else {
41
+ console.warn(`LLMService: No adapter constructor found for supported provider '${provider.id}'. This provider will use the mock adapter as a fallback.`);
42
+ }
43
+ }
44
+ if (registeredCount > 0) {
45
+ console.log(`LLMService: Initialized with ${registeredCount} dynamically registered adapter(s) for: ${successfullyRegisteredProviders.join(", ")}.`);
46
+ }
47
+ else {
48
+ console.log(`LLMService: No real adapters were dynamically registered. All providers will use the mock adapter.`);
49
+ }
50
+ }
51
+ /**
52
+ * Gets list of supported LLM providers
53
+ *
54
+ * @returns Promise resolving to array of provider information
55
+ */
56
+ async getProviders() {
57
+ console.log("LLMService.getProviders called");
58
+ return [...config_1.SUPPORTED_PROVIDERS]; // Return a copy to prevent external modification
59
+ }
60
+ /**
61
+ * Gets list of supported models for a specific provider
62
+ *
63
+ * @param providerId - The provider ID to get models for
64
+ * @returns Promise resolving to array of model information
65
+ */
66
+ async getModels(providerId) {
67
+ console.log(`LLMService.getModels called for provider: ${providerId}`);
68
+ // Validate provider exists
69
+ if (!(0, config_1.isProviderSupported)(providerId)) {
70
+ console.warn(`Requested models for unsupported provider: ${providerId}`);
71
+ return [];
72
+ }
73
+ const models = (0, config_1.getModelsByProvider)(providerId);
74
+ console.log(`Found ${models.length} models for provider: ${providerId}`);
75
+ return [...models]; // Return a copy to prevent external modification
76
+ }
77
+ /**
78
+ * Sends a chat message to an LLM provider
79
+ *
80
+ * @param request - The LLM chat request
81
+ * @returns Promise resolving to either success or failure response
82
+ */
83
+ async sendMessage(request) {
84
+ console.log(`LLMService.sendMessage called for provider: ${request.providerId}, model: ${request.modelId}`);
85
+ try {
86
+ // Validate provider
87
+ if (!(0, config_1.isProviderSupported)(request.providerId)) {
88
+ console.warn(`Unsupported provider in sendMessage: ${request.providerId}`);
89
+ return {
90
+ provider: request.providerId,
91
+ model: request.modelId,
92
+ error: {
93
+ message: `Unsupported provider: ${request.providerId}. Supported providers: ${config_1.SUPPORTED_PROVIDERS.map((p) => p.id).join(", ")}`,
94
+ code: "UNSUPPORTED_PROVIDER",
95
+ type: "validation_error",
96
+ },
97
+ object: "error",
98
+ };
99
+ }
100
+ // Validate model
101
+ if (!(0, config_1.isModelSupported)(request.modelId, request.providerId)) {
102
+ const availableModels = (0, config_1.getModelsByProvider)(request.providerId).map((m) => m.id);
103
+ console.warn(`Unsupported model ${request.modelId} for provider ${request.providerId}. Available: ${availableModels.join(", ")}`);
104
+ return {
105
+ provider: request.providerId,
106
+ model: request.modelId,
107
+ error: {
108
+ message: `Unsupported model: ${request.modelId} for provider: ${request.providerId}. Available models: ${availableModels.join(", ")}`,
109
+ code: "UNSUPPORTED_MODEL",
110
+ type: "validation_error",
111
+ },
112
+ object: "error",
113
+ };
114
+ }
115
+ // Get model info for additional validation or settings
116
+ const modelInfo = (0, config_1.getModelById)(request.modelId, request.providerId);
117
+ if (!modelInfo) {
118
+ // This shouldn't happen if validation above passed, but defensive programming
119
+ console.error(`Model info not found for validated model: ${request.modelId}`);
120
+ return {
121
+ provider: request.providerId,
122
+ model: request.modelId,
123
+ error: {
124
+ message: `Internal error: Model configuration not found for ${request.modelId}`,
125
+ code: "MODEL_CONFIG_ERROR",
126
+ type: "internal_error",
127
+ },
128
+ object: "error",
129
+ };
130
+ }
131
+ // Validate basic request structure
132
+ const structureValidationResult = this.validateRequestStructure(request);
133
+ if (structureValidationResult) {
134
+ return structureValidationResult;
135
+ }
136
+ // Validate settings if provided
137
+ if (request.settings) {
138
+ const settingsValidationErrors = (0, config_1.validateLLMSettings)(request.settings);
139
+ if (settingsValidationErrors.length > 0) {
140
+ return {
141
+ provider: request.providerId,
142
+ model: request.modelId,
143
+ error: {
144
+ message: `Invalid settings: ${settingsValidationErrors.join(", ")}`,
145
+ code: "INVALID_SETTINGS",
146
+ type: "validation_error",
147
+ },
148
+ object: "error",
149
+ };
150
+ }
151
+ }
152
+ // Apply model-specific defaults and merge with user settings
153
+ const finalSettings = this.mergeSettingsForModel(request.modelId, request.providerId, request.settings);
154
+ // Filter out unsupported parameters based on model and provider configuration
155
+ let filteredSettings = { ...finalSettings }; // Create a mutable copy
156
+ // Get provider info for parameter filtering (modelInfo is already available from earlier validation)
157
+ const providerInfo = (0, config_1.getProviderById)(request.providerId);
158
+ const paramsToExclude = new Set();
159
+ // Add provider-level exclusions
160
+ if (providerInfo?.unsupportedParameters) {
161
+ providerInfo.unsupportedParameters.forEach((param) => paramsToExclude.add(param));
162
+ }
163
+ // Add model-level exclusions (these will be added to any provider-level ones)
164
+ if (modelInfo?.unsupportedParameters) {
165
+ modelInfo.unsupportedParameters.forEach((param) => paramsToExclude.add(param));
166
+ }
167
+ if (paramsToExclude.size > 0) {
168
+ console.log(`LLMService: Potential parameters to exclude for provider '${request.providerId}', model '${request.modelId}':`, Array.from(paramsToExclude));
169
+ }
170
+ paramsToExclude.forEach((param) => {
171
+ // Check if the parameter key actually exists in filteredSettings before trying to delete
172
+ // (it might have been undefined initially and thus not part of finalSettings depending on merge logic)
173
+ // Using 'in' operator is robust for checking presence of properties, including those from prototype chain.
174
+ // For direct properties of an object, hasOwnProperty is more specific.
175
+ // Given finalSettings is Required<LLMSettings>, all keys should be present, potentially as undefined.
176
+ if (param in filteredSettings) {
177
+ console.log(`LLMService: Removing excluded parameter '${String(param)}' for provider '${request.providerId}', model '${request.modelId}'. Value was:`, filteredSettings[param]);
178
+ delete filteredSettings[param]; // Cast to allow deletion
179
+ }
180
+ else {
181
+ // This case should ideally not happen if finalSettings truly is Required<LLMSettings>
182
+ // and mergeSettingsForModel ensures all keys are present (even if undefined).
183
+ console.log(`LLMService: Parameter '${String(param)}' marked for exclusion was not found in settings for provider '${request.providerId}', model '${request.modelId}'.`);
184
+ }
185
+ });
186
+ const internalRequest = {
187
+ ...request,
188
+ settings: filteredSettings,
189
+ };
190
+ console.log(`Processing LLM request with (potentially filtered) settings:`, {
191
+ provider: request.providerId,
192
+ model: request.modelId,
193
+ settings: filteredSettings,
194
+ messageCount: request.messages.length,
195
+ });
196
+ console.log(`Processing LLM request: ${request.messages.length} messages, model: ${request.modelId}`);
197
+ // Get client adapter
198
+ const clientAdapter = this.getClientAdapter(request.providerId);
199
+ // Use ApiKeyProvider to get the API key and make the request
200
+ try {
201
+ const apiKey = await this.getApiKey(request.providerId);
202
+ if (!apiKey) {
203
+ return {
204
+ provider: request.providerId,
205
+ model: request.modelId,
206
+ error: {
207
+ message: `API key for provider '${request.providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`,
208
+ code: "API_KEY_ERROR",
209
+ type: "authentication_error",
210
+ },
211
+ object: "error",
212
+ };
213
+ }
214
+ console.log(`Making LLM request with ${clientAdapter.constructor.name} for provider: ${request.providerId}`);
215
+ const result = await clientAdapter.sendMessage(internalRequest, apiKey);
216
+ console.log(`LLM request completed successfully for model: ${request.modelId}`);
217
+ return result;
218
+ }
219
+ catch (error) {
220
+ console.error("Error in LLMService.sendMessage:", error);
221
+ return {
222
+ provider: request.providerId,
223
+ model: request.modelId,
224
+ error: {
225
+ message: error instanceof Error
226
+ ? error.message
227
+ : "An unknown error occurred during message sending.",
228
+ code: "PROVIDER_ERROR",
229
+ type: "server_error",
230
+ providerError: error,
231
+ },
232
+ object: "error",
233
+ };
234
+ }
235
+ }
236
+ catch (error) {
237
+ console.error("Error in LLMService.sendMessage (outer):", error);
238
+ return {
239
+ provider: request.providerId,
240
+ model: request.modelId,
241
+ error: {
242
+ message: error instanceof Error
243
+ ? error.message
244
+ : "An unknown error occurred.",
245
+ code: "UNEXPECTED_ERROR",
246
+ type: "server_error",
247
+ providerError: error,
248
+ },
249
+ object: "error",
250
+ };
251
+ }
252
+ }
253
+ /**
254
+ * Validates basic LLM request structure
255
+ *
256
+ * @param request - The request to validate
257
+ * @returns LLMFailureResponse if validation fails, null if valid
258
+ */
259
+ validateRequestStructure(request) {
260
+ // Basic request structure validation
261
+ if (!request.messages ||
262
+ !Array.isArray(request.messages) ||
263
+ request.messages.length === 0) {
264
+ return {
265
+ provider: request.providerId,
266
+ model: request.modelId,
267
+ error: {
268
+ message: "Request must contain at least one message",
269
+ code: "INVALID_REQUEST",
270
+ type: "validation_error",
271
+ },
272
+ object: "error",
273
+ };
274
+ }
275
+ // Validate message structure
276
+ for (let i = 0; i < request.messages.length; i++) {
277
+ const message = request.messages[i];
278
+ if (!message.role || !message.content) {
279
+ return {
280
+ provider: request.providerId,
281
+ model: request.modelId,
282
+ error: {
283
+ message: `Message at index ${i} must have both 'role' and 'content' properties`,
284
+ code: "INVALID_MESSAGE",
285
+ type: "validation_error",
286
+ },
287
+ object: "error",
288
+ };
289
+ }
290
+ if (!["user", "assistant", "system"].includes(message.role)) {
291
+ return {
292
+ provider: request.providerId,
293
+ model: request.modelId,
294
+ error: {
295
+ message: `Invalid message role '${message.role}' at index ${i}. Must be 'user', 'assistant', or 'system'`,
296
+ code: "INVALID_MESSAGE_ROLE",
297
+ type: "validation_error",
298
+ },
299
+ object: "error",
300
+ };
301
+ }
302
+ }
303
+ return null; // Request is valid
304
+ }
305
+ /**
306
+ * Merges request settings with model-specific and global defaults
307
+ *
308
+ * @param modelId - The model ID to get defaults for
309
+ * @param providerId - The provider ID to get defaults for
310
+ * @param requestSettings - Settings from the request
311
+ * @returns Complete settings object with all required fields
312
+ */
313
+ mergeSettingsForModel(modelId, providerId, requestSettings) {
314
+ // Get model-specific defaults
315
+ const modelDefaults = (0, config_1.getDefaultSettingsForModel)(modelId, providerId);
316
+ // Merge with user-provided settings (user settings take precedence)
317
+ const mergedSettings = {
318
+ temperature: requestSettings?.temperature ?? modelDefaults.temperature,
319
+ maxTokens: requestSettings?.maxTokens ?? modelDefaults.maxTokens,
320
+ topP: requestSettings?.topP ?? modelDefaults.topP,
321
+ stopSequences: requestSettings?.stopSequences ?? modelDefaults.stopSequences,
322
+ frequencyPenalty: requestSettings?.frequencyPenalty ?? modelDefaults.frequencyPenalty,
323
+ presencePenalty: requestSettings?.presencePenalty ?? modelDefaults.presencePenalty,
324
+ user: requestSettings?.user ?? modelDefaults.user,
325
+ supportsSystemMessage: requestSettings?.supportsSystemMessage ??
326
+ modelDefaults.supportsSystemMessage,
327
+ geminiSafetySettings: requestSettings?.geminiSafetySettings ??
328
+ modelDefaults.geminiSafetySettings,
329
+ };
330
+ // Log the final settings for debugging
331
+ console.log(`Merged settings for ${providerId}/${modelId}:`, {
332
+ temperature: mergedSettings.temperature,
333
+ maxTokens: mergedSettings.maxTokens,
334
+ topP: mergedSettings.topP,
335
+ hasStopSequences: mergedSettings.stopSequences.length > 0,
336
+ frequencyPenalty: mergedSettings.frequencyPenalty,
337
+ presencePenalty: mergedSettings.presencePenalty,
338
+ hasUser: !!mergedSettings.user,
339
+ geminiSafetySettingsCount: mergedSettings.geminiSafetySettings.length,
340
+ });
341
+ return mergedSettings;
342
+ }
343
+ /**
344
+ * Gets the appropriate client adapter for a provider
345
+ *
346
+ * @param providerId - The provider ID
347
+ * @returns The client adapter to use
348
+ */
349
+ getClientAdapter(providerId) {
350
+ // Check for registered real adapters first
351
+ const registeredAdapter = this.clientAdapters.get(providerId);
352
+ if (registeredAdapter) {
353
+ console.log(`Using registered adapter for provider: ${providerId}`);
354
+ return registeredAdapter;
355
+ }
356
+ // Fall back to mock adapter for unsupported providers
357
+ console.log(`No real adapter found for ${providerId}, using mock adapter`);
358
+ return this.mockClientAdapter;
359
+ }
360
+ /**
361
+ * Registers a client adapter for a specific provider
362
+ *
363
+ * @param providerId - The provider ID
364
+ * @param adapter - The client adapter implementation
365
+ */
366
+ registerClientAdapter(providerId, adapter) {
367
+ this.clientAdapters.set(providerId, adapter);
368
+ console.log(`Registered client adapter for provider: ${providerId}`);
369
+ }
370
+ /**
371
+ * Gets information about registered adapters
372
+ *
373
+ * @returns Map of provider IDs to adapter info
374
+ */
375
+ getRegisteredAdapters() {
376
+ const adapterInfo = new Map();
377
+ for (const [providerId, adapter] of this.clientAdapters.entries()) {
378
+ adapterInfo.set(providerId, {
379
+ providerId,
380
+ hasAdapter: true,
381
+ adapterInfo: adapter.getAdapterInfo?.() || { name: "Unknown Adapter" },
382
+ });
383
+ }
384
+ return adapterInfo;
385
+ }
386
+ /**
387
+ * Gets a summary of available providers and their adapter status
388
+ *
389
+ * @returns Summary of provider availability
390
+ */
391
+ getProviderSummary() {
392
+ const availableProviders = [];
393
+ const unavailableProviders = [];
394
+ for (const provider of config_1.SUPPORTED_PROVIDERS) {
395
+ if (this.clientAdapters.has(provider.id)) {
396
+ availableProviders.push(provider.id);
397
+ }
398
+ else {
399
+ unavailableProviders.push(provider.id);
400
+ }
401
+ }
402
+ return {
403
+ totalProviders: config_1.SUPPORTED_PROVIDERS.length,
404
+ providersWithAdapters: availableProviders.length,
405
+ availableProviders,
406
+ unavailableProviders,
407
+ };
408
+ }
409
+ }
410
+ exports.LLMService = LLMService;
@@ -0,0 +1,84 @@
1
+ import type { LLMResponse, LLMFailureResponse } from "../types";
2
+ import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
3
+ /**
4
+ * Client adapter for Anthropic API integration
5
+ *
6
+ * This adapter:
7
+ * - Formats requests according to Anthropic's messages API requirements
8
+ * - Handles Claude-specific system message positioning and formatting
9
+ * - Maps Anthropic responses to standardized LLMResponse format
10
+ * - Converts Anthropic errors to standardized LLMFailureResponse format
11
+ * - Manages Claude-specific settings and constraints
12
+ */
13
+ export declare class AnthropicClientAdapter implements ILLMClientAdapter {
14
+ private baseURL?;
15
+ /**
16
+ * Creates a new Anthropic client adapter
17
+ *
18
+ * @param config Optional configuration for the adapter
19
+ * @param config.baseURL Custom base URL for Anthropic-compatible APIs
20
+ */
21
+ constructor(config?: {
22
+ baseURL?: string;
23
+ });
24
+ /**
25
+ * Sends a chat message to Anthropic's API
26
+ *
27
+ * @param request - The internal LLM request with applied settings
28
+ * @param apiKey - The decrypted Anthropic API key
29
+ * @returns Promise resolving to success or failure response
30
+ */
31
+ sendMessage(request: InternalLLMChatRequest, apiKey: string): Promise<LLMResponse | LLMFailureResponse>;
32
+ /**
33
+ * Validates Anthropic API key format
34
+ *
35
+ * @param apiKey - The API key to validate
36
+ * @returns True if the key format appears valid
37
+ */
38
+ validateApiKey(apiKey: string): boolean;
39
+ /**
40
+ * Gets adapter information
41
+ */
42
+ getAdapterInfo(): {
43
+ providerId: "anthropic";
44
+ name: string;
45
+ version: string;
46
+ };
47
+ /**
48
+ * Formats messages for Anthropic API with proper system message handling
49
+ *
50
+ * @param request - The internal LLM request
51
+ * @returns Formatted messages and system message for Anthropic
52
+ */
53
+ private formatMessagesForAnthropic;
54
+ /**
55
+ * Ensures messages alternate between user and assistant roles as required by Anthropic
56
+ *
57
+ * @param messages - Original messages array
58
+ * @returns Cleaned messages with proper alternating pattern
59
+ */
60
+ private ensureAlternatingRoles;
61
+ /**
62
+ * Creates a standardized success response from Anthropic's response
63
+ *
64
+ * @param completion - Raw Anthropic completion response
65
+ * @param request - Original request for context
66
+ * @returns Standardized LLM response
67
+ */
68
+ private createSuccessResponse;
69
+ /**
70
+ * Maps Anthropic stop reasons to standardized format
71
+ *
72
+ * @param anthropicReason - The stop reason from Anthropic
73
+ * @returns Standardized finish reason
74
+ */
75
+ private mapAnthropicStopReason;
76
+ /**
77
+ * Creates a standardized error response from Anthropic errors
78
+ *
79
+ * @param error - The error from Anthropic API
80
+ * @param request - Original request for context
81
+ * @returns Standardized LLM failure response
82
+ */
83
+ private createErrorResponse;
84
+ }