kimi-vercel-ai-sdk-provider 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +198 -0
  2. package/README.md +871 -0
  3. package/dist/index.d.mts +1317 -0
  4. package/dist/index.d.ts +1317 -0
  5. package/dist/index.js +2764 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +2734 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +70 -0
  10. package/src/__tests__/caching.test.ts +97 -0
  11. package/src/__tests__/chat.test.ts +386 -0
  12. package/src/__tests__/code-integration.test.ts +562 -0
  13. package/src/__tests__/code-provider.test.ts +289 -0
  14. package/src/__tests__/code.test.ts +427 -0
  15. package/src/__tests__/core.test.ts +172 -0
  16. package/src/__tests__/files.test.ts +185 -0
  17. package/src/__tests__/integration.test.ts +457 -0
  18. package/src/__tests__/provider.test.ts +188 -0
  19. package/src/__tests__/tools.test.ts +519 -0
  20. package/src/chat/index.ts +42 -0
  21. package/src/chat/kimi-chat-language-model.ts +829 -0
  22. package/src/chat/kimi-chat-messages.ts +297 -0
  23. package/src/chat/kimi-chat-response.ts +84 -0
  24. package/src/chat/kimi-chat-settings.ts +216 -0
  25. package/src/code/index.ts +66 -0
  26. package/src/code/kimi-code-language-model.ts +669 -0
  27. package/src/code/kimi-code-messages.ts +303 -0
  28. package/src/code/kimi-code-provider.ts +239 -0
  29. package/src/code/kimi-code-settings.ts +193 -0
  30. package/src/code/kimi-code-types.ts +354 -0
  31. package/src/core/errors.ts +140 -0
  32. package/src/core/index.ts +36 -0
  33. package/src/core/types.ts +148 -0
  34. package/src/core/utils.ts +210 -0
  35. package/src/files/attachment-processor.ts +276 -0
  36. package/src/files/file-utils.ts +257 -0
  37. package/src/files/index.ts +24 -0
  38. package/src/files/kimi-file-client.ts +292 -0
  39. package/src/index.ts +122 -0
  40. package/src/kimi-provider.ts +263 -0
  41. package/src/tools/builtin-tools.ts +273 -0
  42. package/src/tools/index.ts +33 -0
  43. package/src/tools/prepare-tools.ts +306 -0
  44. package/src/version.ts +4 -0
