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.
- package/LICENSE +198 -0
- package/README.md +871 -0
- package/dist/index.d.mts +1317 -0
- package/dist/index.d.ts +1317 -0
- package/dist/index.js +2764 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2734 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
- package/src/__tests__/caching.test.ts +97 -0
- package/src/__tests__/chat.test.ts +386 -0
- package/src/__tests__/code-integration.test.ts +562 -0
- package/src/__tests__/code-provider.test.ts +289 -0
- package/src/__tests__/code.test.ts +427 -0
- package/src/__tests__/core.test.ts +172 -0
- package/src/__tests__/files.test.ts +185 -0
- package/src/__tests__/integration.test.ts +457 -0
- package/src/__tests__/provider.test.ts +188 -0
- package/src/__tests__/tools.test.ts +519 -0
- package/src/chat/index.ts +42 -0
- package/src/chat/kimi-chat-language-model.ts +829 -0
- package/src/chat/kimi-chat-messages.ts +297 -0
- package/src/chat/kimi-chat-response.ts +84 -0
- package/src/chat/kimi-chat-settings.ts +216 -0
- package/src/code/index.ts +66 -0
- package/src/code/kimi-code-language-model.ts +669 -0
- package/src/code/kimi-code-messages.ts +303 -0
- package/src/code/kimi-code-provider.ts +239 -0
- package/src/code/kimi-code-settings.ts +193 -0
- package/src/code/kimi-code-types.ts +354 -0
- package/src/core/errors.ts +140 -0
- package/src/core/index.ts +36 -0
- package/src/core/types.ts +148 -0
- package/src/core/utils.ts +210 -0
- package/src/files/attachment-processor.ts +276 -0
- package/src/files/file-utils.ts +257 -0
- package/src/files/index.ts +24 -0
- package/src/files/kimi-file-client.ts +292 -0
- package/src/index.ts +122 -0
- package/src/kimi-provider.ts +263 -0
- package/src/tools/builtin-tools.ts +273 -0
- package/src/tools/index.ts +33 -0
- package/src/tools/prepare-tools.ts +306 -0
- 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
|
+
});
|