genai-lite 0.2.1 → 0.3.1
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.
- package/README.md +382 -49
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/llm/LLMService.createMessages.test.d.ts +4 -0
- package/dist/llm/LLMService.createMessages.test.js +364 -0
- package/dist/llm/LLMService.d.ts +48 -83
- package/dist/llm/LLMService.js +176 -480
- package/dist/llm/LLMService.original.d.ts +147 -0
- package/dist/llm/LLMService.original.js +656 -0
- package/dist/llm/LLMService.test.js +187 -0
- package/dist/llm/clients/AnthropicClientAdapter.test.js +4 -0
- package/dist/llm/clients/GeminiClientAdapter.test.js +4 -0
- package/dist/llm/clients/MockClientAdapter.js +29 -13
- package/dist/llm/clients/MockClientAdapter.test.js +4 -0
- package/dist/llm/clients/OpenAIClientAdapter.test.js +4 -0
- package/dist/llm/config.js +5 -0
- package/dist/llm/services/AdapterRegistry.d.ts +59 -0
- package/dist/llm/services/AdapterRegistry.js +113 -0
- package/dist/llm/services/AdapterRegistry.test.d.ts +1 -0
- package/dist/llm/services/AdapterRegistry.test.js +239 -0
- package/dist/llm/services/ModelResolver.d.ts +35 -0
- package/dist/llm/services/ModelResolver.js +116 -0
- package/dist/llm/services/ModelResolver.test.d.ts +1 -0
- package/dist/llm/services/ModelResolver.test.js +158 -0
- package/dist/llm/services/PresetManager.d.ts +27 -0
- package/dist/llm/services/PresetManager.js +50 -0
- package/dist/llm/services/PresetManager.test.d.ts +1 -0
- package/dist/llm/services/PresetManager.test.js +210 -0
- package/dist/llm/services/RequestValidator.d.ts +31 -0
- package/dist/llm/services/RequestValidator.js +122 -0
- package/dist/llm/services/RequestValidator.test.d.ts +1 -0
- package/dist/llm/services/RequestValidator.test.js +159 -0
- package/dist/llm/services/SettingsManager.d.ts +32 -0
- package/dist/llm/services/SettingsManager.js +223 -0
- package/dist/llm/services/SettingsManager.test.d.ts +1 -0
- package/dist/llm/services/SettingsManager.test.js +266 -0
- package/dist/llm/types.d.ts +29 -28
- package/dist/prompting/builder.d.ts +4 -0
- package/dist/prompting/builder.js +12 -61
- package/dist/prompting/content.js +3 -9
- package/dist/prompting/index.d.ts +2 -3
- package/dist/prompting/index.js +4 -5
- package/dist/prompting/parser.d.ts +80 -0
- package/dist/prompting/parser.js +133 -0
- package/dist/prompting/parser.test.js +348 -0
- package/dist/prompting/template.d.ts +8 -0
- package/dist/prompting/template.js +89 -6
- package/dist/prompting/template.test.js +116 -0
- package/package.json +1 -1
|
@@ -359,4 +359,191 @@ describe('LLMService', () => {
|
|
|
359
359
|
expect(gpt4.maxTokens).toBeGreaterThan(0);
|
|
360
360
|
});
|
|
361
361
|
});
|
|
362
|
+
describe('thinking extraction', () => {
|
|
363
|
+
it('should extract thinking tag from response when enabled', async () => {
|
|
364
|
+
// Use mistral provider which doesn't have an adapter, so MockClientAdapter will be used
|
|
365
|
+
const request = {
|
|
366
|
+
providerId: 'mistral',
|
|
367
|
+
modelId: 'codestral-2501',
|
|
368
|
+
messages: [{ role: 'user', content: 'test_thinking:<thinking>I am thinking about this problem.</thinking>Here is the answer.' }],
|
|
369
|
+
settings: {
|
|
370
|
+
thinkingExtraction: {
|
|
371
|
+
enabled: true,
|
|
372
|
+
tag: 'thinking'
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const response = await service.sendMessage(request);
|
|
377
|
+
expect(response.object).toBe('chat.completion');
|
|
378
|
+
const successResponse = response;
|
|
379
|
+
expect(successResponse.choices[0].reasoning).toBe('I am thinking about this problem.');
|
|
380
|
+
expect(successResponse.choices[0].message.content).toBe('Here is the answer.');
|
|
381
|
+
});
|
|
382
|
+
it('should not extract thinking tag when disabled', async () => {
|
|
383
|
+
const request = {
|
|
384
|
+
providerId: 'mistral',
|
|
385
|
+
modelId: 'codestral-2501',
|
|
386
|
+
messages: [{ role: 'user', content: 'test_thinking:<thinking>I am thinking about this problem.</thinking>Here is the answer.' }],
|
|
387
|
+
settings: {
|
|
388
|
+
thinkingExtraction: {
|
|
389
|
+
enabled: false,
|
|
390
|
+
tag: 'thinking'
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const response = await service.sendMessage(request);
|
|
395
|
+
expect(response.object).toBe('chat.completion');
|
|
396
|
+
const successResponse = response;
|
|
397
|
+
expect(successResponse.choices[0].reasoning).toBeUndefined();
|
|
398
|
+
expect(successResponse.choices[0].message.content).toBe('<thinking>I am thinking about this problem.</thinking>Here is the answer.');
|
|
399
|
+
});
|
|
400
|
+
it('should use custom tag name', async () => {
|
|
401
|
+
const request = {
|
|
402
|
+
providerId: 'mistral',
|
|
403
|
+
modelId: 'codestral-2501',
|
|
404
|
+
messages: [{ role: 'user', content: 'test_thinking:<scratchpad>Working through the logic...</scratchpad>Final answer is 42.' }],
|
|
405
|
+
settings: {
|
|
406
|
+
thinkingExtraction: {
|
|
407
|
+
enabled: true,
|
|
408
|
+
tag: 'scratchpad'
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
const response = await service.sendMessage(request);
|
|
413
|
+
expect(response.object).toBe('chat.completion');
|
|
414
|
+
const successResponse = response;
|
|
415
|
+
expect(successResponse.choices[0].reasoning).toBe('Working through the logic...');
|
|
416
|
+
expect(successResponse.choices[0].message.content).toBe('Final answer is 42.');
|
|
417
|
+
});
|
|
418
|
+
it('should append to existing reasoning', async () => {
|
|
419
|
+
// Use test_reasoning to get a response with existing reasoning, then test extraction appends to it
|
|
420
|
+
const request = {
|
|
421
|
+
providerId: 'mistral',
|
|
422
|
+
modelId: 'codestral-2501',
|
|
423
|
+
messages: [{ role: 'user', content: 'test_reasoning:<thinking>Additional thoughts here.</thinking>The analysis is complete.' }],
|
|
424
|
+
settings: {
|
|
425
|
+
thinkingExtraction: {
|
|
426
|
+
enabled: true,
|
|
427
|
+
tag: 'thinking'
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const response = await service.sendMessage(request);
|
|
432
|
+
expect(response.object).toBe('chat.completion');
|
|
433
|
+
const successResponse = response;
|
|
434
|
+
// Should contain both the initial reasoning and the extracted thinking with separator
|
|
435
|
+
expect(successResponse.choices[0].reasoning).toBe('Initial model reasoning from native capabilities.\n\n#### Additional Reasoning\n\nAdditional thoughts here.');
|
|
436
|
+
expect(successResponse.choices[0].message.content).toBe('The analysis is complete.');
|
|
437
|
+
});
|
|
438
|
+
it('should handle missing tag with explicit ignore', async () => {
|
|
439
|
+
const request = {
|
|
440
|
+
providerId: 'mistral',
|
|
441
|
+
modelId: 'codestral-2501',
|
|
442
|
+
messages: [{ role: 'user', content: 'test_thinking:This response has no thinking tag.' }],
|
|
443
|
+
settings: {
|
|
444
|
+
thinkingExtraction: {
|
|
445
|
+
enabled: true,
|
|
446
|
+
tag: 'thinking',
|
|
447
|
+
onMissing: 'ignore' // Explicitly set to ignore
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const response = await service.sendMessage(request);
|
|
452
|
+
expect(response.object).toBe('chat.completion');
|
|
453
|
+
const successResponse = response;
|
|
454
|
+
expect(successResponse.choices[0].reasoning).toBeUndefined();
|
|
455
|
+
expect(successResponse.choices[0].message.content).toBe('This response has no thinking tag.');
|
|
456
|
+
});
|
|
457
|
+
it('should use default settings when not specified', async () => {
|
|
458
|
+
// Default is now disabled, needs explicit opt-in
|
|
459
|
+
const request = {
|
|
460
|
+
providerId: 'mistral',
|
|
461
|
+
modelId: 'codestral-2501',
|
|
462
|
+
messages: [{ role: 'user', content: 'test_thinking:<thinking>Default extraction test.</thinking>Result here.' }]
|
|
463
|
+
};
|
|
464
|
+
const response = await service.sendMessage(request);
|
|
465
|
+
expect(response.object).toBe('chat.completion');
|
|
466
|
+
const successResponse = response;
|
|
467
|
+
// With default settings (enabled: false), no extraction should occur
|
|
468
|
+
expect(successResponse.choices[0].reasoning).toBeUndefined();
|
|
469
|
+
expect(successResponse.choices[0].message.content).toBe('<thinking>Default extraction test.</thinking>Result here.');
|
|
470
|
+
});
|
|
471
|
+
describe('onMissing behavior', () => {
|
|
472
|
+
it('should use auto mode by default with error for non-native models', async () => {
|
|
473
|
+
const request = {
|
|
474
|
+
providerId: 'mistral',
|
|
475
|
+
modelId: 'codestral-2501', // Non-native reasoning model (using mock)
|
|
476
|
+
messages: [{ role: 'user', content: 'test_thinking:Response without thinking tag.' }],
|
|
477
|
+
settings: {
|
|
478
|
+
thinkingExtraction: {
|
|
479
|
+
enabled: true,
|
|
480
|
+
// onMissing defaults to 'auto'
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
const response = await service.sendMessage(request);
|
|
485
|
+
expect(response.object).toBe('error');
|
|
486
|
+
const errorResponse = response;
|
|
487
|
+
expect(errorResponse.error.code).toBe('MISSING_EXPECTED_TAG');
|
|
488
|
+
expect(errorResponse.error.type).toBe('validation_error');
|
|
489
|
+
expect(errorResponse.error.message).toContain('response was expected to start with a <thinking> tag');
|
|
490
|
+
expect(errorResponse.error.message).toContain('does not have native reasoning active');
|
|
491
|
+
});
|
|
492
|
+
it('should handle missing tag for non-reasoning model with warn', async () => {
|
|
493
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
494
|
+
const request = {
|
|
495
|
+
providerId: 'mistral',
|
|
496
|
+
modelId: 'codestral-2501',
|
|
497
|
+
messages: [{ role: 'user', content: 'test_thinking:Response without thinking tag.' }],
|
|
498
|
+
settings: {
|
|
499
|
+
thinkingExtraction: {
|
|
500
|
+
enabled: true,
|
|
501
|
+
onMissing: 'warn'
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const response = await service.sendMessage(request);
|
|
506
|
+
expect(response.object).toBe('chat.completion');
|
|
507
|
+
const successResponse = response;
|
|
508
|
+
expect(successResponse.choices[0].message.content).toBe('Response without thinking tag.');
|
|
509
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Expected <thinking> tag was not found'));
|
|
510
|
+
consoleSpy.mockRestore();
|
|
511
|
+
});
|
|
512
|
+
it('should handle missing tag for non-reasoning model with ignore', async () => {
|
|
513
|
+
const request = {
|
|
514
|
+
providerId: 'mistral',
|
|
515
|
+
modelId: 'codestral-2501',
|
|
516
|
+
messages: [{ role: 'user', content: 'test_thinking:Response without thinking tag.' }],
|
|
517
|
+
settings: {
|
|
518
|
+
thinkingExtraction: {
|
|
519
|
+
enabled: true,
|
|
520
|
+
onMissing: 'ignore'
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const response = await service.sendMessage(request);
|
|
525
|
+
expect(response.object).toBe('chat.completion');
|
|
526
|
+
const successResponse = response;
|
|
527
|
+
expect(successResponse.choices[0].message.content).toBe('Response without thinking tag.');
|
|
528
|
+
});
|
|
529
|
+
it('should work with custom tag names in error messages', async () => {
|
|
530
|
+
const request = {
|
|
531
|
+
providerId: 'mistral',
|
|
532
|
+
modelId: 'codestral-2501',
|
|
533
|
+
messages: [{ role: 'user', content: 'test_thinking:Response without custom tag.' }],
|
|
534
|
+
settings: {
|
|
535
|
+
thinkingExtraction: {
|
|
536
|
+
enabled: true,
|
|
537
|
+
tag: 'reasoning',
|
|
538
|
+
onMissing: 'error'
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const response = await service.sendMessage(request);
|
|
543
|
+
expect(response.object).toBe('error');
|
|
544
|
+
const errorResponse = response;
|
|
545
|
+
expect(errorResponse.error.message).toContain('expected to start with a <reasoning> tag');
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
});
|
|
362
549
|
});
|
|
@@ -31,7 +31,8 @@ class MockClientAdapter {
|
|
|
31
31
|
try {
|
|
32
32
|
// Check for special test patterns in the last user message
|
|
33
33
|
const lastMessage = request.messages[request.messages.length - 1];
|
|
34
|
-
const
|
|
34
|
+
const originalContent = lastMessage?.content || "";
|
|
35
|
+
const content = originalContent.toLowerCase();
|
|
35
36
|
// Simulate various error conditions based on message content
|
|
36
37
|
if (content.includes("error_invalid_key")) {
|
|
37
38
|
return this.createErrorResponse("Invalid API key provided", types_1.ADAPTER_ERROR_CODES.INVALID_API_KEY, 401, request);
|
|
@@ -58,7 +59,7 @@ class MockClientAdapter {
|
|
|
58
59
|
return this.createErrorResponse("Generic provider error", types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, 500, request);
|
|
59
60
|
}
|
|
60
61
|
// Generate successful mock response
|
|
61
|
-
return this.createSuccessResponse(request, content);
|
|
62
|
+
return this.createSuccessResponse(request, content, originalContent);
|
|
62
63
|
}
|
|
63
64
|
catch (error) {
|
|
64
65
|
return this.createErrorResponse(`Mock adapter error: ${error instanceof Error ? error.message : "Unknown error"}`, types_1.ADAPTER_ERROR_CODES.UNKNOWN_ERROR, 500, request);
|
|
@@ -84,7 +85,7 @@ class MockClientAdapter {
|
|
|
84
85
|
/**
|
|
85
86
|
* Creates a successful mock response
|
|
86
87
|
*/
|
|
87
|
-
createSuccessResponse(request, userContent) {
|
|
88
|
+
createSuccessResponse(request, userContent, originalContent) {
|
|
88
89
|
// Generate response content based on user input and settings
|
|
89
90
|
let responseContent;
|
|
90
91
|
// Check for settings-based test patterns
|
|
@@ -94,6 +95,16 @@ class MockClientAdapter {
|
|
|
94
95
|
else if (userContent.includes("test_settings")) {
|
|
95
96
|
responseContent = this.generateSettingsTestResponse(request.settings);
|
|
96
97
|
}
|
|
98
|
+
else if (userContent.includes("test_thinking:")) {
|
|
99
|
+
// Extract content after "test_thinking:" for testing thinking extraction
|
|
100
|
+
const startIndex = originalContent.indexOf("test_thinking:") + "test_thinking:".length;
|
|
101
|
+
responseContent = originalContent.substring(startIndex).trim();
|
|
102
|
+
}
|
|
103
|
+
else if (userContent.includes("test_reasoning:")) {
|
|
104
|
+
// Extract content after "test_reasoning:" and return it as both content and reasoning
|
|
105
|
+
const startIndex = originalContent.indexOf("test_reasoning:") + "test_reasoning:".length;
|
|
106
|
+
responseContent = originalContent.substring(startIndex).trim();
|
|
107
|
+
}
|
|
97
108
|
else if (userContent.includes("hello") || userContent.includes("hi")) {
|
|
98
109
|
responseContent =
|
|
99
110
|
"Hello! I'm a mock LLM assistant. How can I help you today?";
|
|
@@ -147,21 +158,26 @@ class MockClientAdapter {
|
|
|
147
158
|
else if (request.settings.stopSequences.some((seq) => responseContent.includes(seq))) {
|
|
148
159
|
finishReason = "stop";
|
|
149
160
|
}
|
|
161
|
+
// Check if we need to add reasoning to the response
|
|
162
|
+
const isReasoningTest = userContent.includes("test_reasoning:");
|
|
163
|
+
const choice = {
|
|
164
|
+
message: {
|
|
165
|
+
role: "assistant",
|
|
166
|
+
content: responseContent,
|
|
167
|
+
},
|
|
168
|
+
finish_reason: finishReason,
|
|
169
|
+
index: 0,
|
|
170
|
+
};
|
|
171
|
+
// Add reasoning field for test_reasoning pattern
|
|
172
|
+
if (isReasoningTest) {
|
|
173
|
+
choice.reasoning = "Initial model reasoning from native capabilities.";
|
|
174
|
+
}
|
|
150
175
|
return {
|
|
151
176
|
id: `mock-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
152
177
|
provider: request.providerId,
|
|
153
178
|
model: request.modelId,
|
|
154
179
|
created: Math.floor(Date.now() / 1000),
|
|
155
|
-
choices: [
|
|
156
|
-
{
|
|
157
|
-
message: {
|
|
158
|
-
role: "assistant",
|
|
159
|
-
content: responseContent,
|
|
160
|
-
},
|
|
161
|
-
finish_reason: finishReason,
|
|
162
|
-
index: 0,
|
|
163
|
-
},
|
|
164
|
-
],
|
|
180
|
+
choices: [choice],
|
|
165
181
|
usage: {
|
|
166
182
|
prompt_tokens: promptTokenCount,
|
|
167
183
|
completion_tokens: mockTokenCount,
|
package/dist/llm/config.js
CHANGED
|
@@ -63,6 +63,11 @@ exports.DEFAULT_LLM_SETTINGS = {
|
|
|
63
63
|
maxTokens: undefined,
|
|
64
64
|
exclude: false,
|
|
65
65
|
},
|
|
66
|
+
thinkingExtraction: {
|
|
67
|
+
enabled: false, // Now requires explicit opt-in, works with onMissing: 'auto'
|
|
68
|
+
tag: 'thinking',
|
|
69
|
+
onMissing: 'auto' // Smart enforcement based on native reasoning status
|
|
70
|
+
},
|
|
66
71
|
};
|
|
67
72
|
/**
|
|
68
73
|
* Per-provider default setting overrides
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ApiProviderId } from "../types";
|
|
2
|
+
import type { ILLMClientAdapter } from "../clients/types";
|
|
3
|
+
/**
|
|
4
|
+
* Information about a registered adapter
|
|
5
|
+
*/
|
|
6
|
+
export interface AdapterInfo {
|
|
7
|
+
providerId: ApiProviderId;
|
|
8
|
+
hasAdapter: boolean;
|
|
9
|
+
adapterInfo: {
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Summary of provider availability
|
|
15
|
+
*/
|
|
16
|
+
export interface ProviderSummary {
|
|
17
|
+
totalProviders: number;
|
|
18
|
+
providersWithAdapters: number;
|
|
19
|
+
availableProviders: string[];
|
|
20
|
+
unavailableProviders: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Registry for managing LLM client adapters
|
|
24
|
+
*/
|
|
25
|
+
export declare class AdapterRegistry {
|
|
26
|
+
private clientAdapters;
|
|
27
|
+
private mockClientAdapter;
|
|
28
|
+
constructor();
|
|
29
|
+
/**
|
|
30
|
+
* Initializes adapters for all supported providers
|
|
31
|
+
*/
|
|
32
|
+
private initializeAdapters;
|
|
33
|
+
/**
|
|
34
|
+
* Registers a client adapter for a specific provider
|
|
35
|
+
*
|
|
36
|
+
* @param providerId - The provider ID
|
|
37
|
+
* @param adapter - The client adapter implementation
|
|
38
|
+
*/
|
|
39
|
+
registerAdapter(providerId: ApiProviderId, adapter: ILLMClientAdapter): void;
|
|
40
|
+
/**
|
|
41
|
+
* Gets the appropriate client adapter for a provider
|
|
42
|
+
*
|
|
43
|
+
* @param providerId - The provider ID
|
|
44
|
+
* @returns The client adapter to use
|
|
45
|
+
*/
|
|
46
|
+
getAdapter(providerId: ApiProviderId): ILLMClientAdapter;
|
|
47
|
+
/**
|
|
48
|
+
* Gets information about registered adapters
|
|
49
|
+
*
|
|
50
|
+
* @returns Map of provider IDs to adapter info
|
|
51
|
+
*/
|
|
52
|
+
getRegisteredAdapters(): Map<ApiProviderId, AdapterInfo>;
|
|
53
|
+
/**
|
|
54
|
+
* Gets a summary of available providers and their adapter status
|
|
55
|
+
*
|
|
56
|
+
* @returns Summary of provider availability
|
|
57
|
+
*/
|
|
58
|
+
getProviderSummary(): ProviderSummary;
|
|
59
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AdapterRegistry = void 0;
|
|
4
|
+
const MockClientAdapter_1 = require("../clients/MockClientAdapter");
|
|
5
|
+
const config_1 = require("../config");
|
|
6
|
+
/**
|
|
7
|
+
* Registry for managing LLM client adapters
|
|
8
|
+
*/
|
|
9
|
+
class AdapterRegistry {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.clientAdapters = new Map();
|
|
12
|
+
this.mockClientAdapter = new MockClientAdapter_1.MockClientAdapter();
|
|
13
|
+
this.initializeAdapters();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initializes adapters for all supported providers
|
|
17
|
+
*/
|
|
18
|
+
initializeAdapters() {
|
|
19
|
+
let registeredCount = 0;
|
|
20
|
+
const successfullyRegisteredProviders = [];
|
|
21
|
+
for (const provider of config_1.SUPPORTED_PROVIDERS) {
|
|
22
|
+
const AdapterClass = config_1.ADAPTER_CONSTRUCTORS[provider.id];
|
|
23
|
+
if (AdapterClass) {
|
|
24
|
+
try {
|
|
25
|
+
const adapterConfig = config_1.ADAPTER_CONFIGS[provider.id];
|
|
26
|
+
const adapterInstance = new AdapterClass(adapterConfig);
|
|
27
|
+
this.registerAdapter(provider.id, adapterInstance);
|
|
28
|
+
registeredCount++;
|
|
29
|
+
successfullyRegisteredProviders.push(provider.id);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`LLMService: Failed to instantiate adapter for provider '${provider.id}'. This provider will use the mock adapter. Error:`, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.warn(`LLMService: No adapter constructor found for supported provider '${provider.id}'. This provider will use the mock adapter as a fallback.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (registeredCount > 0) {
|
|
40
|
+
console.log(`LLMService: Initialized with ${registeredCount} dynamically registered adapter(s) for: ${successfullyRegisteredProviders.join(", ")}.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(`LLMService: No real adapters were dynamically registered. All providers will use the mock adapter.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Registers a client adapter for a specific provider
|
|
48
|
+
*
|
|
49
|
+
* @param providerId - The provider ID
|
|
50
|
+
* @param adapter - The client adapter implementation
|
|
51
|
+
*/
|
|
52
|
+
registerAdapter(providerId, adapter) {
|
|
53
|
+
this.clientAdapters.set(providerId, adapter);
|
|
54
|
+
console.log(`Registered client adapter for provider: ${providerId}`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets the appropriate client adapter for a provider
|
|
58
|
+
*
|
|
59
|
+
* @param providerId - The provider ID
|
|
60
|
+
* @returns The client adapter to use
|
|
61
|
+
*/
|
|
62
|
+
getAdapter(providerId) {
|
|
63
|
+
// Check for registered real adapters first
|
|
64
|
+
const registeredAdapter = this.clientAdapters.get(providerId);
|
|
65
|
+
if (registeredAdapter) {
|
|
66
|
+
console.log(`Using registered adapter for provider: ${providerId}`);
|
|
67
|
+
return registeredAdapter;
|
|
68
|
+
}
|
|
69
|
+
// Fall back to mock adapter for unsupported providers
|
|
70
|
+
console.log(`No real adapter found for ${providerId}, using mock adapter`);
|
|
71
|
+
return this.mockClientAdapter;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Gets information about registered adapters
|
|
75
|
+
*
|
|
76
|
+
* @returns Map of provider IDs to adapter info
|
|
77
|
+
*/
|
|
78
|
+
getRegisteredAdapters() {
|
|
79
|
+
const adapterInfo = new Map();
|
|
80
|
+
for (const [providerId, adapter] of this.clientAdapters.entries()) {
|
|
81
|
+
adapterInfo.set(providerId, {
|
|
82
|
+
providerId,
|
|
83
|
+
hasAdapter: true,
|
|
84
|
+
adapterInfo: adapter.getAdapterInfo?.() || { name: "Unknown Adapter" },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return adapterInfo;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Gets a summary of available providers and their adapter status
|
|
91
|
+
*
|
|
92
|
+
* @returns Summary of provider availability
|
|
93
|
+
*/
|
|
94
|
+
getProviderSummary() {
|
|
95
|
+
const availableProviders = [];
|
|
96
|
+
const unavailableProviders = [];
|
|
97
|
+
for (const provider of config_1.SUPPORTED_PROVIDERS) {
|
|
98
|
+
if (this.clientAdapters.has(provider.id)) {
|
|
99
|
+
availableProviders.push(provider.id);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
unavailableProviders.push(provider.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
totalProviders: config_1.SUPPORTED_PROVIDERS.length,
|
|
107
|
+
providersWithAdapters: availableProviders.length,
|
|
108
|
+
availableProviders,
|
|
109
|
+
unavailableProviders,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.AdapterRegistry = AdapterRegistry;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|