@@ -0,0 +1,457 @@
1
+ /**
2
+ * Integration tests with mocked API responses.
3
+ * These tests verify the full flow from model creation to response parsing.
4
+ * @module
5
+ */
6
+
7
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { createKimi } from '../index';
9
+
10
+ // Mock fetch globally
11
+ const mockFetch = vi.fn();
12
+ global.fetch = mockFetch;
13
+
14
+ /**
15
+ * Helper to create a properly mocked fetch Response
16
+ */
17
+ function createMockResponse(data: unknown, status = 200, headers: Record<string, string> = {}) {
18
+ const jsonStr = JSON.stringify(data);
19
+ return {
20
+ ok: status >= 200 && status < 300,
21
+ status,
22
+ headers: new Headers({
23
+ 'content-type': 'application/json',
24
+ ...headers
25
+ }),
26
+ json: async () => data,
27
+ text: async () => jsonStr,
28
+ clone: function () {
29
+ return this;
30
+ }
31
+ };
32
+ }
33
+
34
+ describe('KimiChatLanguageModel integration', () => {
35
+ const originalEnv = process.env;
36
+
37
+ beforeAll(() => {
38
+ process.env = { ...originalEnv };
39
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
40
+ });
41
+
42
+ afterAll(() => {
43
+ process.env = originalEnv;
44
+ });
45
+
46
+ beforeEach(() => {
47
+ mockFetch.mockReset();
48
+ });
49
+
50
+ afterEach(() => {
51
+ vi.clearAllMocks();
52
+ });
53
+
54
+ describe('doGenerate', () => {
55
+ it('should make a successful non-streaming request', async () => {
56
+ const mockResponse = {
57
+ id: 'chatcmpl-123',
58
+ object: 'chat.completion',
59
+ created: 1677652288,
60
+ model: 'kimi-k2.5',
61
+ choices: [
62
+ {
63
+ index: 0,
64
+ message: {
65
+ role: 'assistant',
66
+ content: 'Hello! How can I help you today?'
67
+ },
68
+ finish_reason: 'stop'
69
+ }
70
+ ],
71
+ usage: {
72
+ prompt_tokens: 10,
73
+ completion_tokens: 15,
74
+ total_tokens: 25
75
+ }
76
+ };
77
+
78
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse, 200, { 'x-request-id': 'req-123' }));
79
+
80
+ const provider = createKimi();
81
+ const model = provider('kimi-k2.5');
82
+
83
+ const result = await model.doGenerate({
84
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }]
85
+ });
86
+
87
+ // Check content array has text
88
+ expect(result.content).toHaveLength(1);
89
+ expect(result.content[0]).toMatchObject({
90
+ type: 'text',
91
+ text: 'Hello! How can I help you today?'
92
+ });
93
+
94
+ // finishReason is an object with unified/raw
95
+ expect(result.finishReason.unified).toBe('stop');
96
+ // Usage has detailed token breakdown
97
+ expect(result.usage.inputTokens.total).toBe(10);
98
+ expect(result.usage.outputTokens.total).toBe(15);
99
+
100
+ // Verify request was made correctly
101
+ expect(mockFetch).toHaveBeenCalledTimes(1);
102
+ const [url, options] = mockFetch.mock.calls[0];
103
+ expect(url).toContain('/chat/completions');
104
+ expect(options.method).toBe('POST');
105
+ // Headers may be a Headers object or plain object
106
+ const authHeader = options.headers.Authorization || options.headers.authorization;
107
+ expect(authHeader).toBe('Bearer test-api-key');
108
+
109
+ const body = JSON.parse(options.body);
110
+ expect(body.model).toBe('kimi-k2.5');
111
+ // stream may be undefined (default) or false
112
+ expect(body.stream).toBeFalsy();
113
+ });
114
+
115
+ it('should handle tool calls in response', async () => {
116
+ const mockResponse = {
117
+ id: 'chatcmpl-456',
118
+ object: 'chat.completion',
119
+ created: 1677652288,
120
+ model: 'kimi-k2.5',
121
+ choices: [
122
+ {
123
+ index: 0,
124
+ message: {
125
+ role: 'assistant',
126
+ content: null,
127
+ tool_calls: [
128
+ {
129
+ id: 'call_abc123',
130
+ type: 'function',
131
+ function: {
132
+ name: 'get_weather',
133
+ arguments: '{"location":"San Francisco"}'
134
+ }
135
+ }
136
+ ]
137
+ },
138
+ finish_reason: 'tool_calls'
139
+ }
140
+ ],
141
+ usage: {
142
+ prompt_tokens: 20,
143
+ completion_tokens: 30,
144
+ total_tokens: 50
145
+ }
146
+ };
147
+
148
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
149
+
150
+ const provider = createKimi();
151
+ const model = provider('kimi-k2.5');
152
+
153
+ const result = await model.doGenerate({
154
+ tools: [
155
+ {
156
+ type: 'function',
157
+ name: 'get_weather',
158
+ description: 'Get weather for a location',
159
+ inputSchema: {
160
+ type: 'object',
161
+ properties: {
162
+ location: { type: 'string' }
163
+ },
164
+ required: ['location']
165
+ }
166
+ }
167
+ ],
168
+ prompt: [{ role: 'user', content: [{ type: 'text', text: "What's the weather in SF?" }] }]
169
+ });
170
+
171
+ expect(result.finishReason.unified).toBe('tool-calls');
172
+ expect(result.content).toHaveLength(1);
173
+ expect(result.content[0]).toMatchObject({
174
+ type: 'tool-call',
175
+ toolCallId: 'call_abc123',
176
+ toolName: 'get_weather'
177
+ });
178
+ });
179
+
180
+ it('should handle web search tool in request', async () => {
181
+ const mockResponse = {
182
+ id: 'chatcmpl-789',
183
+ object: 'chat.completion',
184
+ created: 1677652288,
185
+ model: 'kimi-k2.5',
186
+ choices: [
187
+ {
188
+ index: 0,
189
+ message: {
190
+ role: 'assistant',
191
+ content: 'Based on my web search, here are the latest AI news...',
192
+ tool_calls: [
193
+ {
194
+ id: 'web_search_call',
195
+ type: 'builtin_function',
196
+ function: {
197
+ name: '$web_search',
198
+ arguments: '{}'
199
+ },
200
+ search_result: {
201
+ search_result_tokens: 500
202
+ }
203
+ }
204
+ ]
205
+ },
206
+ finish_reason: 'stop'
207
+ }
208
+ ],
209
+ usage: {
210
+ prompt_tokens: 15,
211
+ completion_tokens: 100,
212
+ total_tokens: 115
213
+ }
214
+ };
215
+
216
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
217
+
218
+ const provider = createKimi();
219
+ const model = provider('kimi-k2.5', { webSearch: true });
220
+
221
+ const result = await model.doGenerate({
222
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'What are the latest AI news?' }] }]
223
+ });
224
+
225
+ // Check content has text
226
+ const textContent = result.content.find((c) => c.type === 'text');
227
+ expect(textContent).toBeDefined();
228
+ expect((textContent as { type: 'text'; text: string }).text).toBe(
229
+ 'Based on my web search, here are the latest AI news...'
230
+ );
231
+ expect(result.finishReason.unified).toBe('stop');
232
+
233
+ // Verify web search was included in request
234
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
235
+ expect(body.tools).toBeDefined();
236
+ expect(body.tools.some((t: { function: { name: string } }) => t.function?.name === '$web_search')).toBe(true);
237
+ });
238
+
239
+ it('should handle API errors', async () => {
240
+ const errorResponse = {
241
+ error: {
242
+ message: 'Invalid API key',
243
+ type: 'authentication_error',
244
+ code: 'invalid_api_key'
245
+ }
246
+ };
247
+ mockFetch.mockResolvedValueOnce(createMockResponse(errorResponse, 401));
248
+
249
+ const provider = createKimi();
250
+ const model = provider('kimi-k2.5');
251
+
252
+ await expect(
253
+ model.doGenerate({
254
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }]
255
+ })
256
+ ).rejects.toThrow();
257
+ });
258
+
259
+ it('should handle rate limit errors', async () => {
260
+ const errorResponse = {
261
+ error: {
262
+ message: 'Rate limit exceeded',
263
+ type: 'rate_limit_error'
264
+ }
265
+ };
266
+ mockFetch.mockResolvedValueOnce(createMockResponse(errorResponse, 429, { 'retry-after': '60' }));
267
+
268
+ const provider = createKimi();
269
+ const model = provider('kimi-k2.5');
270
+
271
+ await expect(
272
+ model.doGenerate({
273
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }]
274
+ })
275
+ ).rejects.toThrow();
276
+ });
277
+ });
278
+
279
+ describe('doStream', () => {
280
+ it('should verify streaming request is made correctly', async () => {
281
+ // For streaming tests, we just verify the request is constructed correctly
282
+ // Full streaming integration testing requires more complex SSE mocking
283
+ const mockResponse = {
284
+ id: 'chatcmpl-stream',
285
+ object: 'chat.completion',
286
+ created: 1677652288,
287
+ model: 'kimi-k2.5',
288
+ choices: [
289
+ {
290
+ index: 0,
291
+ message: { role: 'assistant', content: 'Hello!' },
292
+ finish_reason: 'stop'
293
+ }
294
+ ],
295
+ usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 }
296
+ };
297
+
298
+ // Use non-streaming response for this test to verify request format
299
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
300
+
301
+ const provider = createKimi();
302
+ const model = provider('kimi-k2.5');
303
+
304
+ // Use doGenerate to verify request construction (doStream has same request format)
305
+ await model.doGenerate({
306
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hi' }] }]
307
+ });
308
+
309
+ // Verify request was made correctly
310
+ expect(mockFetch).toHaveBeenCalledTimes(1);
311
+ const [url, options] = mockFetch.mock.calls[0];
312
+ expect(url).toContain('/chat/completions');
313
+ expect(options.method).toBe('POST');
314
+
315
+ const body = JSON.parse(options.body);
316
+ expect(body.model).toBe('kimi-k2.5');
317
+ expect(body.messages).toHaveLength(1);
318
+ expect(body.messages[0].role).toBe('user');
319
+ });
320
+ });
321
+
322
+ describe('provider options', () => {
323
+ it('should pass kimi provider options', async () => {
324
+ const mockResponse = {
325
+ id: 'chatcmpl-opts',
326
+ object: 'chat.completion',
327
+ created: 1677652288,
328
+ model: 'kimi-k2.5',
329
+ choices: [
330
+ {
331
+ index: 0,
332
+ message: { role: 'assistant', content: 'Response' },
333
+ finish_reason: 'stop'
334
+ }
335
+ ],
336
+ usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 }
337
+ };
338
+
339
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
340
+
341
+ const provider = createKimi();
342
+ const model = provider('kimi-k2.5');
343
+
344
+ await model.doGenerate({
345
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
346
+ providerOptions: {
347
+ kimi: {
348
+ // Use the correct schema format: boolean | { enabled: boolean, config?: {...} }
349
+ webSearch: { enabled: true, config: { search_result: true } }
350
+ }
351
+ }
352
+ });
353
+
354
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
355
+ expect(body.tools).toBeDefined();
356
+ expect(body.tools.some((t: { function: { name: string } }) => t.function?.name === '$web_search')).toBe(true);
357
+ });
358
+ });
359
+
360
+ describe('JSON mode', () => {
361
+ it('should set response_format for JSON mode via responseFormat option', async () => {
362
+ const mockResponse = {
363
+ id: 'chatcmpl-json',
364
+ object: 'chat.completion',
365
+ created: 1677652288,
366
+ model: 'kimi-k2.5',
367
+ choices: [
368
+ {
369
+ index: 0,
370
+ message: { role: 'assistant', content: '{"result": "test"}' },
371
+ finish_reason: 'stop'
372
+ }
373
+ ],
374
+ usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 }
375
+ };
376
+
377
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
378
+
379
+ const provider = createKimi();
380
+ const model = provider('kimi-k2.5');
381
+
382
+ await model.doGenerate({
383
+ responseFormat: { type: 'json' },
384
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Return JSON' }] }]
385
+ });
386
+
387
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
388
+ expect(body.response_format).toEqual({ type: 'json_object' });
389
+ });
390
+ });
391
+
392
+ describe('request construction', () => {
393
+ it('should include temperature and other parameters', async () => {
394
+ const mockResponse = {
395
+ id: 'chatcmpl-params',
396
+ object: 'chat.completion',
397
+ created: 1677652288,
398
+ model: 'kimi-k2.5',
399
+ choices: [
400
+ {
401
+ index: 0,
402
+ message: { role: 'assistant', content: 'Response' },
403
+ finish_reason: 'stop'
404
+ }
405
+ ],
406
+ usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 }
407
+ };
408
+
409
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
410
+
411
+ const provider = createKimi();
412
+ const model = provider('kimi-k2.5');
413
+
414
+ await model.doGenerate({
415
+ temperature: 0.7,
416
+ maxOutputTokens: 1000,
417
+ topP: 0.9,
418
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
419
+ });
420
+
421
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
422
+ expect(body.temperature).toBe(0.7);
423
+ expect(body.max_tokens).toBe(1000);
424
+ expect(body.top_p).toBe(0.9);
425
+ });
426
+
427
+ it('should use correct endpoint', async () => {
428
+ const mockResponse = {
429
+ id: 'chatcmpl-endpoint',
430
+ object: 'chat.completion',
431
+ created: 1677652288,
432
+ model: 'kimi-k2.5',
433
+ choices: [
434
+ {
435
+ index: 0,
436
+ message: { role: 'assistant', content: 'Response' },
437
+ finish_reason: 'stop'
438
+ }
439
+ ],
440
+ usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 }
441
+ };
442
+
443
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResponse));
444
+
445
+ // Test global endpoint
446
+ const globalProvider = createKimi({ endpoint: 'global' });
447
+ const globalModel = globalProvider('kimi-k2.5');
448
+
449
+ await globalModel.doGenerate({
450
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
451
+ });
452
+
453
+ const [url] = mockFetch.mock.calls[0];
454
+ expect(url).toContain('api.moonshot.ai');
455
+ });
456
+ });
457
+ });
@@ -0,0 +1,188 @@
1
+ import type { KimiChatLanguageModel } from '../chat';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { createKimi, kimi, kimiTools } from '../index';
4
+
5
+ describe('createKimi', () => {
6
+ const originalEnv = process.env;
7
+
8
+ beforeEach(() => {
9
+ vi.resetModules();
10
+ process.env = { ...originalEnv };
11
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
12
+ });
13
+
14
+ afterEach(() => {
15
+ process.env = originalEnv;
16
+ });
17
+
18
+ it('should create a provider with default settings', () => {
19
+ const provider = createKimi();
20
+ expect(provider.specificationVersion).toBe('v3');
21
+ });
22
+
23
+ it('should create a chat model', () => {
24
+ const provider = createKimi();
25
+ const model = provider('kimi-k2.5');
26
+ expect(model.modelId).toBe('kimi-k2.5');
27
+ expect(model.specificationVersion).toBe('v3');
28
+ });
29
+
30
+ it('should create a chat model via languageModel method', () => {
31
+ const provider = createKimi();
32
+ const model = provider.languageModel('kimi-k2-turbo');
33
+ expect(model.modelId).toBe('kimi-k2-turbo');
34
+ });
35
+
36
+ it('should create a chat model via chat method', () => {
37
+ const provider = createKimi();
38
+ const model = provider.chat('kimi-k2.5-thinking');
39
+ expect(model.modelId).toBe('kimi-k2.5-thinking');
40
+ });
41
+
42
+ it('should throw when called with new', () => {
43
+ const provider = createKimi();
44
+ // The error can be either our custom message or the native constructor error
45
+ // biome-ignore lint/suspicious/noExplicitAny: testing edge case requires any
46
+ expect(() => new (provider as any)('kimi-k2.5')).toThrow();
47
+ });
48
+
49
+ it('should throw for unsupported model types', () => {
50
+ const provider = createKimi();
51
+
52
+ expect(() => provider.embeddingModel('some-model')).toThrow();
53
+ expect(() => provider.imageModel('some-model')).toThrow();
54
+ expect(() => provider.rerankingModel!('some-model')).toThrow();
55
+ });
56
+
57
+ it('should include tools object', () => {
58
+ const provider = createKimi();
59
+ expect(provider.tools).toBeDefined();
60
+ expect(provider.tools.webSearch).toBeDefined();
61
+ expect(provider.tools.codeInterpreter).toBeDefined();
62
+ });
63
+
64
+ it('should pass settings to model', () => {
65
+ const provider = createKimi();
66
+ const model = provider('kimi-k2.5', { webSearch: true });
67
+ expect(model.modelId).toBe('kimi-k2.5');
68
+ });
69
+ });
70
+
71
+ describe('default kimi provider', () => {
72
+ beforeEach(() => {
73
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
74
+ });
75
+
76
+ it('should be a valid provider', () => {
77
+ expect(kimi.specificationVersion).toBe('v3');
78
+ });
79
+
80
+ it('should create models', () => {
81
+ const model = kimi('kimi-k2.5');
82
+ expect(model.modelId).toBe('kimi-k2.5');
83
+ });
84
+ });
85
+
86
+ describe('kimiTools', () => {
87
+ it('should create web search tool', () => {
88
+ const tool = kimiTools.webSearch();
89
+ expect(tool.type).toBe('provider');
90
+ expect(tool.id).toBe('kimi.webSearch');
91
+ });
92
+
93
+ it('should create code interpreter tool', () => {
94
+ const tool = kimiTools.codeInterpreter();
95
+ expect(tool.type).toBe('provider');
96
+ expect(tool.id).toBe('kimi.codeInterpreter');
97
+ });
98
+ });
99
+
100
+ describe('provider endpoints', () => {
101
+ beforeEach(() => {
102
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
103
+ });
104
+
105
+ it('should use global endpoint by default', () => {
106
+ const provider = createKimi();
107
+ const model = provider('kimi-k2.5');
108
+ // Can't directly test baseURL but the provider should work
109
+ expect(model.provider).toBe('kimi.chat');
110
+ });
111
+
112
+ it('should allow cn endpoint selection', () => {
113
+ const provider = createKimi({ endpoint: 'cn' });
114
+ const model = provider('kimi-k2.5');
115
+ expect(model.provider).toBe('kimi.chat');
116
+ });
117
+
118
+ it('should allow custom baseURL', () => {
119
+ const provider = createKimi({ baseURL: 'https://custom.api.com/v1' });
120
+ const model = provider('kimi-k2.5');
121
+ expect(model.provider).toBe('kimi.chat');
122
+ });
123
+ });
124
+
125
+ describe('model capabilities', () => {
126
+ beforeEach(() => {
127
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
128
+ });
129
+
130
+ it('should detect thinking model capabilities', () => {
131
+ const model = kimi('kimi-k2.5-thinking') as KimiChatLanguageModel;
132
+ expect(model.capabilities.thinking).toBe(true);
133
+ expect(model.capabilities.alwaysThinking).toBe(true);
134
+ });
135
+
136
+ it('should detect K2.5 video support', () => {
137
+ const model = kimi('kimi-k2.5') as KimiChatLanguageModel;
138
+ expect(model.capabilities.videoInput).toBe(true);
139
+ });
140
+
141
+ it('should not have video for K2 turbo', () => {
142
+ const model = kimi('kimi-k2-turbo') as KimiChatLanguageModel;
143
+ expect(model.capabilities.videoInput).toBe(false);
144
+ });
145
+
146
+ it('should allow capability overrides', () => {
147
+ const model = kimi('kimi-k2-turbo', {
148
+ capabilities: { videoInput: true }
149
+ }) as KimiChatLanguageModel;
150
+ expect(model.capabilities.videoInput).toBe(true);
151
+ });
152
+ });
153
+
154
+ describe('supportedUrls', () => {
155
+ beforeEach(() => {
156
+ process.env.MOONSHOT_API_KEY = 'test-api-key';
157
+ });
158
+
159
+ it('should support image URLs for all models', async () => {
160
+ const model = kimi('kimi-k2-turbo') as KimiChatLanguageModel;
161
+ const supportedUrls = await Promise.resolve(model.supportedUrls);
162
+ expect(supportedUrls['image/*']).toBeDefined();
163
+ expect(supportedUrls['image/*'][0]).toBeInstanceOf(RegExp);
164
+ });
165
+
166
+ it('should support video URLs for K2.5 models', async () => {
167
+ const model = kimi('kimi-k2.5') as KimiChatLanguageModel;
168
+ const supportedUrls = await Promise.resolve(model.supportedUrls);
169
+ expect(supportedUrls['video/*']).toBeDefined();
170
+ });
171
+
172
+ it('should not support video URLs for non-K2.5 models', async () => {
173
+ const model = kimi('kimi-k2-turbo') as KimiChatLanguageModel;
174
+ const supportedUrls = await Promise.resolve(model.supportedUrls);
175
+ expect(supportedUrls['video/*']).toBeUndefined();
176
+ });
177
+
178
+ it('should allow custom supportedUrls override', async () => {
179
+ const customUrls = {
180
+ 'image/*': [/^https:\/\/cdn\.example\.com/]
181
+ };
182
+ const model = kimi('kimi-k2.5', {
183
+ supportedUrls: customUrls
184
+ }) as KimiChatLanguageModel;
185
+ const supportedUrls = await Promise.resolve(model.supportedUrls);
186
+ expect(supportedUrls).toEqual(customUrls);
187
+ });
188
+ });