genai-lite 0.4.0 → 0.4.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.
- package/README.md +47 -37
- package/dist/llm/LLMService.d.ts +29 -2
- package/dist/llm/LLMService.js +80 -36
- package/dist/llm/config.js +4 -4
- package/dist/llm/services/SettingsManager.js +17 -11
- package/dist/llm/types.d.ts +81 -22
- package/dist/prompting/parser.d.ts +2 -2
- package/dist/prompting/parser.js +2 -2
- package/package.json +1 -1
- package/dist/llm/LLMService.createMessages.test.d.ts +0 -4
- package/dist/llm/LLMService.createMessages.test.js +0 -364
- package/dist/llm/LLMService.original.d.ts +0 -147
- package/dist/llm/LLMService.original.js +0 -656
- package/dist/llm/LLMService.prepareMessage.test.d.ts +0 -1
- package/dist/llm/LLMService.prepareMessage.test.js +0 -303
- package/dist/llm/LLMService.presets.test.d.ts +0 -1
- package/dist/llm/LLMService.presets.test.js +0 -210
- package/dist/llm/LLMService.sendMessage.preset.test.d.ts +0 -1
- package/dist/llm/LLMService.sendMessage.preset.test.js +0 -153
- package/dist/llm/LLMService.test.d.ts +0 -1
- package/dist/llm/LLMService.test.js +0 -639
- package/dist/llm/clients/AnthropicClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/AnthropicClientAdapter.test.js +0 -273
- package/dist/llm/clients/GeminiClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/GeminiClientAdapter.test.js +0 -405
- package/dist/llm/clients/LlamaCppClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppClientAdapter.test.js +0 -447
- package/dist/llm/clients/LlamaCppServerClient.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppServerClient.test.js +0 -294
- package/dist/llm/clients/MockClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/MockClientAdapter.test.js +0 -250
- package/dist/llm/clients/OpenAIClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/OpenAIClientAdapter.test.js +0 -258
- package/dist/llm/clients/adapterErrorUtils.test.d.ts +0 -1
- package/dist/llm/clients/adapterErrorUtils.test.js +0 -123
- package/dist/llm/config.test.d.ts +0 -1
- package/dist/llm/config.test.js +0 -176
- package/dist/llm/services/AdapterRegistry.test.d.ts +0 -1
- package/dist/llm/services/AdapterRegistry.test.js +0 -239
- package/dist/llm/services/ModelResolver.test.d.ts +0 -1
- package/dist/llm/services/ModelResolver.test.js +0 -179
- package/dist/llm/services/PresetManager.test.d.ts +0 -1
- package/dist/llm/services/PresetManager.test.js +0 -210
- package/dist/llm/services/RequestValidator.test.d.ts +0 -1
- package/dist/llm/services/RequestValidator.test.js +0 -159
- package/dist/llm/services/SettingsManager.test.d.ts +0 -1
- package/dist/llm/services/SettingsManager.test.js +0 -266
- package/dist/prompting/builder.d.ts +0 -38
- package/dist/prompting/builder.js +0 -63
- package/dist/prompting/builder.test.d.ts +0 -4
- package/dist/prompting/builder.test.js +0 -109
- package/dist/prompting/content.test.d.ts +0 -4
- package/dist/prompting/content.test.js +0 -212
- package/dist/prompting/parser.test.d.ts +0 -4
- package/dist/prompting/parser.test.js +0 -464
- package/dist/prompting/template.test.d.ts +0 -1
- package/dist/prompting/template.test.js +0 -250
- package/dist/providers/fromEnvironment.test.d.ts +0 -1
- package/dist/providers/fromEnvironment.test.js +0 -59
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// Mock the client adapters first
|
|
4
|
-
jest.mock('../clients/MockClientAdapter');
|
|
5
|
-
jest.mock('../clients/OpenAIClientAdapter', () => ({
|
|
6
|
-
OpenAIClientAdapter: jest.fn().mockImplementation(() => ({
|
|
7
|
-
sendMessage: jest.fn(),
|
|
8
|
-
getAdapterInfo: () => ({ providerId: 'openai', name: 'OpenAI Adapter' })
|
|
9
|
-
}))
|
|
10
|
-
}));
|
|
11
|
-
jest.mock('../clients/AnthropicClientAdapter', () => ({
|
|
12
|
-
AnthropicClientAdapter: jest.fn().mockImplementation(() => ({
|
|
13
|
-
sendMessage: jest.fn(),
|
|
14
|
-
getAdapterInfo: () => ({ providerId: 'anthropic', name: 'Anthropic Adapter' })
|
|
15
|
-
}))
|
|
16
|
-
}));
|
|
17
|
-
jest.mock('../clients/GeminiClientAdapter', () => ({
|
|
18
|
-
GeminiClientAdapter: jest.fn().mockImplementation(() => ({
|
|
19
|
-
sendMessage: jest.fn(),
|
|
20
|
-
getAdapterInfo: () => ({ providerId: 'gemini', name: 'Gemini Adapter' })
|
|
21
|
-
}))
|
|
22
|
-
}));
|
|
23
|
-
// Mock the config imports - must be before imports that use it
|
|
24
|
-
jest.mock('../config', () => {
|
|
25
|
-
const { OpenAIClientAdapter } = require('../clients/OpenAIClientAdapter');
|
|
26
|
-
const { AnthropicClientAdapter } = require('../clients/AnthropicClientAdapter');
|
|
27
|
-
const { GeminiClientAdapter } = require('../clients/GeminiClientAdapter');
|
|
28
|
-
return {
|
|
29
|
-
SUPPORTED_PROVIDERS: [
|
|
30
|
-
{ id: 'openai', name: 'OpenAI' },
|
|
31
|
-
{ id: 'anthropic', name: 'Anthropic' },
|
|
32
|
-
{ id: 'gemini', name: 'Google Gemini' },
|
|
33
|
-
{ id: 'mistral', name: 'Mistral' }
|
|
34
|
-
],
|
|
35
|
-
ADAPTER_CONSTRUCTORS: {
|
|
36
|
-
openai: OpenAIClientAdapter,
|
|
37
|
-
anthropic: AnthropicClientAdapter,
|
|
38
|
-
gemini: GeminiClientAdapter,
|
|
39
|
-
// mistral intentionally missing to test fallback
|
|
40
|
-
},
|
|
41
|
-
ADAPTER_CONFIGS: {
|
|
42
|
-
openai: {},
|
|
43
|
-
anthropic: {},
|
|
44
|
-
gemini: {},
|
|
45
|
-
mistral: {}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
// Now import the modules
|
|
50
|
-
const AdapterRegistry_1 = require("./AdapterRegistry");
|
|
51
|
-
const MockClientAdapter_1 = require("../clients/MockClientAdapter");
|
|
52
|
-
const OpenAIClientAdapter_1 = require("../clients/OpenAIClientAdapter");
|
|
53
|
-
describe('AdapterRegistry', () => {
|
|
54
|
-
let registry;
|
|
55
|
-
let consoleLogSpy;
|
|
56
|
-
let consoleWarnSpy;
|
|
57
|
-
let consoleErrorSpy;
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
jest.clearAllMocks();
|
|
60
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
61
|
-
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
62
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
63
|
-
});
|
|
64
|
-
afterEach(() => {
|
|
65
|
-
consoleLogSpy.mockRestore();
|
|
66
|
-
consoleWarnSpy.mockRestore();
|
|
67
|
-
consoleErrorSpy.mockRestore();
|
|
68
|
-
});
|
|
69
|
-
describe('initialization', () => {
|
|
70
|
-
it('should initialize with adapters for providers with constructors', () => {
|
|
71
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
72
|
-
// Should have logged successful initialization
|
|
73
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Initialized with 3 dynamically registered adapter(s)'));
|
|
74
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('openai, anthropic, gemini'));
|
|
75
|
-
// Should warn about missing constructor for mistral
|
|
76
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("No adapter constructor found for supported provider 'mistral'"));
|
|
77
|
-
});
|
|
78
|
-
it('should handle adapter initialization errors gracefully', () => {
|
|
79
|
-
// Make OpenAIClientAdapter throw an error on construction
|
|
80
|
-
OpenAIClientAdapter_1.OpenAIClientAdapter.mockImplementationOnce(() => {
|
|
81
|
-
throw new Error('Initialization failed');
|
|
82
|
-
});
|
|
83
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
84
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to instantiate adapter for provider 'openai'"), expect.any(Error));
|
|
85
|
-
});
|
|
86
|
-
it('should log when no adapters are registered', () => {
|
|
87
|
-
// Clear all module cache to ensure clean isolation
|
|
88
|
-
jest.resetModules();
|
|
89
|
-
// Mock the config module with no constructors before requiring AdapterRegistry
|
|
90
|
-
jest.doMock('../config', () => ({
|
|
91
|
-
SUPPORTED_PROVIDERS: [
|
|
92
|
-
{ id: 'openai', name: 'OpenAI' },
|
|
93
|
-
{ id: 'anthropic', name: 'Anthropic' },
|
|
94
|
-
{ id: 'gemini', name: 'Google Gemini' },
|
|
95
|
-
{ id: 'mistral', name: 'Mistral' }
|
|
96
|
-
],
|
|
97
|
-
ADAPTER_CONSTRUCTORS: {}, // No constructors
|
|
98
|
-
ADAPTER_CONFIGS: {
|
|
99
|
-
openai: {},
|
|
100
|
-
anthropic: {},
|
|
101
|
-
gemini: {},
|
|
102
|
-
mistral: {}
|
|
103
|
-
}
|
|
104
|
-
}));
|
|
105
|
-
// Mock the client adapters to prevent import errors
|
|
106
|
-
jest.doMock('../clients/MockClientAdapter', () => ({
|
|
107
|
-
MockClientAdapter: jest.fn()
|
|
108
|
-
}));
|
|
109
|
-
jest.doMock('../clients/OpenAIClientAdapter', () => ({
|
|
110
|
-
OpenAIClientAdapter: jest.fn()
|
|
111
|
-
}));
|
|
112
|
-
jest.doMock('../clients/AnthropicClientAdapter', () => ({
|
|
113
|
-
AnthropicClientAdapter: jest.fn()
|
|
114
|
-
}));
|
|
115
|
-
jest.doMock('../clients/GeminiClientAdapter', () => ({
|
|
116
|
-
GeminiClientAdapter: jest.fn()
|
|
117
|
-
}));
|
|
118
|
-
// Clear console spy calls before the test
|
|
119
|
-
consoleLogSpy.mockClear();
|
|
120
|
-
// Now require the AdapterRegistry with mocked dependencies
|
|
121
|
-
const { AdapterRegistry: IsolatedAdapterRegistry } = require('./AdapterRegistry');
|
|
122
|
-
const isolatedRegistry = new IsolatedAdapterRegistry();
|
|
123
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('LLMService: No real adapters were dynamically registered. All providers will use the mock adapter.');
|
|
124
|
-
// Restore modules after test
|
|
125
|
-
jest.resetModules();
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
describe('getAdapter', () => {
|
|
129
|
-
beforeEach(() => {
|
|
130
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
131
|
-
});
|
|
132
|
-
it('should return registered adapter for supported provider', () => {
|
|
133
|
-
const adapter = registry.getAdapter('openai');
|
|
134
|
-
expect(adapter).toBeDefined();
|
|
135
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('Using registered adapter for provider: openai');
|
|
136
|
-
});
|
|
137
|
-
it('should return mock adapter for provider without adapter', () => {
|
|
138
|
-
const adapter = registry.getAdapter('mistral');
|
|
139
|
-
expect(adapter).toBeInstanceOf(MockClientAdapter_1.MockClientAdapter);
|
|
140
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('No real adapter found for mistral, using mock adapter');
|
|
141
|
-
});
|
|
142
|
-
it('should return mock adapter for unknown provider', () => {
|
|
143
|
-
const adapter = registry.getAdapter('unknown-provider');
|
|
144
|
-
expect(adapter).toBeInstanceOf(MockClientAdapter_1.MockClientAdapter);
|
|
145
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('No real adapter found for unknown-provider, using mock adapter');
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe('registerAdapter', () => {
|
|
149
|
-
beforeEach(() => {
|
|
150
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
151
|
-
});
|
|
152
|
-
it('should register new adapter', () => {
|
|
153
|
-
const mockAdapter = {
|
|
154
|
-
sendMessage: jest.fn(),
|
|
155
|
-
getAdapterInfo: () => ({ providerId: 'custom-provider', name: 'Custom Adapter' })
|
|
156
|
-
};
|
|
157
|
-
registry.registerAdapter('custom-provider', mockAdapter);
|
|
158
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('Registered client adapter for provider: custom-provider');
|
|
159
|
-
// Should be able to retrieve it
|
|
160
|
-
const retrievedAdapter = registry.getAdapter('custom-provider');
|
|
161
|
-
expect(retrievedAdapter).toBe(mockAdapter);
|
|
162
|
-
});
|
|
163
|
-
it('should override existing adapter', () => {
|
|
164
|
-
const newAdapter = {
|
|
165
|
-
sendMessage: jest.fn(),
|
|
166
|
-
getAdapterInfo: () => ({ providerId: 'openai', name: 'New OpenAI Adapter' })
|
|
167
|
-
};
|
|
168
|
-
registry.registerAdapter('openai', newAdapter);
|
|
169
|
-
const retrievedAdapter = registry.getAdapter('openai');
|
|
170
|
-
expect(retrievedAdapter).toBe(newAdapter);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
describe('getRegisteredAdapters', () => {
|
|
174
|
-
beforeEach(() => {
|
|
175
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
176
|
-
});
|
|
177
|
-
it('should return info for all registered adapters', () => {
|
|
178
|
-
const adapterInfo = registry.getRegisteredAdapters();
|
|
179
|
-
expect(adapterInfo.size).toBe(3); // openai, anthropic, gemini
|
|
180
|
-
expect(adapterInfo.get('openai')).toEqual({
|
|
181
|
-
providerId: 'openai',
|
|
182
|
-
hasAdapter: true,
|
|
183
|
-
adapterInfo: { providerId: 'openai', name: 'OpenAI Adapter' }
|
|
184
|
-
});
|
|
185
|
-
expect(adapterInfo.get('anthropic')).toEqual({
|
|
186
|
-
providerId: 'anthropic',
|
|
187
|
-
hasAdapter: true,
|
|
188
|
-
adapterInfo: { providerId: 'anthropic', name: 'Anthropic Adapter' }
|
|
189
|
-
});
|
|
190
|
-
expect(adapterInfo.get('gemini')).toEqual({
|
|
191
|
-
providerId: 'gemini',
|
|
192
|
-
hasAdapter: true,
|
|
193
|
-
adapterInfo: { providerId: 'gemini', name: 'Gemini Adapter' }
|
|
194
|
-
});
|
|
195
|
-
// mistral should not be in the map
|
|
196
|
-
expect(adapterInfo.has('mistral')).toBe(false);
|
|
197
|
-
});
|
|
198
|
-
it('should handle adapters without getAdapterInfo method', () => {
|
|
199
|
-
const adapterWithoutInfo = {
|
|
200
|
-
sendMessage: jest.fn(),
|
|
201
|
-
// No getAdapterInfo method
|
|
202
|
-
};
|
|
203
|
-
registry.registerAdapter('custom', adapterWithoutInfo);
|
|
204
|
-
const adapterInfo = registry.getRegisteredAdapters();
|
|
205
|
-
expect(adapterInfo.get('custom')).toEqual({
|
|
206
|
-
providerId: 'custom',
|
|
207
|
-
hasAdapter: true,
|
|
208
|
-
adapterInfo: { name: 'Unknown Adapter' }
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
describe('getProviderSummary', () => {
|
|
213
|
-
beforeEach(() => {
|
|
214
|
-
registry = new AdapterRegistry_1.AdapterRegistry();
|
|
215
|
-
});
|
|
216
|
-
it('should return correct summary of provider availability', () => {
|
|
217
|
-
const summary = registry.getProviderSummary();
|
|
218
|
-
expect(summary).toEqual({
|
|
219
|
-
totalProviders: 4,
|
|
220
|
-
providersWithAdapters: 3,
|
|
221
|
-
availableProviders: ['openai', 'anthropic', 'gemini'],
|
|
222
|
-
unavailableProviders: ['mistral']
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
it('should update summary after registering new adapter', () => {
|
|
226
|
-
const mockAdapter = {
|
|
227
|
-
sendMessage: jest.fn(),
|
|
228
|
-
};
|
|
229
|
-
registry.registerAdapter('mistral', mockAdapter);
|
|
230
|
-
const summary = registry.getProviderSummary();
|
|
231
|
-
expect(summary).toEqual({
|
|
232
|
-
totalProviders: 4,
|
|
233
|
-
providersWithAdapters: 4,
|
|
234
|
-
availableProviders: ['openai', 'anthropic', 'gemini', 'mistral'],
|
|
235
|
-
unavailableProviders: []
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const ModelResolver_1 = require("./ModelResolver");
|
|
4
|
-
describe('ModelResolver', () => {
|
|
5
|
-
let resolver;
|
|
6
|
-
let mockPresetManager;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockPresetManager = {
|
|
9
|
-
getPresets: jest.fn(),
|
|
10
|
-
resolvePreset: jest.fn(),
|
|
11
|
-
};
|
|
12
|
-
resolver = new ModelResolver_1.ModelResolver(mockPresetManager);
|
|
13
|
-
});
|
|
14
|
-
describe('preset resolution', () => {
|
|
15
|
-
it('should resolve model from valid preset', () => {
|
|
16
|
-
const mockPreset = {
|
|
17
|
-
id: 'test-preset',
|
|
18
|
-
displayName: 'Test Preset',
|
|
19
|
-
providerId: 'openai',
|
|
20
|
-
modelId: 'gpt-4.1',
|
|
21
|
-
settings: { temperature: 0.7 }
|
|
22
|
-
};
|
|
23
|
-
mockPresetManager.resolvePreset.mockReturnValue(mockPreset);
|
|
24
|
-
const result = resolver.resolve({ presetId: 'test-preset' });
|
|
25
|
-
expect(mockPresetManager.resolvePreset).toHaveBeenCalledWith('test-preset');
|
|
26
|
-
expect(result.error).toBeUndefined();
|
|
27
|
-
expect(result.providerId).toBe('openai');
|
|
28
|
-
expect(result.modelId).toBe('gpt-4.1');
|
|
29
|
-
expect(result.modelInfo).toBeDefined();
|
|
30
|
-
expect(result.settings).toEqual({ temperature: 0.7 });
|
|
31
|
-
});
|
|
32
|
-
it('should return error for non-existent preset', () => {
|
|
33
|
-
mockPresetManager.resolvePreset.mockReturnValue(null);
|
|
34
|
-
const result = resolver.resolve({ presetId: 'non-existent' });
|
|
35
|
-
expect(result.error).toBeDefined();
|
|
36
|
-
expect(result.error?.error.code).toBe('PRESET_NOT_FOUND');
|
|
37
|
-
expect(result.error?.error.message).toContain('Preset not found: non-existent');
|
|
38
|
-
});
|
|
39
|
-
it('should return error for preset with invalid model', () => {
|
|
40
|
-
const mockPreset = {
|
|
41
|
-
id: 'invalid-model-preset',
|
|
42
|
-
displayName: 'Invalid Model Preset',
|
|
43
|
-
providerId: 'openai',
|
|
44
|
-
modelId: 'invalid-model',
|
|
45
|
-
settings: {}
|
|
46
|
-
};
|
|
47
|
-
mockPresetManager.resolvePreset.mockReturnValue(mockPreset);
|
|
48
|
-
const result = resolver.resolve({ presetId: 'invalid-model-preset' });
|
|
49
|
-
expect(result.error).toBeDefined();
|
|
50
|
-
expect(result.error?.error.code).toBe('MODEL_NOT_FOUND');
|
|
51
|
-
expect(result.error?.error.message).toContain('Model not found for preset');
|
|
52
|
-
});
|
|
53
|
-
it('should merge preset settings with user settings', () => {
|
|
54
|
-
const mockPreset = {
|
|
55
|
-
id: 'test-preset',
|
|
56
|
-
displayName: 'Test Preset',
|
|
57
|
-
providerId: 'openai',
|
|
58
|
-
modelId: 'gpt-4.1',
|
|
59
|
-
settings: {
|
|
60
|
-
temperature: 0.7,
|
|
61
|
-
maxTokens: 1000
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
mockPresetManager.resolvePreset.mockReturnValue(mockPreset);
|
|
65
|
-
const result = resolver.resolve({
|
|
66
|
-
presetId: 'test-preset',
|
|
67
|
-
settings: {
|
|
68
|
-
temperature: 0.9, // Override
|
|
69
|
-
topP: 0.95 // New setting
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
expect(result.settings).toEqual({
|
|
73
|
-
temperature: 0.9, // User override
|
|
74
|
-
maxTokens: 1000, // From preset
|
|
75
|
-
topP: 0.95 // User addition
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
describe('direct model resolution', () => {
|
|
80
|
-
it('should resolve model from valid provider and model IDs', () => {
|
|
81
|
-
const result = resolver.resolve({
|
|
82
|
-
providerId: 'openai',
|
|
83
|
-
modelId: 'gpt-4.1'
|
|
84
|
-
});
|
|
85
|
-
expect(result.error).toBeUndefined();
|
|
86
|
-
expect(result.providerId).toBe('openai');
|
|
87
|
-
expect(result.modelId).toBe('gpt-4.1');
|
|
88
|
-
expect(result.modelInfo).toBeDefined();
|
|
89
|
-
expect(result.modelInfo?.id).toBe('gpt-4.1');
|
|
90
|
-
});
|
|
91
|
-
it('should return error when neither preset nor provider/model provided', () => {
|
|
92
|
-
const result = resolver.resolve({});
|
|
93
|
-
expect(result.error).toBeDefined();
|
|
94
|
-
expect(result.error?.error.code).toBe('INVALID_MODEL_SELECTION');
|
|
95
|
-
expect(result.error?.error.message).toContain('Either presetId or both providerId and modelId must be provided');
|
|
96
|
-
});
|
|
97
|
-
it('should return error when only providerId provided', () => {
|
|
98
|
-
const result = resolver.resolve({ providerId: 'openai' });
|
|
99
|
-
expect(result.error).toBeDefined();
|
|
100
|
-
expect(result.error?.error.code).toBe('INVALID_MODEL_SELECTION');
|
|
101
|
-
});
|
|
102
|
-
it('should return error when only modelId provided', () => {
|
|
103
|
-
const result = resolver.resolve({ modelId: 'gpt-4.1' });
|
|
104
|
-
expect(result.error).toBeDefined();
|
|
105
|
-
expect(result.error?.error.code).toBe('INVALID_MODEL_SELECTION');
|
|
106
|
-
});
|
|
107
|
-
it('should return error for unsupported provider', () => {
|
|
108
|
-
const result = resolver.resolve({
|
|
109
|
-
providerId: 'unsupported-provider',
|
|
110
|
-
modelId: 'some-model'
|
|
111
|
-
});
|
|
112
|
-
expect(result.error).toBeDefined();
|
|
113
|
-
expect(result.error?.error.code).toBe('UNSUPPORTED_PROVIDER');
|
|
114
|
-
expect(result.error?.error.message).toContain('Unsupported provider');
|
|
115
|
-
expect(result.error?.error.message).toContain('Supported providers:');
|
|
116
|
-
});
|
|
117
|
-
it('should create fallback model info for unknown models (with warning)', () => {
|
|
118
|
-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
119
|
-
const result = resolver.resolve({
|
|
120
|
-
providerId: 'openai',
|
|
121
|
-
modelId: 'unsupported-model'
|
|
122
|
-
});
|
|
123
|
-
// Should succeed with fallback, not error
|
|
124
|
-
expect(result.error).toBeUndefined();
|
|
125
|
-
expect(result.modelInfo).toBeDefined();
|
|
126
|
-
expect(result.modelInfo?.id).toBe('unsupported-model');
|
|
127
|
-
expect(result.modelInfo?.providerId).toBe('openai');
|
|
128
|
-
// Should warn about unknown model
|
|
129
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown model "unsupported-model"'));
|
|
130
|
-
consoleWarnSpy.mockRestore();
|
|
131
|
-
});
|
|
132
|
-
it('should silently create fallback for llamacpp unknown models (no warning)', () => {
|
|
133
|
-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
134
|
-
const result = resolver.resolve({
|
|
135
|
-
providerId: 'llamacpp',
|
|
136
|
-
modelId: 'my-custom-gguf-model'
|
|
137
|
-
});
|
|
138
|
-
// Should succeed with fallback, not error
|
|
139
|
-
expect(result.error).toBeUndefined();
|
|
140
|
-
expect(result.modelInfo).toBeDefined();
|
|
141
|
-
expect(result.modelInfo?.id).toBe('my-custom-gguf-model');
|
|
142
|
-
expect(result.modelInfo?.providerId).toBe('llamacpp');
|
|
143
|
-
// Should NOT warn (llamacpp allows unknown models silently)
|
|
144
|
-
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
145
|
-
consoleWarnSpy.mockRestore();
|
|
146
|
-
});
|
|
147
|
-
it('should pass through user settings for direct resolution', () => {
|
|
148
|
-
const settings = {
|
|
149
|
-
temperature: 0.8,
|
|
150
|
-
maxTokens: 2000
|
|
151
|
-
};
|
|
152
|
-
const result = resolver.resolve({
|
|
153
|
-
providerId: 'openai',
|
|
154
|
-
modelId: 'gpt-4.1',
|
|
155
|
-
settings
|
|
156
|
-
});
|
|
157
|
-
expect(result.settings).toBe(settings); // Should be the same reference
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
describe('priority handling', () => {
|
|
161
|
-
it('should prioritize presetId over providerId/modelId when both provided', () => {
|
|
162
|
-
const mockPreset = {
|
|
163
|
-
id: 'test-preset',
|
|
164
|
-
displayName: 'Test Preset',
|
|
165
|
-
providerId: 'anthropic',
|
|
166
|
-
modelId: 'claude-3-5-sonnet-20241022',
|
|
167
|
-
settings: {}
|
|
168
|
-
};
|
|
169
|
-
mockPresetManager.resolvePreset.mockReturnValue(mockPreset);
|
|
170
|
-
const result = resolver.resolve({
|
|
171
|
-
presetId: 'test-preset',
|
|
172
|
-
providerId: 'openai', // These should be ignored
|
|
173
|
-
modelId: 'gpt-4.1' // These should be ignored
|
|
174
|
-
});
|
|
175
|
-
expect(result.providerId).toBe('anthropic');
|
|
176
|
-
expect(result.modelId).toBe('claude-3-5-sonnet-20241022');
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const PresetManager_1 = require("./PresetManager");
|
|
7
|
-
const presets_json_1 = __importDefault(require("../../config/presets.json"));
|
|
8
|
-
describe('PresetManager', () => {
|
|
9
|
-
describe('Default behavior', () => {
|
|
10
|
-
it('should load default presets when no options provided', () => {
|
|
11
|
-
const manager = new PresetManager_1.PresetManager();
|
|
12
|
-
const presets = manager.getPresets();
|
|
13
|
-
expect(presets).toHaveLength(presets_json_1.default.length);
|
|
14
|
-
expect(presets).toEqual(expect.arrayContaining(presets_json_1.default.map(preset => expect.objectContaining({
|
|
15
|
-
id: preset.id,
|
|
16
|
-
displayName: preset.displayName,
|
|
17
|
-
providerId: preset.providerId,
|
|
18
|
-
modelId: preset.modelId
|
|
19
|
-
}))));
|
|
20
|
-
});
|
|
21
|
-
it('should return a copy of presets to prevent external modification', () => {
|
|
22
|
-
const manager = new PresetManager_1.PresetManager();
|
|
23
|
-
const presets1 = manager.getPresets();
|
|
24
|
-
const presets2 = manager.getPresets();
|
|
25
|
-
expect(presets1).not.toBe(presets2); // Different array instances
|
|
26
|
-
expect(presets1).toEqual(presets2); // Same content
|
|
27
|
-
// Modifying returned array should not affect service
|
|
28
|
-
presets1.push({
|
|
29
|
-
id: 'test-preset',
|
|
30
|
-
displayName: 'Test',
|
|
31
|
-
providerId: 'openai',
|
|
32
|
-
modelId: 'gpt-4',
|
|
33
|
-
settings: {}
|
|
34
|
-
});
|
|
35
|
-
const presets3 = manager.getPresets();
|
|
36
|
-
expect(presets3).toHaveLength(presets_json_1.default.length);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
describe('Extend mode', () => {
|
|
40
|
-
it('should add new presets to defaults in extend mode', () => {
|
|
41
|
-
const customPresets = [
|
|
42
|
-
{
|
|
43
|
-
id: 'custom-preset-1',
|
|
44
|
-
displayName: 'Custom Preset 1',
|
|
45
|
-
providerId: 'openai',
|
|
46
|
-
modelId: 'gpt-4',
|
|
47
|
-
settings: { temperature: 0.5 }
|
|
48
|
-
}
|
|
49
|
-
];
|
|
50
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'extend');
|
|
51
|
-
const presets = manager.getPresets();
|
|
52
|
-
expect(presets).toHaveLength(presets_json_1.default.length + 1);
|
|
53
|
-
expect(presets).toContainEqual(expect.objectContaining({
|
|
54
|
-
id: 'custom-preset-1',
|
|
55
|
-
displayName: 'Custom Preset 1'
|
|
56
|
-
}));
|
|
57
|
-
});
|
|
58
|
-
it('should override default presets with same ID in extend mode', () => {
|
|
59
|
-
const existingPresetId = presets_json_1.default[0].id;
|
|
60
|
-
const customPresets = [
|
|
61
|
-
{
|
|
62
|
-
id: existingPresetId,
|
|
63
|
-
displayName: 'Overridden Preset',
|
|
64
|
-
providerId: 'anthropic',
|
|
65
|
-
modelId: 'claude-3-5-sonnet-20241022',
|
|
66
|
-
settings: { temperature: 0.8 }
|
|
67
|
-
}
|
|
68
|
-
];
|
|
69
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'extend');
|
|
70
|
-
const presets = manager.getPresets();
|
|
71
|
-
const overriddenPreset = presets.find(p => p.id === existingPresetId);
|
|
72
|
-
expect(presets).toHaveLength(presets_json_1.default.length);
|
|
73
|
-
expect(overriddenPreset).toBeDefined();
|
|
74
|
-
expect(overriddenPreset?.displayName).toBe('Overridden Preset');
|
|
75
|
-
expect(overriddenPreset?.providerId).toBe('anthropic');
|
|
76
|
-
});
|
|
77
|
-
it('should use extend mode by default when mode not specified', () => {
|
|
78
|
-
const customPresets = [
|
|
79
|
-
{
|
|
80
|
-
id: 'custom-preset-default',
|
|
81
|
-
displayName: 'Custom Default',
|
|
82
|
-
providerId: 'gemini',
|
|
83
|
-
modelId: 'gemini-2.0-flash',
|
|
84
|
-
settings: { temperature: 0.3 }
|
|
85
|
-
}
|
|
86
|
-
];
|
|
87
|
-
const manager = new PresetManager_1.PresetManager(customPresets);
|
|
88
|
-
const presets = manager.getPresets();
|
|
89
|
-
expect(presets).toHaveLength(presets_json_1.default.length + 1);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
describe('Replace mode', () => {
|
|
93
|
-
it('should use only custom presets in replace mode', () => {
|
|
94
|
-
const customPresets = [
|
|
95
|
-
{
|
|
96
|
-
id: 'replace-preset-1',
|
|
97
|
-
displayName: 'Replace Preset 1',
|
|
98
|
-
providerId: 'openai',
|
|
99
|
-
modelId: 'gpt-4',
|
|
100
|
-
settings: { temperature: 0.5 }
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 'replace-preset-2',
|
|
104
|
-
displayName: 'Replace Preset 2',
|
|
105
|
-
providerId: 'anthropic',
|
|
106
|
-
modelId: 'claude-3-5-sonnet-20241022',
|
|
107
|
-
settings: { temperature: 0.3 }
|
|
108
|
-
}
|
|
109
|
-
];
|
|
110
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'replace');
|
|
111
|
-
const presets = manager.getPresets();
|
|
112
|
-
expect(presets).toHaveLength(2);
|
|
113
|
-
expect(presets).toEqual(expect.arrayContaining([
|
|
114
|
-
expect.objectContaining({ id: 'replace-preset-1' }),
|
|
115
|
-
expect.objectContaining({ id: 'replace-preset-2' })
|
|
116
|
-
]));
|
|
117
|
-
// Should not contain any default presets
|
|
118
|
-
const defaultPresetIds = presets_json_1.default.map(p => p.id);
|
|
119
|
-
const actualPresetIds = presets.map(p => p.id);
|
|
120
|
-
expect(actualPresetIds).not.toContain(expect.arrayContaining(defaultPresetIds));
|
|
121
|
-
});
|
|
122
|
-
it('should return empty array when replace mode with no custom presets', () => {
|
|
123
|
-
const manager = new PresetManager_1.PresetManager([], 'replace');
|
|
124
|
-
const presets = manager.getPresets();
|
|
125
|
-
expect(presets).toHaveLength(0);
|
|
126
|
-
});
|
|
127
|
-
it('should handle undefined presets array in replace mode', () => {
|
|
128
|
-
const manager = new PresetManager_1.PresetManager(undefined, 'replace');
|
|
129
|
-
const presets = manager.getPresets();
|
|
130
|
-
expect(presets).toHaveLength(0);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('Edge cases', () => {
|
|
134
|
-
it('should handle duplicate IDs within custom presets', () => {
|
|
135
|
-
const customPresets = [
|
|
136
|
-
{
|
|
137
|
-
id: 'duplicate-id',
|
|
138
|
-
displayName: 'First Preset',
|
|
139
|
-
providerId: 'openai',
|
|
140
|
-
modelId: 'gpt-4',
|
|
141
|
-
settings: { temperature: 0.5 }
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
id: 'duplicate-id',
|
|
145
|
-
displayName: 'Second Preset',
|
|
146
|
-
providerId: 'anthropic',
|
|
147
|
-
modelId: 'claude-3-5-sonnet-20241022',
|
|
148
|
-
settings: { temperature: 0.3 }
|
|
149
|
-
}
|
|
150
|
-
];
|
|
151
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'replace');
|
|
152
|
-
const presets = manager.getPresets();
|
|
153
|
-
const duplicatePresets = presets.filter(p => p.id === 'duplicate-id');
|
|
154
|
-
// Last one should win
|
|
155
|
-
expect(duplicatePresets).toHaveLength(1);
|
|
156
|
-
expect(duplicatePresets[0].displayName).toBe('Second Preset');
|
|
157
|
-
});
|
|
158
|
-
it('should handle presets with complex settings including gemini safety settings', () => {
|
|
159
|
-
const customPresets = [
|
|
160
|
-
{
|
|
161
|
-
id: 'gemini-complex',
|
|
162
|
-
displayName: 'Gemini Complex',
|
|
163
|
-
providerId: 'gemini',
|
|
164
|
-
modelId: 'gemini-2.0-flash',
|
|
165
|
-
settings: {
|
|
166
|
-
temperature: 0.5,
|
|
167
|
-
maxTokens: 2000,
|
|
168
|
-
geminiSafetySettings: [
|
|
169
|
-
{ category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
|
|
170
|
-
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
];
|
|
175
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'replace');
|
|
176
|
-
const presets = manager.getPresets();
|
|
177
|
-
expect(presets).toHaveLength(1);
|
|
178
|
-
expect(presets[0].settings.geminiSafetySettings).toHaveLength(2);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
describe('resolvePreset', () => {
|
|
182
|
-
it('should find preset by ID', () => {
|
|
183
|
-
const customPresets = [
|
|
184
|
-
{
|
|
185
|
-
id: 'test-preset-1',
|
|
186
|
-
displayName: 'Test Preset 1',
|
|
187
|
-
providerId: 'openai',
|
|
188
|
-
modelId: 'gpt-4',
|
|
189
|
-
settings: {}
|
|
190
|
-
}
|
|
191
|
-
];
|
|
192
|
-
const manager = new PresetManager_1.PresetManager(customPresets, 'replace');
|
|
193
|
-
const preset = manager.resolvePreset('test-preset-1');
|
|
194
|
-
expect(preset).toBeDefined();
|
|
195
|
-
expect(preset?.displayName).toBe('Test Preset 1');
|
|
196
|
-
});
|
|
197
|
-
it('should return null for non-existent preset', () => {
|
|
198
|
-
const manager = new PresetManager_1.PresetManager();
|
|
199
|
-
const preset = manager.resolvePreset('non-existent-preset');
|
|
200
|
-
expect(preset).toBeNull();
|
|
201
|
-
});
|
|
202
|
-
it('should find default preset in extend mode', () => {
|
|
203
|
-
const manager = new PresetManager_1.PresetManager();
|
|
204
|
-
const firstDefaultPresetId = presets_json_1.default[0].id;
|
|
205
|
-
const preset = manager.resolvePreset(firstDefaultPresetId);
|
|
206
|
-
expect(preset).toBeDefined();
|
|
207
|
-
expect(preset?.id).toBe(firstDefaultPresetId);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|