kimi-vercel-ai-sdk-provider 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kimi-vercel-ai-sdk-provider",
3
3
  "description": "Kimi (Moonshot AI) provider for Vercel AI SDK v6",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -15,6 +15,9 @@
15
15
  "scripts": {
16
16
  "build": "tsup",
17
17
  "clean": "del-cli dist *.tsbuildinfo",
18
+ "format": "biome format --write",
19
+ "lint": "biome lint --write",
20
+ "lint:fix": "biome lint --write",
18
21
  "type-check": "tsc --noEmit",
19
22
  "test": "vitest run",
20
23
  "test:watch": "vitest",
@@ -1,9 +1,27 @@
1
- import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
1
+ import type { LanguageModelV3StreamPart } from '@ai-sdk/provider';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
3
  import { createKimiCode } from '../code';
3
4
 
4
5
  // Mock fetch for API testing
5
6
  const mockFetch = vi.fn();
6
7
 
8
+ async function readStreamParts<T>(stream: ReadableStream<T>) {
9
+ const reader = stream.getReader();
10
+ const parts: T[] = [];
11
+
12
+ while (true) {
13
+ const { value, done } = await reader.read();
14
+ if (done) {
15
+ break;
16
+ }
17
+ if (value !== undefined) {
18
+ parts.push(value);
19
+ }
20
+ }
21
+
22
+ return parts;
23
+ }
24
+
7
25
  describe('KimiCodeLanguageModel Integration', () => {
8
26
  const originalEnv = process.env;
9
27
 
@@ -147,7 +165,7 @@ describe('KimiCodeLanguageModel Integration', () => {
147
165
 
148
166
  const result = await model.doGenerate({
149
167
  prompt: [{ role: 'user', content: [{ type: 'text', text: 'Read the index file' }] }],
150
-
168
+
151
169
  tools: [
152
170
  {
153
171
  type: 'function',
@@ -198,8 +216,7 @@ describe('KimiCodeLanguageModel Integration', () => {
198
216
  });
199
217
 
200
218
  await model.doGenerate({
201
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think hard' }] }],
202
-
219
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think hard' }] }]
203
220
  });
204
221
 
205
222
  const body = JSON.parse(mockFetch.mock.calls[0][1].body);
@@ -239,8 +256,7 @@ describe('KimiCodeLanguageModel Integration', () => {
239
256
  const model = provider('kimi-for-coding');
240
257
 
241
258
  const result = await model.doGenerate({
242
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
243
-
259
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
244
260
  });
245
261
 
246
262
  expect(result.usage.inputTokens.total).toBe(100);
@@ -273,8 +289,7 @@ describe('KimiCodeLanguageModel Integration', () => {
273
289
 
274
290
  await expect(
275
291
  model.doGenerate({
276
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
277
-
292
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
278
293
  })
279
294
  ).rejects.toThrow('Invalid API key');
280
295
  });
@@ -305,7 +320,7 @@ describe('KimiCodeLanguageModel Integration', () => {
305
320
 
306
321
  const result = await model.doGenerate({
307
322
  prompt: [{ role: 'user', content: [{ type: 'text', text: 'Write a long story' }] }],
308
-
323
+
309
324
  maxOutputTokens: 100
310
325
  });
311
326
 
@@ -350,14 +365,10 @@ describe('KimiCodeLanguageModel Integration', () => {
350
365
  const model = provider('kimi-for-coding');
351
366
 
352
367
  const result = await model.doStream({
353
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Say hello' }] }],
354
-
368
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Say hello' }] }]
355
369
  });
356
370
 
357
- const parts: any[] = [];
358
- for await (const part of result.stream) {
359
- parts.push(part);
360
- }
371
+ const parts = await readStreamParts<LanguageModelV3StreamPart>(result.stream);
361
372
 
362
373
  // Verify streaming worked
363
374
  expect(parts.length).toBeGreaterThan(0);
@@ -409,14 +420,10 @@ describe('KimiCodeLanguageModel Integration', () => {
409
420
  const model = provider('kimi-k2-thinking');
410
421
 
411
422
  const result = await model.doStream({
412
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think about this' }] }],
413
-
423
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think about this' }] }]
414
424
  });
415
425
 
416
- const parts: any[] = [];
417
- for await (const part of result.stream) {
418
- parts.push(part);
419
- }
426
+ const parts = await readStreamParts<LanguageModelV3StreamPart>(result.stream);
420
427
 
421
428
  const partTypes = parts.map((p) => p.type);
422
429
  expect(partTypes).toContain('reasoning-start');
@@ -463,7 +470,7 @@ describe('KimiCodeLanguageModel Integration', () => {
463
470
 
464
471
  const result = await model.doStream({
465
472
  prompt: [{ role: 'user', content: [{ type: 'text', text: 'Read the file' }] }],
466
-
473
+
467
474
  tools: [
468
475
  {
469
476
  type: 'function',
@@ -474,10 +481,7 @@ describe('KimiCodeLanguageModel Integration', () => {
474
481
  ]
475
482
  });
476
483
 
477
- const parts: any[] = [];
478
- for await (const part of result.stream) {
479
- parts.push(part);
480
- }
484
+ const parts = await readStreamParts<LanguageModelV3StreamPart>(result.stream);
481
485
 
482
486
  const partTypes = parts.map((p) => p.type);
483
487
  expect(partTypes).toContain('tool-input-start');
@@ -485,9 +489,12 @@ describe('KimiCodeLanguageModel Integration', () => {
485
489
  expect(partTypes).toContain('tool-input-end');
486
490
  expect(partTypes).toContain('tool-call');
487
491
 
488
- const toolCallPart = parts.find((p) => p.type === 'tool-call');
489
- expect(toolCallPart.toolName).toBe('read_file');
490
- expect(toolCallPart.input).toContain('/src/index.ts');
492
+ const toolCallPart = parts.find(
493
+ (p): p is Extract<LanguageModelV3StreamPart, { type: 'tool-call' }> => p.type === 'tool-call'
494
+ );
495
+ expect(toolCallPart).toBeDefined();
496
+ expect(toolCallPart?.toolName).toBe('read_file');
497
+ expect(toolCallPart?.input).toContain('/src/index.ts');
491
498
  });
492
499
  });
493
500
 
@@ -517,8 +524,7 @@ describe('KimiCodeLanguageModel Integration', () => {
517
524
  const model = provider('kimi-for-coding');
518
525
 
519
526
  await model.doGenerate({
520
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
521
-
527
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
522
528
  });
523
529
 
524
530
  const headers = mockFetch.mock.calls[0][1].headers;
@@ -1,12 +1,11 @@
1
- import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import {
3
- createKimiCode,
4
- kimiCode,
5
- KimiCodeLanguageModel,
6
- KIMI_CODE_BASE_URL,
7
3
  KIMI_CODE_DEFAULT_MODEL,
8
4
  KIMI_CODE_THINKING_MODEL,
9
- inferKimiCodeCapabilities
5
+ KimiCodeLanguageModel,
6
+ createKimiCode,
7
+ inferKimiCodeCapabilities,
8
+ kimiCode
10
9
  } from '../code';
11
10
 
12
11
  describe('createKimiCode', () => {
@@ -171,9 +171,7 @@ describe('toAnthropicThinking', () => {
171
171
 
172
172
  describe('convertToKimiCodePrompt', () => {
173
173
  it('should convert simple user message', async () => {
174
- const result = await convertToKimiCodePrompt([
175
- { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }
176
- ]);
174
+ const result = await convertToKimiCodePrompt([{ role: 'user', content: [{ type: 'text', text: 'Hello!' }] }]);
177
175
 
178
176
  expect(result.system).toBeUndefined();
179
177
  expect(result.messages).toEqual([{ role: 'user', content: 'Hello!' }]);
@@ -42,8 +42,9 @@ describe('createKimi', () => {
42
42
  it('should throw when called with new', () => {
43
43
  const provider = createKimi();
44
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();
45
+ // Test that the provider function throws when used as a constructor
46
+ const ProviderAsConstructor = provider as unknown as new (modelId: string) => unknown;
47
+ expect(() => new ProviderAsConstructor('kimi-k2.5')).toThrow();
47
48
  });
48
49
 
49
50
  it('should throw for unsupported model types', () => {
@@ -765,6 +765,26 @@ const kimiTokenUsageSchema = z
765
765
  })
766
766
  .nullish();
767
767
 
768
+ /**
769
+ * Schema for content parts in response messages.
770
+ * Can be text, image, or other content types.
771
+ */
772
+ const kimiContentPartSchema = z.union([
773
+ z.object({
774
+ type: z.literal('text'),
775
+ text: z.string()
776
+ }),
777
+ z.object({
778
+ type: z.literal('image_url'),
779
+ image_url: z.object({
780
+ url: z.string()
781
+ })
782
+ }),
783
+ z.looseObject({
784
+ type: z.string()
785
+ })
786
+ ]);
787
+
768
788
  const kimiChatResponseSchema = z.looseObject({
769
789
  id: z.string().nullish(),
770
790
  created: z.number().nullish(),
@@ -773,7 +793,7 @@ const kimiChatResponseSchema = z.looseObject({
773
793
  z.object({
774
794
  message: z.object({
775
795
  role: z.string().nullish(),
776
- content: z.union([z.string(), z.array(z.any())]).nullish(),
796
+ content: z.union([z.string(), z.array(kimiContentPartSchema)]).nullish(),
777
797
  reasoning_content: z.string().nullish(),
778
798
  reasoning: z.string().nullish(),
779
799
  tool_calls: z
@@ -19,7 +19,7 @@ export const kimiErrorSchema = z.union([
19
19
  error: z.object({
20
20
  message: z.string(),
21
21
  type: z.string().nullish(),
22
- param: z.any().nullish(),
22
+ param: z.string().nullish(),
23
23
  code: z.union([z.string(), z.number()]).nullish(),
24
24
  request_id: z.string().nullish()
25
25
  })
@@ -257,7 +257,8 @@ function guessFilename(attachment: Attachment, contentType: string): string {
257
257
  const urlPath = attachment.url.split('?')[0];
258
258
  const segments = urlPath.split('/');
259
259
  const lastSegment = segments[segments.length - 1];
260
- if (lastSegment && lastSegment.includes('.')) {
260
+
261
+ if (lastSegment.includes('.')) {
261
262
  return lastSegment;
262
263
  }
263
264
  }