@hazeljs/ai 0.2.0-beta.54 → 0.2.0-beta.56

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 (42) hide show
  1. package/README.md +1 -1
  2. package/dist/ai-enhanced.service.js +1 -1
  3. package/dist/ai-enhanced.service.test.d.ts +2 -0
  4. package/dist/ai-enhanced.service.test.d.ts.map +1 -0
  5. package/dist/ai-enhanced.service.test.js +501 -0
  6. package/dist/ai-enhanced.test.d.ts +2 -0
  7. package/dist/ai-enhanced.test.d.ts.map +1 -0
  8. package/dist/ai-enhanced.test.js +587 -0
  9. package/dist/ai.decorator.test.d.ts +2 -0
  10. package/dist/ai.decorator.test.d.ts.map +1 -0
  11. package/dist/ai.decorator.test.js +189 -0
  12. package/dist/ai.module.test.d.ts +2 -0
  13. package/dist/ai.module.test.d.ts.map +1 -0
  14. package/dist/ai.module.test.js +23 -0
  15. package/dist/ai.service.js +1 -1
  16. package/dist/ai.service.test.d.ts +2 -0
  17. package/dist/ai.service.test.d.ts.map +1 -0
  18. package/dist/ai.service.test.js +222 -0
  19. package/dist/context/context.manager.test.d.ts +2 -0
  20. package/dist/context/context.manager.test.d.ts.map +1 -0
  21. package/dist/context/context.manager.test.js +180 -0
  22. package/dist/providers/anthropic.provider.test.d.ts +2 -0
  23. package/dist/providers/anthropic.provider.test.d.ts.map +1 -0
  24. package/dist/providers/anthropic.provider.test.js +222 -0
  25. package/dist/providers/cohere.provider.test.d.ts +2 -0
  26. package/dist/providers/cohere.provider.test.d.ts.map +1 -0
  27. package/dist/providers/cohere.provider.test.js +267 -0
  28. package/dist/providers/gemini.provider.test.d.ts +2 -0
  29. package/dist/providers/gemini.provider.test.d.ts.map +1 -0
  30. package/dist/providers/gemini.provider.test.js +219 -0
  31. package/dist/providers/ollama.provider.test.d.ts +2 -0
  32. package/dist/providers/ollama.provider.test.d.ts.map +1 -0
  33. package/dist/providers/ollama.provider.test.js +267 -0
  34. package/dist/providers/openai.provider.test.d.ts +2 -0
  35. package/dist/providers/openai.provider.test.d.ts.map +1 -0
  36. package/dist/providers/openai.provider.test.js +364 -0
  37. package/dist/tracking/token.tracker.js +1 -1
  38. package/dist/tracking/token.tracker.test.d.ts +2 -0
  39. package/dist/tracking/token.tracker.test.d.ts.map +1 -0
  40. package/dist/tracking/token.tracker.test.js +272 -0
  41. package/dist/vector/vector.service.js +1 -1
  42. package/package.json +2 -2
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('@hazeljs/core', () => ({
4
+ __esModule: true,
5
+ default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
6
+ }));
7
+ const mockMessagesCreate = jest.fn();
8
+ const mockMessagesStream = jest.fn();
9
+ jest.mock('@anthropic-ai/sdk', () => ({
10
+ __esModule: true,
11
+ default: jest.fn().mockImplementation(() => ({
12
+ messages: {
13
+ create: mockMessagesCreate,
14
+ stream: mockMessagesStream,
15
+ },
16
+ })),
17
+ }));
18
+ const anthropic_provider_1 = require("./anthropic.provider");
19
+ const BASE_REQUEST = {
20
+ messages: [{ role: 'user', content: 'Hello' }],
21
+ model: 'claude-3-5-sonnet-20241022',
22
+ };
23
+ const MOCK_RESPONSE = {
24
+ id: 'msg_001',
25
+ content: [{ type: 'text', text: 'Hello there!' }],
26
+ model: 'claude-3-5-sonnet-20241022',
27
+ stop_reason: 'end_turn',
28
+ usage: { input_tokens: 10, output_tokens: 15 },
29
+ };
30
+ describe('AnthropicProvider', () => {
31
+ let provider;
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ provider = new anthropic_provider_1.AnthropicProvider('test-api-key');
35
+ });
36
+ describe('constructor', () => {
37
+ it('sets name to anthropic', () => {
38
+ expect(provider.name).toBe('anthropic');
39
+ });
40
+ it('warns when no API key provided', () => {
41
+ new anthropic_provider_1.AnthropicProvider();
42
+ // Constructor runs without throwing
43
+ });
44
+ it('uses ANTHROPIC_API_KEY env var', () => {
45
+ process.env.ANTHROPIC_API_KEY = 'env-key';
46
+ const p = new anthropic_provider_1.AnthropicProvider();
47
+ expect(p).toBeDefined();
48
+ delete process.env.ANTHROPIC_API_KEY;
49
+ });
50
+ });
51
+ describe('getSupportedModels()', () => {
52
+ it('returns a list of claude models', () => {
53
+ const models = provider.getSupportedModels();
54
+ expect(models).toContain('claude-3-5-sonnet-20241022');
55
+ expect(models.length).toBeGreaterThan(0);
56
+ });
57
+ });
58
+ describe('complete()', () => {
59
+ it('returns a completion response', async () => {
60
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
61
+ const result = await provider.complete(BASE_REQUEST);
62
+ expect(result.content).toBe('Hello there!');
63
+ expect(result.role).toBe('assistant');
64
+ expect(result.usage?.promptTokens).toBe(10);
65
+ expect(result.usage?.completionTokens).toBe(15);
66
+ expect(result.usage?.totalTokens).toBe(25);
67
+ expect(result.finishReason).toBe('end_turn');
68
+ });
69
+ it('uses default model when not specified', async () => {
70
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
71
+ await provider.complete({ messages: [{ role: 'user', content: 'hi' }] });
72
+ expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-3-5-sonnet-20241022' }));
73
+ });
74
+ it('passes maxTokens when specified', async () => {
75
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
76
+ await provider.complete({ ...BASE_REQUEST, maxTokens: 500 });
77
+ expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ max_tokens: 500 }));
78
+ });
79
+ it('separates system messages from conversation', async () => {
80
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
81
+ await provider.complete({
82
+ messages: [
83
+ { role: 'system', content: 'You are helpful.' },
84
+ { role: 'user', content: 'Hello' },
85
+ ],
86
+ });
87
+ expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({
88
+ system: 'You are helpful.',
89
+ messages: [{ role: 'user', content: 'Hello' }],
90
+ }));
91
+ });
92
+ it('handles response with no stop_reason', async () => {
93
+ mockMessagesCreate.mockResolvedValue({ ...MOCK_RESPONSE, stop_reason: null });
94
+ const result = await provider.complete(BASE_REQUEST);
95
+ expect(result.finishReason).toBe('end_turn');
96
+ });
97
+ it('concatenates multiple text content blocks', async () => {
98
+ mockMessagesCreate.mockResolvedValue({
99
+ ...MOCK_RESPONSE,
100
+ content: [
101
+ { type: 'text', text: 'Part 1. ' },
102
+ { type: 'text', text: 'Part 2.' },
103
+ ],
104
+ });
105
+ const result = await provider.complete(BASE_REQUEST);
106
+ expect(result.content).toBe('Part 1. Part 2.');
107
+ });
108
+ it('ignores non-text content blocks', async () => {
109
+ mockMessagesCreate.mockResolvedValue({
110
+ ...MOCK_RESPONSE,
111
+ content: [
112
+ { type: 'tool_use', id: 'tool_1' },
113
+ { type: 'text', text: 'Some text' },
114
+ ],
115
+ });
116
+ const result = await provider.complete(BASE_REQUEST);
117
+ expect(result.content).toBe('Some text');
118
+ });
119
+ it('throws wrapped error on API failure', async () => {
120
+ mockMessagesCreate.mockRejectedValue(new Error('Rate limit exceeded'));
121
+ await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Anthropic API error: Rate limit exceeded');
122
+ });
123
+ it('handles non-Error thrown objects', async () => {
124
+ mockMessagesCreate.mockRejectedValue('string error');
125
+ await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Anthropic API error: Unknown error');
126
+ });
127
+ it('sets undefined system when no system messages', async () => {
128
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
129
+ await provider.complete({ messages: [{ role: 'user', content: 'hi' }] });
130
+ expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ system: undefined }));
131
+ });
132
+ });
133
+ describe('streamComplete()', () => {
134
+ function makeStreamEvents(events) {
135
+ return (async function* () {
136
+ for (const ev of events) {
137
+ yield ev;
138
+ }
139
+ })();
140
+ }
141
+ it('yields chunks for message_start and content_block_delta events', async () => {
142
+ mockMessagesStream.mockReturnValue(makeStreamEvents([
143
+ { type: 'message_start', message: { id: 'msg_1', usage: { input_tokens: 5 } } },
144
+ { type: 'content_block_delta', delta: { type: 'text_delta', text: 'Hello' } },
145
+ { type: 'content_block_delta', delta: { type: 'text_delta', text: ' world' } },
146
+ { type: 'message_delta', usage: { output_tokens: 10 } },
147
+ { type: 'message_stop' },
148
+ ]));
149
+ const results = [];
150
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
151
+ results.push(chunk);
152
+ }
153
+ // 2 content deltas + 1 message_stop
154
+ expect(results.length).toBeGreaterThan(0);
155
+ });
156
+ it('includes usage in final chunk', async () => {
157
+ mockMessagesStream.mockReturnValue(makeStreamEvents([
158
+ { type: 'message_start', message: { id: 'msg_2', usage: { input_tokens: 8 } } },
159
+ { type: 'message_delta', usage: { output_tokens: 12 } },
160
+ { type: 'message_stop' },
161
+ ]));
162
+ const results = [];
163
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
164
+ results.push(chunk);
165
+ }
166
+ const finalChunk = results[results.length - 1];
167
+ expect(finalChunk.done).toBe(true);
168
+ expect(finalChunk.usage).toBeDefined();
169
+ });
170
+ it('ignores non-text_delta content blocks', async () => {
171
+ mockMessagesStream.mockReturnValue(makeStreamEvents([
172
+ { type: 'content_block_delta', delta: { type: 'input_json_delta', partial_json: '{}' } },
173
+ { type: 'message_stop' },
174
+ ]));
175
+ const results = [];
176
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
177
+ results.push(chunk);
178
+ }
179
+ // Only message_stop chunk
180
+ expect(results).toHaveLength(1);
181
+ });
182
+ it('uses default model for streaming', async () => {
183
+ mockMessagesStream.mockReturnValue(makeStreamEvents([{ type: 'message_stop' }]));
184
+ for await (const _chunk of provider.streamComplete({
185
+ messages: [{ role: 'user', content: 'hi' }],
186
+ })) {
187
+ // consume
188
+ }
189
+ expect(mockMessagesStream).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-3-5-sonnet-20241022' }));
190
+ });
191
+ it('throws wrapped error on streaming failure', async () => {
192
+ mockMessagesStream.mockImplementation(async function* () {
193
+ throw new Error('Stream error');
194
+ yield { type: 'message_stop' };
195
+ });
196
+ await expect(async () => {
197
+ for await (const _chunk of provider.streamComplete(BASE_REQUEST)) {
198
+ // consume
199
+ }
200
+ }).rejects.toThrow('Anthropic streaming error: Stream error');
201
+ });
202
+ });
203
+ describe('embed()', () => {
204
+ it('throws not supported error', async () => {
205
+ await expect(provider.embed({ input: 'test' })).rejects.toThrow('Anthropic does not support embeddings');
206
+ });
207
+ });
208
+ describe('isAvailable()', () => {
209
+ it('returns false when no API key', async () => {
210
+ const p = new anthropic_provider_1.AnthropicProvider('');
211
+ expect(await p.isAvailable()).toBe(false);
212
+ });
213
+ it('returns true when API responds successfully', async () => {
214
+ mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
215
+ expect(await provider.isAvailable()).toBe(true);
216
+ });
217
+ it('returns false on API error', async () => {
218
+ mockMessagesCreate.mockRejectedValue(new Error('Unauthorized'));
219
+ expect(await provider.isAvailable()).toBe(false);
220
+ });
221
+ });
222
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cohere.provider.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cohere.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/cohere.provider.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('@hazeljs/core', () => ({
4
+ __esModule: true,
5
+ default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
6
+ }));
7
+ const mockGenerate = jest.fn();
8
+ const mockGenerateStream = jest.fn();
9
+ const mockEmbed = jest.fn();
10
+ const mockRerank = jest.fn();
11
+ jest.mock('cohere-ai', () => ({
12
+ CohereClient: jest.fn().mockImplementation(() => ({
13
+ generate: mockGenerate,
14
+ generateStream: mockGenerateStream,
15
+ embed: mockEmbed,
16
+ rerank: mockRerank,
17
+ })),
18
+ }));
19
+ const cohere_provider_1 = require("./cohere.provider");
20
+ const BASE_REQUEST = {
21
+ messages: [{ role: 'user', content: 'Hello Cohere' }],
22
+ model: 'command',
23
+ };
24
+ const MOCK_GENERATE_RESPONSE = {
25
+ id: 'cohere-123',
26
+ generations: [{ text: 'Cohere response' }],
27
+ meta: { billedUnits: { inputTokens: 10, outputTokens: 20 } },
28
+ };
29
+ describe('CohereProvider', () => {
30
+ let provider;
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ provider = new cohere_provider_1.CohereProvider('test-api-key');
34
+ });
35
+ describe('constructor', () => {
36
+ it('sets name to cohere', () => {
37
+ expect(provider.name).toBe('cohere');
38
+ });
39
+ it('warns when no API key', () => {
40
+ new cohere_provider_1.CohereProvider(); // Should not throw
41
+ });
42
+ it('uses COHERE_API_KEY env var', () => {
43
+ process.env.COHERE_API_KEY = 'env-key';
44
+ const p = new cohere_provider_1.CohereProvider();
45
+ expect(p).toBeDefined();
46
+ delete process.env.COHERE_API_KEY;
47
+ });
48
+ });
49
+ describe('getSupportedModels()', () => {
50
+ it('returns list of cohere models', () => {
51
+ const models = provider.getSupportedModels();
52
+ expect(models).toContain('command');
53
+ expect(models.length).toBeGreaterThan(0);
54
+ });
55
+ });
56
+ describe('complete()', () => {
57
+ it('returns a completion response', async () => {
58
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
59
+ const result = await provider.complete(BASE_REQUEST);
60
+ expect(result.content).toBe('Cohere response');
61
+ expect(result.role).toBe('assistant');
62
+ expect(result.usage?.promptTokens).toBe(10);
63
+ expect(result.usage?.completionTokens).toBe(20);
64
+ expect(result.usage?.totalTokens).toBe(30);
65
+ expect(result.finishReason).toBe('COMPLETE');
66
+ });
67
+ it('uses default model when not specified', async () => {
68
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
69
+ await provider.complete({ messages: [{ role: 'user', content: 'hi' }] });
70
+ expect(mockGenerate).toHaveBeenCalledWith(expect.objectContaining({ model: 'command' }));
71
+ });
72
+ it('passes temperature and maxTokens to API', async () => {
73
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
74
+ await provider.complete({ ...BASE_REQUEST, temperature: 0.5, maxTokens: 200, topP: 0.9 });
75
+ expect(mockGenerate).toHaveBeenCalledWith(expect.objectContaining({ temperature: 0.5, maxTokens: 200, p: 0.9 }));
76
+ });
77
+ it('handles missing meta/billedUnits gracefully', async () => {
78
+ mockGenerate.mockResolvedValue({
79
+ generations: [{ text: 'ok' }],
80
+ meta: undefined,
81
+ });
82
+ const result = await provider.complete(BASE_REQUEST);
83
+ expect(result.usage?.totalTokens).toBe(0);
84
+ });
85
+ it('uses generated id when response has one', async () => {
86
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
87
+ const result = await provider.complete(BASE_REQUEST);
88
+ expect(result.id).toBe('cohere-123');
89
+ });
90
+ it('generates a fallback id when response has no id', async () => {
91
+ mockGenerate.mockResolvedValue({ ...MOCK_GENERATE_RESPONSE, id: undefined });
92
+ const result = await provider.complete(BASE_REQUEST);
93
+ expect(result.id).toMatch(/^cohere-\d+$/);
94
+ });
95
+ it('converts messages to prompt format', async () => {
96
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
97
+ await provider.complete({
98
+ messages: [
99
+ { role: 'user', content: 'User msg' },
100
+ { role: 'assistant', content: 'Asst msg' },
101
+ ],
102
+ });
103
+ const callArg = mockGenerate.mock.calls[0][0];
104
+ expect(callArg.prompt).toContain('user: User msg');
105
+ expect(callArg.prompt).toContain('assistant: Asst msg');
106
+ });
107
+ it('throws wrapped error on API failure', async () => {
108
+ mockGenerate.mockRejectedValue(new Error('Quota exceeded'));
109
+ await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Cohere API error: Quota exceeded');
110
+ });
111
+ it('wraps non-Error thrown values', async () => {
112
+ mockGenerate.mockRejectedValue('string error');
113
+ await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Cohere API error: Unknown error');
114
+ });
115
+ });
116
+ describe('streamComplete()', () => {
117
+ it('yields text-generation chunks', async () => {
118
+ async function* mockStream() {
119
+ yield { eventType: 'text-generation', text: 'Hello ' };
120
+ yield { eventType: 'text-generation', text: 'world' };
121
+ yield {
122
+ eventType: 'stream-end',
123
+ response: {
124
+ meta: { billedUnits: { inputTokens: 5, outputTokens: 10 } },
125
+ },
126
+ };
127
+ }
128
+ mockGenerateStream.mockReturnValue(mockStream());
129
+ const results = [];
130
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
131
+ results.push(chunk);
132
+ }
133
+ // 2 text chunks + 1 stream-end
134
+ expect(results.length).toBe(3);
135
+ });
136
+ it('yields done=true chunk on stream-end with usage', async () => {
137
+ async function* mockStream() {
138
+ yield {
139
+ eventType: 'stream-end',
140
+ response: { meta: { billedUnits: { inputTokens: 5, outputTokens: 3 } } },
141
+ };
142
+ }
143
+ mockGenerateStream.mockReturnValue(mockStream());
144
+ const results = [];
145
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
146
+ results.push(chunk);
147
+ }
148
+ const last = results[results.length - 1];
149
+ expect(last.done).toBe(true);
150
+ expect(last.usage).toBeDefined();
151
+ });
152
+ it('yields stream-end with undefined usage when no billedUnits', async () => {
153
+ async function* mockStream() {
154
+ yield { eventType: 'stream-end', response: { meta: {} } };
155
+ }
156
+ mockGenerateStream.mockReturnValue(mockStream());
157
+ const results = [];
158
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
159
+ results.push(chunk);
160
+ }
161
+ expect(results[0].usage).toBeUndefined();
162
+ });
163
+ it('ignores unknown event types', async () => {
164
+ async function* mockStream() {
165
+ yield { eventType: 'unknown-event' };
166
+ yield { eventType: 'stream-end', response: {} };
167
+ }
168
+ mockGenerateStream.mockReturnValue(mockStream());
169
+ const results = [];
170
+ for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
171
+ results.push(chunk);
172
+ }
173
+ expect(results.length).toBe(1); // only stream-end
174
+ });
175
+ it('throws wrapped error on streaming failure', async () => {
176
+ mockGenerateStream.mockImplementation(async function* () {
177
+ throw new Error('Stream crashed');
178
+ yield { eventType: 'stream-end' };
179
+ });
180
+ await expect(async () => {
181
+ for await (const _chunk of provider.streamComplete(BASE_REQUEST)) {
182
+ // consume
183
+ }
184
+ }).rejects.toThrow('Cohere streaming error: Stream crashed');
185
+ });
186
+ });
187
+ describe('embed()', () => {
188
+ it('returns embeddings for string array', async () => {
189
+ mockEmbed.mockResolvedValue({
190
+ embeddings: [
191
+ [0.1, 0.2],
192
+ [0.3, 0.4],
193
+ ],
194
+ });
195
+ const result = await provider.embed({ input: ['first', 'second'] });
196
+ expect(result.embeddings).toHaveLength(2);
197
+ expect(result.model).toBe('embed-english-v3.0');
198
+ });
199
+ it('handles single string input', async () => {
200
+ mockEmbed.mockResolvedValue({ embeddings: [[0.5, 0.6]] });
201
+ const result = await provider.embed({ input: 'single' });
202
+ expect(result.embeddings).toHaveLength(1);
203
+ });
204
+ it('handles { float: number[][] } response format', async () => {
205
+ mockEmbed.mockResolvedValue({ embeddings: { float: [[0.7, 0.8]] } });
206
+ const result = await provider.embed({ input: 'test' });
207
+ expect(result.embeddings).toEqual([[0.7, 0.8]]);
208
+ });
209
+ it('uses custom model when specified', async () => {
210
+ mockEmbed.mockResolvedValue({ embeddings: [[0.1]] });
211
+ const result = await provider.embed({ input: 'test', model: 'embed-multilingual-v3.0' });
212
+ expect(result.model).toBe('embed-multilingual-v3.0');
213
+ });
214
+ it('estimates token usage from input length', async () => {
215
+ mockEmbed.mockResolvedValue({ embeddings: [[0.1]] });
216
+ const result = await provider.embed({ input: 'hello world' }); // 11 chars → ~3 tokens
217
+ expect(result.usage?.promptTokens).toBeGreaterThan(0);
218
+ });
219
+ it('throws wrapped error on failure', async () => {
220
+ mockEmbed.mockRejectedValue(new Error('Embedding failed'));
221
+ await expect(provider.embed({ input: 'test' })).rejects.toThrow('Cohere embedding error: Embedding failed');
222
+ });
223
+ });
224
+ describe('isAvailable()', () => {
225
+ it('returns false when no API key', async () => {
226
+ const p = new cohere_provider_1.CohereProvider('');
227
+ expect(await p.isAvailable()).toBe(false);
228
+ });
229
+ it('returns true when API responds', async () => {
230
+ mockGenerate.mockResolvedValue(MOCK_GENERATE_RESPONSE);
231
+ expect(await provider.isAvailable()).toBe(true);
232
+ });
233
+ it('returns false on API error', async () => {
234
+ mockGenerate.mockRejectedValue(new Error('Unauthorized'));
235
+ expect(await provider.isAvailable()).toBe(false);
236
+ });
237
+ });
238
+ describe('rerank()', () => {
239
+ it('returns ranked documents', async () => {
240
+ mockRerank.mockResolvedValue({
241
+ results: [
242
+ { index: 1, relevanceScore: 0.9 },
243
+ { index: 0, relevanceScore: 0.7 },
244
+ ],
245
+ });
246
+ const result = await provider.rerank('query', ['doc0', 'doc1'], 2);
247
+ expect(result).toHaveLength(2);
248
+ expect(result[0].score).toBe(0.9);
249
+ expect(result[0].index).toBe(1);
250
+ expect(result[0].document).toBe('doc1');
251
+ });
252
+ it('uses default rerank model', async () => {
253
+ mockRerank.mockResolvedValue({ results: [] });
254
+ await provider.rerank('query', ['doc1']);
255
+ expect(mockRerank).toHaveBeenCalledWith(expect.objectContaining({ model: 'rerank-english-v3.0' }));
256
+ });
257
+ it('uses custom model when specified', async () => {
258
+ mockRerank.mockResolvedValue({ results: [] });
259
+ await provider.rerank('query', ['doc'], undefined, 'rerank-multilingual-v3.0');
260
+ expect(mockRerank).toHaveBeenCalledWith(expect.objectContaining({ model: 'rerank-multilingual-v3.0' }));
261
+ });
262
+ it('throws wrapped error on rerank failure', async () => {
263
+ mockRerank.mockRejectedValue(new Error('Rerank failed'));
264
+ await expect(provider.rerank('q', ['d'])).rejects.toThrow('Cohere rerank error: Rerank failed');
265
+ });
266
+ });
267
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gemini.provider.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/gemini.provider.test.ts"],"names":[],"mappings":""}