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,303 @@
1
+ /**
2
+ * Message conversion for Kimi Code API.
3
+ * Converts AI SDK messages to Anthropic-compatible format used by Kimi Code.
4
+ * @module
5
+ */
6
+
7
+ import type { LanguageModelV3FilePart, LanguageModelV3Prompt } from '@ai-sdk/provider';
8
+ import { UnsupportedFunctionalityError } from '@ai-sdk/provider';
9
+ import { convertToBase64 } from '@ai-sdk/provider-utils';
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Kimi Code message content part types.
17
+ */
18
+ export type KimiCodeContentPart =
19
+ | { type: 'text'; text: string }
20
+ | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
21
+ | { type: 'image'; source: { type: 'url'; url: string } }
22
+ | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> }
23
+ | { type: 'tool_result'; tool_use_id: string; content: string | KimiCodeContentPart[]; is_error?: boolean };
24
+
25
+ /**
26
+ * Kimi Code message format (Anthropic-compatible).
27
+ */
28
+ export interface KimiCodeMessage {
29
+ role: 'user' | 'assistant';
30
+ content: string | KimiCodeContentPart[];
31
+ }
32
+
33
+ /**
34
+ * Kimi Code prompt structure.
35
+ */
36
+ export interface KimiCodePrompt {
37
+ system?: string;
38
+ messages: KimiCodeMessage[];
39
+ }
40
+
41
+ // ============================================================================
42
+ // Conversion Functions
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Convert AI SDK prompt to Kimi Code message format.
47
+ *
48
+ * @param prompt - AI SDK prompt
49
+ * @returns Kimi Code formatted prompt with system and messages
50
+ */
51
+ export async function convertToKimiCodePrompt(prompt: LanguageModelV3Prompt): Promise<KimiCodePrompt> {
52
+ let systemMessage: string | undefined;
53
+ const messages: KimiCodeMessage[] = [];
54
+
55
+ for (const message of prompt) {
56
+ switch (message.role) {
57
+ case 'system': {
58
+ // System messages have content as string in V3
59
+ const systemText = typeof message.content === 'string' ? message.content : '';
60
+ systemMessage = systemMessage ? `${systemMessage}\n\n${systemText}` : systemText;
61
+ break;
62
+ }
63
+
64
+ case 'user': {
65
+ const content: KimiCodeContentPart[] = [];
66
+
67
+ for (const part of message.content) {
68
+ switch (part.type) {
69
+ case 'text':
70
+ content.push({ type: 'text', text: part.text });
71
+ break;
72
+
73
+ case 'file':
74
+ // Check if it's an image file
75
+ if (part.mediaType?.startsWith('image/')) {
76
+ content.push(await convertFilePart(part));
77
+ } else {
78
+ throw new UnsupportedFunctionalityError({
79
+ functionality: `file type: ${part.mediaType}`
80
+ });
81
+ }
82
+ break;
83
+
84
+ default:
85
+ throw new UnsupportedFunctionalityError({
86
+ functionality: `user content part type: ${(part as { type: string }).type}`
87
+ });
88
+ }
89
+ }
90
+
91
+ messages.push({
92
+ role: 'user',
93
+ content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
94
+ });
95
+ break;
96
+ }
97
+
98
+ case 'assistant': {
99
+ const content: KimiCodeContentPart[] = [];
100
+
101
+ for (const part of message.content) {
102
+ switch (part.type) {
103
+ case 'text':
104
+ content.push({ type: 'text', text: part.text });
105
+ break;
106
+
107
+ case 'tool-call':
108
+ content.push({
109
+ type: 'tool_use',
110
+ id: part.toolCallId,
111
+ name: part.toolName,
112
+ input: typeof part.input === 'string' ? JSON.parse(part.input) : (part.input as Record<string, unknown>)
113
+ });
114
+ break;
115
+
116
+ case 'reasoning':
117
+ // Include reasoning as text (Kimi Code handles thinking blocks)
118
+ if (part.text) {
119
+ content.push({ type: 'text', text: `<thinking>${part.text}</thinking>` });
120
+ }
121
+ break;
122
+
123
+ default:
124
+ throw new UnsupportedFunctionalityError({
125
+ functionality: `assistant content part type: ${(part as { type: string }).type}`
126
+ });
127
+ }
128
+ }
129
+
130
+ if (content.length > 0) {
131
+ messages.push({
132
+ role: 'assistant',
133
+ content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
134
+ });
135
+ }
136
+ break;
137
+ }
138
+
139
+ case 'tool': {
140
+ // Tool results need to be part of a user message in Anthropic format
141
+ const toolResults: KimiCodeContentPart[] = [];
142
+
143
+ for (const part of message.content) {
144
+ if (part.type === 'tool-result') {
145
+ toolResults.push(convertToolResultPart(part));
146
+ }
147
+ }
148
+
149
+ // If the last message is from the assistant, add tool results as user message
150
+ if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
151
+ messages.push({
152
+ role: 'user',
153
+ content: toolResults
154
+ });
155
+ } else {
156
+ // Merge with existing user message or create new one
157
+ const lastMessage = messages[messages.length - 1];
158
+ if (lastMessage?.role === 'user' && Array.isArray(lastMessage.content)) {
159
+ lastMessage.content.push(...toolResults);
160
+ } else {
161
+ messages.push({
162
+ role: 'user',
163
+ content: toolResults
164
+ });
165
+ }
166
+ }
167
+ break;
168
+ }
169
+
170
+ default:
171
+ throw new UnsupportedFunctionalityError({
172
+ functionality: `message role: ${(message as { role: string }).role}`
173
+ });
174
+ }
175
+ }
176
+
177
+ return {
178
+ system: systemMessage,
179
+ messages
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Convert a file part to Kimi Code format.
185
+ */
186
+ async function convertFilePart(part: LanguageModelV3FilePart): Promise<KimiCodeContentPart> {
187
+ const mediaType = part.mediaType ?? 'image/png';
188
+
189
+ // Handle URL data
190
+ if (part.data instanceof URL) {
191
+ return {
192
+ type: 'image',
193
+ source: {
194
+ type: 'url',
195
+ url: part.data.toString()
196
+ }
197
+ };
198
+ }
199
+
200
+ // Handle string data
201
+ if (typeof part.data === 'string') {
202
+ // Check if it's a URL string
203
+ if (part.data.startsWith('http://') || part.data.startsWith('https://')) {
204
+ return {
205
+ type: 'image',
206
+ source: {
207
+ type: 'url',
208
+ url: part.data
209
+ }
210
+ };
211
+ }
212
+
213
+ // Check if it's a data URL
214
+ if (part.data.startsWith('data:')) {
215
+ const [header, data] = part.data.split(',');
216
+ const extractedMimeType = header.match(/data:([^;]+)/)?.[1] ?? mediaType;
217
+ return {
218
+ type: 'image',
219
+ source: {
220
+ type: 'base64',
221
+ media_type: extractedMimeType,
222
+ data
223
+ }
224
+ };
225
+ }
226
+
227
+ // Assume it's base64 encoded
228
+ return {
229
+ type: 'image',
230
+ source: {
231
+ type: 'base64',
232
+ media_type: mediaType,
233
+ data: part.data
234
+ }
235
+ };
236
+ }
237
+
238
+ // Handle Uint8Array - convert to base64
239
+ const base64 = convertToBase64(part.data);
240
+ return {
241
+ type: 'image',
242
+ source: {
243
+ type: 'base64',
244
+ media_type: mediaType,
245
+ data: base64
246
+ }
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Tool result output type from LanguageModelV3.
252
+ */
253
+ interface ToolResultOutput {
254
+ type: 'text' | 'error-text' | 'json' | 'error-json' | 'content' | 'execution-denied';
255
+ value?: unknown;
256
+ reason?: string;
257
+ }
258
+
259
+ /**
260
+ * Convert a tool result part to Kimi Code format.
261
+ */
262
+ function convertToolResultPart(part: {
263
+ type: 'tool-result';
264
+ toolCallId: string;
265
+ toolName: string;
266
+ output: ToolResultOutput;
267
+ }): KimiCodeContentPart {
268
+ // Handle different output types based on the discriminated union
269
+ const output = part.output;
270
+ let content: string;
271
+ let isError = false;
272
+
273
+ switch (output.type) {
274
+ case 'text':
275
+ content = String(output.value ?? '');
276
+ break;
277
+ case 'error-text':
278
+ content = String(output.value ?? '');
279
+ isError = true;
280
+ break;
281
+ case 'execution-denied':
282
+ content = output.reason ?? 'Tool execution denied.';
283
+ isError = true;
284
+ break;
285
+ case 'json':
286
+ case 'content':
287
+ content = JSON.stringify(output.value);
288
+ break;
289
+ case 'error-json':
290
+ content = JSON.stringify(output.value);
291
+ isError = true;
292
+ break;
293
+ default:
294
+ content = JSON.stringify(output);
295
+ }
296
+
297
+ return {
298
+ type: 'tool_result',
299
+ tool_use_id: part.toolCallId,
300
+ content,
301
+ is_error: isError || undefined
302
+ };
303
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Kimi Code provider factory.
3
+ * @module
4
+ */
5
+
6
+ import type { KimiCodeSettings } from './kimi-code-settings';
7
+ import { type LanguageModelV3, NoSuchModelError, type ProviderV3 } from '@ai-sdk/provider';
8
+ import {
9
+ type FetchFunction,
10
+ loadOptionalSetting,
11
+ withUserAgentSuffix,
12
+ withoutTrailingSlash
13
+ } from '@ai-sdk/provider-utils';
14
+ import { VERSION } from '../version';
15
+ import { KimiCodeLanguageModel } from './kimi-code-language-model';
16
+ import { KIMI_CODE_BASE_URL, KIMI_CODE_DEFAULT_MODEL, type KimiCodeModelId } from './kimi-code-types';
17
+
18
+ // ============================================================================
19
+ // Provider Settings
20
+ // ============================================================================
21
+
22
+ /**
23
+ * Settings for creating a Kimi Code provider instance.
24
+ */
25
+ export interface KimiCodeProviderSettings {
26
+ /**
27
+ * Kimi Code API key. Defaults to the KIMI_CODE_API_KEY or KIMI_API_KEY environment variable.
28
+ */
29
+ apiKey?: string;
30
+
31
+ /**
32
+ * Base URL override. Defaults to https://api.kimi.com/coding/v1
33
+ */
34
+ baseURL?: string;
35
+
36
+ /**
37
+ * Default headers for all requests.
38
+ */
39
+ headers?: Record<string, string | undefined>;
40
+
41
+ /**
42
+ * Custom fetch implementation.
43
+ */
44
+ fetch?: FetchFunction;
45
+
46
+ /**
47
+ * ID generator for tool call fallback IDs.
48
+ */
49
+ generateId?: () => string;
50
+
51
+ /**
52
+ * Include usage details in streaming responses.
53
+ */
54
+ includeUsageInStream?: boolean;
55
+
56
+ /**
57
+ * Override supported URL patterns for file parts.
58
+ */
59
+ supportedUrls?: LanguageModelV3['supportedUrls'];
60
+ }
61
+
62
+ // ============================================================================
63
+ // Provider Interface
64
+ // ============================================================================
65
+
66
+ /**
67
+ * The Kimi Code provider interface.
68
+ */
69
+ export interface KimiCodeProvider extends Omit<ProviderV3, 'specificationVersion'> {
70
+ specificationVersion: 'v3';
71
+
72
+ /**
73
+ * Creates a Kimi Code language model.
74
+ * @param modelId - The model identifier (defaults to 'kimi-for-coding')
75
+ * @param settings - Optional model settings
76
+ */
77
+ (modelId?: KimiCodeModelId, settings?: KimiCodeSettings): LanguageModelV3;
78
+
79
+ /**
80
+ * Creates a Kimi Code language model.
81
+ * @param modelId - The model identifier
82
+ * @param settings - Optional model settings
83
+ */
84
+ languageModel(modelId: KimiCodeModelId, settings?: KimiCodeSettings): LanguageModelV3;
85
+
86
+ /**
87
+ * Creates a Kimi Code language model (alias for languageModel).
88
+ * @param modelId - The model identifier
89
+ * @param settings - Optional model settings
90
+ */
91
+ chat(modelId: KimiCodeModelId, settings?: KimiCodeSettings): LanguageModelV3;
92
+ }
93
+
94
+ // ============================================================================
95
+ // Provider Factory
96
+ // ============================================================================
97
+
98
+ /**
99
+ * Create a Kimi Code provider instance.
100
+ *
101
+ * @param options - Provider settings
102
+ * @returns A configured Kimi Code provider
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { createKimiCode } from 'kimi-vercel-ai-sdk-provider
107
+ ';
108
+ *
109
+ * const kimiCode = createKimiCode({
110
+ * apiKey: process.env.KIMI_CODE_API_KEY,
111
+ * });
112
+ *
113
+ * const result = await generateText({
114
+ * model: kimiCode(), // Uses default 'kimi-for-coding' model
115
+ * prompt: 'Write a TypeScript function to merge two sorted arrays',
116
+ * });
117
+ * ```
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // With extended thinking enabled
122
+ * const result = await generateText({
123
+ * model: kimiCode('kimi-for-coding', {
124
+ * extendedThinking: {
125
+ * enabled: true,
126
+ * effort: 'high'
127
+ * }
128
+ * }),
129
+ * prompt: 'Design a distributed cache system',
130
+ * });
131
+ * ```
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * // Using with Claude Code compatible settings
136
+ * const result = await generateText({
137
+ * model: kimiCode('kimi-k2-thinking'),
138
+ * prompt: 'Explain and fix this bug',
139
+ * });
140
+ * ```
141
+ */
142
+ export function createKimiCode(options: KimiCodeProviderSettings = {}): KimiCodeProvider {
143
+ const resolvedBaseURL =
144
+ loadOptionalSetting({
145
+ settingValue: options.baseURL,
146
+ environmentVariableName: 'KIMI_CODE_BASE_URL'
147
+ }) ?? KIMI_CODE_BASE_URL;
148
+
149
+ const baseURL = withoutTrailingSlash(resolvedBaseURL) ?? KIMI_CODE_BASE_URL;
150
+
151
+ const getHeaders = () => {
152
+ // Try KIMI_CODE_API_KEY first, fall back to KIMI_API_KEY
153
+ let apiKey = options.apiKey;
154
+ if (!apiKey) {
155
+ apiKey = process.env.KIMI_CODE_API_KEY ?? process.env.KIMI_API_KEY;
156
+ }
157
+ if (!apiKey) {
158
+ throw new Error(
159
+ 'Kimi Code API key is required. Set the KIMI_CODE_API_KEY or KIMI_API_KEY environment variable, or pass the apiKey option.'
160
+ );
161
+ }
162
+
163
+ return withUserAgentSuffix(
164
+ {
165
+ 'x-api-key': apiKey,
166
+ 'anthropic-version': '2023-06-01',
167
+ ...options.headers
168
+ },
169
+ `ai-sdk/kimi-code/${VERSION}`
170
+ );
171
+ };
172
+
173
+ const createCodeModel = (modelId: KimiCodeModelId = KIMI_CODE_DEFAULT_MODEL, settings: KimiCodeSettings = {}) =>
174
+ new KimiCodeLanguageModel(modelId, settings, {
175
+ provider: 'kimi.code',
176
+ baseURL,
177
+ headers: getHeaders,
178
+ fetch: options.fetch,
179
+ generateId: options.generateId,
180
+ includeUsageInStream: settings.includeUsageInStream ?? options.includeUsageInStream,
181
+ supportedUrls: settings.supportedUrls ?? options.supportedUrls
182
+ });
183
+
184
+ const provider: KimiCodeProvider = (
185
+ modelId: KimiCodeModelId = KIMI_CODE_DEFAULT_MODEL,
186
+ settings?: KimiCodeSettings
187
+ ): KimiCodeLanguageModel => {
188
+ if (new.target) {
189
+ throw new Error('The Kimi Code provider function cannot be called with new.');
190
+ }
191
+
192
+ return createCodeModel(modelId, settings);
193
+ };
194
+
195
+ provider.specificationVersion = 'v3';
196
+ provider.languageModel = createCodeModel;
197
+ provider.chat = createCodeModel;
198
+
199
+ provider.embeddingModel = (modelId: string) => {
200
+ throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });
201
+ };
202
+
203
+ provider.imageModel = (modelId: string) => {
204
+ throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
205
+ };
206
+
207
+ provider.rerankingModel = (modelId: string) => {
208
+ throw new NoSuchModelError({ modelId, modelType: 'rerankingModel' });
209
+ };
210
+
211
+ return provider;
212
+ }
213
+
214
+ /**
215
+ * Default Kimi Code provider instance.
216
+ *
217
+ * Uses the KIMI_CODE_API_KEY or KIMI_API_KEY environment variable for authentication.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * import { kimiCode } from 'kimi-vercel-ai-sdk-provider
222
+ ';
223
+ *
224
+ * const result = await generateText({
225
+ * model: kimiCode(), // Uses default model
226
+ * prompt: 'Implement a binary search tree',
227
+ * });
228
+ * ```
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * // With extended thinking
233
+ * const result = await generateText({
234
+ * model: kimiCode('kimi-for-coding', { extendedThinking: true }),
235
+ * prompt: 'Design a microservices architecture',
236
+ * });
237
+ * ```
238
+ */
239
+ export const kimiCode = createKimiCode();