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,829 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi chat language model implementation.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
InvalidResponseDataError,
|
|
8
|
+
type JSONValue,
|
|
9
|
+
type LanguageModelV3,
|
|
10
|
+
type LanguageModelV3CallOptions,
|
|
11
|
+
type LanguageModelV3Content,
|
|
12
|
+
type LanguageModelV3FinishReason,
|
|
13
|
+
type LanguageModelV3GenerateResult,
|
|
14
|
+
type LanguageModelV3StreamPart,
|
|
15
|
+
type LanguageModelV3StreamResult,
|
|
16
|
+
type SharedV3ProviderMetadata,
|
|
17
|
+
type SharedV3Warning
|
|
18
|
+
} from '@ai-sdk/provider';
|
|
19
|
+
import {
|
|
20
|
+
type ParseResult,
|
|
21
|
+
combineHeaders,
|
|
22
|
+
createEventSourceResponseHandler,
|
|
23
|
+
createJsonResponseHandler,
|
|
24
|
+
generateId,
|
|
25
|
+
isParsableJson,
|
|
26
|
+
parseProviderOptions,
|
|
27
|
+
postJsonToApi,
|
|
28
|
+
removeUndefinedEntries
|
|
29
|
+
} from '@ai-sdk/provider-utils';
|
|
30
|
+
import { z } from 'zod/v4';
|
|
31
|
+
import { kimiErrorSchema, kimiFailedResponseHandler } from '../core';
|
|
32
|
+
import { type KimiCodeInterpreterToolOptions, type KimiWebSearchToolOptions, prepareKimiTools } from '../tools';
|
|
33
|
+
import { convertToKimiChatMessages } from './kimi-chat-messages';
|
|
34
|
+
import {
|
|
35
|
+
convertKimiUsage,
|
|
36
|
+
extractCodeInterpreterTokens,
|
|
37
|
+
extractMessageContent,
|
|
38
|
+
extractWebSearchTokens,
|
|
39
|
+
getKimiRequestId,
|
|
40
|
+
getResponseMetadata,
|
|
41
|
+
mapKimiFinishReason
|
|
42
|
+
} from './kimi-chat-response';
|
|
43
|
+
import {
|
|
44
|
+
type KimiCachingConfig,
|
|
45
|
+
type KimiChatConfig,
|
|
46
|
+
type KimiChatModelId,
|
|
47
|
+
type KimiChatSettings,
|
|
48
|
+
type KimiProviderOptions,
|
|
49
|
+
inferModelCapabilities,
|
|
50
|
+
kimiProviderOptionsSchema
|
|
51
|
+
} from './kimi-chat-settings';
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Language Model Implementation
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Kimi chat language model implementing LanguageModelV3.
|
|
59
|
+
*/
|
|
60
|
+
export class KimiChatLanguageModel implements LanguageModelV3 {
|
|
61
|
+
readonly specificationVersion = 'v3';
|
|
62
|
+
readonly modelId: KimiChatModelId;
|
|
63
|
+
|
|
64
|
+
private readonly config: KimiChatConfig;
|
|
65
|
+
private readonly settings: KimiChatSettings;
|
|
66
|
+
private readonly generateIdFn: () => string;
|
|
67
|
+
private readonly supportsStructuredOutputs: boolean;
|
|
68
|
+
|
|
69
|
+
constructor(modelId: KimiChatModelId, settings: KimiChatSettings, config: KimiChatConfig) {
|
|
70
|
+
this.modelId = modelId;
|
|
71
|
+
this.settings = settings;
|
|
72
|
+
this.config = config;
|
|
73
|
+
this.generateIdFn = config.generateId ?? generateId;
|
|
74
|
+
this.supportsStructuredOutputs = config.supportsStructuredOutputs ?? false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get provider(): string {
|
|
78
|
+
return this.config.provider;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private get providerOptionsName(): string {
|
|
82
|
+
return this.config.provider.split('.')[0].trim();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the inferred or configured capabilities for this model.
|
|
87
|
+
*/
|
|
88
|
+
get capabilities() {
|
|
89
|
+
const inferred = inferModelCapabilities(this.modelId);
|
|
90
|
+
return {
|
|
91
|
+
...inferred,
|
|
92
|
+
...this.settings.capabilities
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get supportedUrls() {
|
|
97
|
+
const caps = this.capabilities;
|
|
98
|
+
const patterns: Record<string, RegExp[]> = {
|
|
99
|
+
'image/*': [/^https?:\/\/.*$/i]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Add video support for models that support it
|
|
103
|
+
if (caps.videoInput) {
|
|
104
|
+
patterns['video/*'] = [/^https?:\/\/.*$/i];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return this.settings.supportedUrls ?? this.config.supportedUrls ?? patterns;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async getArgs({
|
|
111
|
+
prompt,
|
|
112
|
+
maxOutputTokens,
|
|
113
|
+
temperature,
|
|
114
|
+
topP,
|
|
115
|
+
topK,
|
|
116
|
+
frequencyPenalty,
|
|
117
|
+
presencePenalty,
|
|
118
|
+
stopSequences,
|
|
119
|
+
responseFormat,
|
|
120
|
+
seed,
|
|
121
|
+
providerOptions,
|
|
122
|
+
tools,
|
|
123
|
+
toolChoice
|
|
124
|
+
}: LanguageModelV3CallOptions) {
|
|
125
|
+
const warnings: SharedV3Warning[] = [];
|
|
126
|
+
|
|
127
|
+
const deprecatedOptions = await parseProviderOptions({
|
|
128
|
+
provider: 'moonshot',
|
|
129
|
+
providerOptions,
|
|
130
|
+
schema: kimiProviderOptionsSchema
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (deprecatedOptions != null) {
|
|
134
|
+
warnings.push({
|
|
135
|
+
type: 'other',
|
|
136
|
+
message: "The 'moonshot' key in providerOptions is deprecated. Use 'kimi' instead."
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const providerOptionsName = this.providerOptionsName;
|
|
141
|
+
const kimiOptions = await parseProviderOptions({
|
|
142
|
+
provider: providerOptionsName,
|
|
143
|
+
providerOptions,
|
|
144
|
+
schema: kimiProviderOptionsSchema
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const options: KimiProviderOptions = {
|
|
148
|
+
...(deprecatedOptions ?? {}),
|
|
149
|
+
...(kimiOptions ?? {})
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (topK != null) {
|
|
153
|
+
warnings.push({ type: 'unsupported', feature: 'topK' });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const strictJsonSchema = options.strictJsonSchema ?? true;
|
|
157
|
+
|
|
158
|
+
if (responseFormat?.type === 'json' && responseFormat.schema != null && !this.supportsStructuredOutputs) {
|
|
159
|
+
warnings.push({
|
|
160
|
+
type: 'unsupported',
|
|
161
|
+
feature: 'responseFormat',
|
|
162
|
+
details: 'JSON schema response format requires structured outputs support.'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Resolve web search configuration from settings and provider options
|
|
167
|
+
const webSearch = resolveBuiltinToolConfig(this.settings.webSearch, options.webSearch);
|
|
168
|
+
|
|
169
|
+
// Resolve code interpreter configuration from settings and provider options
|
|
170
|
+
const codeInterpreter = resolveBuiltinToolConfig(this.settings.codeInterpreter, options.codeInterpreter);
|
|
171
|
+
|
|
172
|
+
// Resolve tool choice polyfill setting
|
|
173
|
+
const toolChoicePolyfill = options.toolChoicePolyfill ?? this.settings.toolChoicePolyfill ?? true;
|
|
174
|
+
|
|
175
|
+
const {
|
|
176
|
+
tools: kimiTools,
|
|
177
|
+
toolChoice: kimiToolChoice,
|
|
178
|
+
toolWarnings,
|
|
179
|
+
toolChoiceSystemMessage
|
|
180
|
+
} = prepareKimiTools({
|
|
181
|
+
tools,
|
|
182
|
+
toolChoice,
|
|
183
|
+
webSearch,
|
|
184
|
+
codeInterpreter,
|
|
185
|
+
toolChoicePolyfill
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Resolve caching configuration
|
|
189
|
+
const caching = resolveCachingConfig(this.settings.caching, options.caching);
|
|
190
|
+
|
|
191
|
+
// Build caching headers
|
|
192
|
+
const cachingHeaders = buildCachingHeaders(caching);
|
|
193
|
+
|
|
194
|
+
const passthroughOptions = getPassthroughOptions({
|
|
195
|
+
providerOptions,
|
|
196
|
+
providerOptionsName,
|
|
197
|
+
deprecatedProviderOptionsName: 'moonshot',
|
|
198
|
+
knownKeys: Object.keys(kimiProviderOptionsSchema.shape)
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Convert messages and optionally inject tool choice system message
|
|
202
|
+
const messages = convertToKimiChatMessages(prompt);
|
|
203
|
+
if (toolChoiceSystemMessage) {
|
|
204
|
+
// Prepend the tool choice instruction as a system message
|
|
205
|
+
messages.unshift({ role: 'system', content: toolChoiceSystemMessage });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const body = removeUndefinedEntries({
|
|
209
|
+
model: this.modelId,
|
|
210
|
+
messages,
|
|
211
|
+
max_tokens: maxOutputTokens,
|
|
212
|
+
temperature,
|
|
213
|
+
top_p: topP,
|
|
214
|
+
frequency_penalty: frequencyPenalty,
|
|
215
|
+
presence_penalty: presencePenalty,
|
|
216
|
+
stop: stopSequences,
|
|
217
|
+
seed,
|
|
218
|
+
response_format:
|
|
219
|
+
responseFormat?.type === 'json'
|
|
220
|
+
? this.supportsStructuredOutputs && responseFormat.schema != null
|
|
221
|
+
? {
|
|
222
|
+
type: 'json_schema',
|
|
223
|
+
json_schema: {
|
|
224
|
+
schema: responseFormat.schema,
|
|
225
|
+
strict: strictJsonSchema,
|
|
226
|
+
name: responseFormat.name ?? 'response',
|
|
227
|
+
description: responseFormat.description
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
: { type: 'json_object' }
|
|
231
|
+
: undefined,
|
|
232
|
+
tools: kimiTools,
|
|
233
|
+
tool_choice: kimiToolChoice,
|
|
234
|
+
user: options.user,
|
|
235
|
+
...(kimiTools != null && options.parallelToolCalls != null
|
|
236
|
+
? { parallel_tool_calls: options.parallelToolCalls }
|
|
237
|
+
: {}),
|
|
238
|
+
...passthroughOptions
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const requestHeaders: Record<string, string | undefined> = {
|
|
242
|
+
...(options.requestId ? { 'X-Request-ID': options.requestId } : {}),
|
|
243
|
+
...(options.extraHeaders ?? {}),
|
|
244
|
+
...cachingHeaders
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
body,
|
|
249
|
+
warnings: [...warnings, ...toolWarnings],
|
|
250
|
+
requestHeaders
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult> {
|
|
255
|
+
const { body, warnings, requestHeaders } = await this.getArgs(options);
|
|
256
|
+
|
|
257
|
+
const {
|
|
258
|
+
responseHeaders,
|
|
259
|
+
value: response,
|
|
260
|
+
rawValue
|
|
261
|
+
} = await postJsonToApi({
|
|
262
|
+
url: `${this.config.baseURL}/chat/completions`,
|
|
263
|
+
headers: combineHeaders(this.config.headers(), requestHeaders, options.headers),
|
|
264
|
+
body,
|
|
265
|
+
failedResponseHandler: kimiFailedResponseHandler,
|
|
266
|
+
successfulResponseHandler: createJsonResponseHandler(kimiChatResponseSchema),
|
|
267
|
+
abortSignal: options.abortSignal,
|
|
268
|
+
fetch: this.config.fetch
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const choice = response.choices[0];
|
|
272
|
+
const content: Array<LanguageModelV3Content> = [];
|
|
273
|
+
|
|
274
|
+
const { text, reasoning } = extractMessageContent(choice.message);
|
|
275
|
+
|
|
276
|
+
if (reasoning.length > 0) {
|
|
277
|
+
content.push({ type: 'reasoning', text: reasoning });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (text.length > 0) {
|
|
281
|
+
content.push({ type: 'text', text });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (choice.message.tool_calls != null) {
|
|
285
|
+
for (const toolCall of choice.message.tool_calls) {
|
|
286
|
+
content.push({
|
|
287
|
+
type: 'tool-call',
|
|
288
|
+
toolCallId: toolCall.id ?? this.generateIdFn(),
|
|
289
|
+
toolName: toolCall.function.name,
|
|
290
|
+
input: toolCall.function.arguments ?? ''
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Extract built-in tool token usage from tool calls
|
|
296
|
+
const webSearchTokens = extractWebSearchTokens(choice.message.tool_calls);
|
|
297
|
+
const codeInterpreterTokens = extractCodeInterpreterTokens(choice.message.tool_calls);
|
|
298
|
+
|
|
299
|
+
const providerMetadata = buildProviderMetadata({
|
|
300
|
+
providerOptionsName: this.providerOptionsName,
|
|
301
|
+
responseHeaders,
|
|
302
|
+
webSearchTokens,
|
|
303
|
+
codeInterpreterTokens
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
content,
|
|
308
|
+
finishReason: {
|
|
309
|
+
unified: mapKimiFinishReason(choice.finish_reason),
|
|
310
|
+
raw: choice.finish_reason ?? undefined
|
|
311
|
+
},
|
|
312
|
+
usage: convertKimiUsage(response.usage, webSearchTokens, codeInterpreterTokens),
|
|
313
|
+
...(providerMetadata ? { providerMetadata } : {}),
|
|
314
|
+
request: { body },
|
|
315
|
+
response: {
|
|
316
|
+
...getResponseMetadata(response),
|
|
317
|
+
headers: responseHeaders,
|
|
318
|
+
body: rawValue
|
|
319
|
+
},
|
|
320
|
+
warnings
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {
|
|
325
|
+
const { body, warnings, requestHeaders } = await this.getArgs(options);
|
|
326
|
+
const streamBody = {
|
|
327
|
+
...body,
|
|
328
|
+
stream: true,
|
|
329
|
+
stream_options: this.config.includeUsageInStream ? { include_usage: true } : undefined
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const { responseHeaders, value: response } = await postJsonToApi({
|
|
333
|
+
url: `${this.config.baseURL}/chat/completions`,
|
|
334
|
+
headers: combineHeaders(this.config.headers(), requestHeaders, options.headers),
|
|
335
|
+
body: streamBody,
|
|
336
|
+
failedResponseHandler: kimiFailedResponseHandler,
|
|
337
|
+
successfulResponseHandler: createEventSourceResponseHandler(kimiChatChunkSchema),
|
|
338
|
+
abortSignal: options.abortSignal,
|
|
339
|
+
fetch: this.config.fetch
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const requestId = getKimiRequestId(responseHeaders);
|
|
343
|
+
|
|
344
|
+
let finishReason: LanguageModelV3FinishReason = {
|
|
345
|
+
unified: 'other',
|
|
346
|
+
raw: undefined
|
|
347
|
+
};
|
|
348
|
+
let usage: z.infer<typeof kimiTokenUsageSchema> | undefined;
|
|
349
|
+
|
|
350
|
+
let isFirstChunk = true;
|
|
351
|
+
let isActiveText = false;
|
|
352
|
+
let isActiveReasoning = false;
|
|
353
|
+
|
|
354
|
+
const toolCalls: Array<{
|
|
355
|
+
id: string;
|
|
356
|
+
type: 'function';
|
|
357
|
+
function: { name: string; arguments: string };
|
|
358
|
+
hasFinished: boolean;
|
|
359
|
+
}> = [];
|
|
360
|
+
|
|
361
|
+
const providerOptionsName = this.providerOptionsName;
|
|
362
|
+
const _generateIdFn = this.generateIdFn;
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
stream: response.pipeThrough(
|
|
366
|
+
new TransformStream<ParseResult<z.infer<typeof kimiChatChunkSchema>>, LanguageModelV3StreamPart>({
|
|
367
|
+
start(controller) {
|
|
368
|
+
controller.enqueue({ type: 'stream-start', warnings });
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
transform(chunk, controller) {
|
|
372
|
+
if (options.includeRawChunks) {
|
|
373
|
+
controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!chunk.success) {
|
|
377
|
+
finishReason = { unified: 'error', raw: undefined };
|
|
378
|
+
controller.enqueue({ type: 'error', error: chunk.error });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if ('error' in chunk.value) {
|
|
383
|
+
finishReason = { unified: 'error', raw: undefined };
|
|
384
|
+
const error = (chunk.value as { error?: { message?: string } }).error;
|
|
385
|
+
controller.enqueue({
|
|
386
|
+
type: 'error',
|
|
387
|
+
error: error?.message ?? chunk.value
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const value = chunk.value as z.infer<typeof kimiChatChunkBaseSchema>;
|
|
393
|
+
|
|
394
|
+
if (isFirstChunk) {
|
|
395
|
+
isFirstChunk = false;
|
|
396
|
+
controller.enqueue({
|
|
397
|
+
type: 'response-metadata',
|
|
398
|
+
...getResponseMetadata(value)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (value.usage != null) {
|
|
403
|
+
usage = value.usage;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const choice = value.choices[0];
|
|
407
|
+
if (!choice) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (choice.finish_reason != null) {
|
|
412
|
+
finishReason = {
|
|
413
|
+
unified: mapKimiFinishReason(choice.finish_reason),
|
|
414
|
+
raw: choice.finish_reason ?? undefined
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const delta = choice.delta;
|
|
419
|
+
if (!delta) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
|
|
424
|
+
if (reasoningDelta) {
|
|
425
|
+
if (!isActiveReasoning) {
|
|
426
|
+
if (isActiveText) {
|
|
427
|
+
controller.enqueue({ type: 'text-end', id: 'text-0' });
|
|
428
|
+
isActiveText = false;
|
|
429
|
+
}
|
|
430
|
+
controller.enqueue({
|
|
431
|
+
type: 'reasoning-start',
|
|
432
|
+
id: 'reasoning-0'
|
|
433
|
+
});
|
|
434
|
+
isActiveReasoning = true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
controller.enqueue({
|
|
438
|
+
type: 'reasoning-delta',
|
|
439
|
+
id: 'reasoning-0',
|
|
440
|
+
delta: reasoningDelta
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (delta.content) {
|
|
445
|
+
if (isActiveReasoning) {
|
|
446
|
+
controller.enqueue({ type: 'reasoning-end', id: 'reasoning-0' });
|
|
447
|
+
isActiveReasoning = false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!isActiveText) {
|
|
451
|
+
controller.enqueue({ type: 'text-start', id: 'text-0' });
|
|
452
|
+
isActiveText = true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
controller.enqueue({
|
|
456
|
+
type: 'text-delta',
|
|
457
|
+
id: 'text-0',
|
|
458
|
+
delta: delta.content
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (delta.tool_calls != null) {
|
|
463
|
+
if (isActiveReasoning) {
|
|
464
|
+
controller.enqueue({ type: 'reasoning-end', id: 'reasoning-0' });
|
|
465
|
+
isActiveReasoning = false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
469
|
+
const index = toolCallDelta.index ?? toolCalls.length;
|
|
470
|
+
|
|
471
|
+
if (toolCalls[index] == null) {
|
|
472
|
+
if (toolCallDelta.id == null) {
|
|
473
|
+
throw new InvalidResponseDataError({
|
|
474
|
+
data: toolCallDelta,
|
|
475
|
+
message: "Expected 'id' to be a string."
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (toolCallDelta.function?.name == null) {
|
|
480
|
+
throw new InvalidResponseDataError({
|
|
481
|
+
data: toolCallDelta,
|
|
482
|
+
message: "Expected 'function.name' to be a string."
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
controller.enqueue({
|
|
487
|
+
type: 'tool-input-start',
|
|
488
|
+
id: toolCallDelta.id,
|
|
489
|
+
toolName: toolCallDelta.function.name
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
toolCalls[index] = {
|
|
493
|
+
id: toolCallDelta.id,
|
|
494
|
+
type: 'function',
|
|
495
|
+
function: {
|
|
496
|
+
name: toolCallDelta.function.name,
|
|
497
|
+
arguments: toolCallDelta.function.arguments ?? ''
|
|
498
|
+
},
|
|
499
|
+
hasFinished: false
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const toolCall = toolCalls[index];
|
|
503
|
+
|
|
504
|
+
if (toolCall.function.arguments.length > 0) {
|
|
505
|
+
controller.enqueue({
|
|
506
|
+
type: 'tool-input-delta',
|
|
507
|
+
id: toolCall.id,
|
|
508
|
+
delta: toolCall.function.arguments
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (isParsableJson(toolCall.function.arguments)) {
|
|
513
|
+
controller.enqueue({ type: 'tool-input-end', id: toolCall.id });
|
|
514
|
+
|
|
515
|
+
controller.enqueue({
|
|
516
|
+
type: 'tool-call',
|
|
517
|
+
toolCallId: toolCall.id,
|
|
518
|
+
toolName: toolCall.function.name,
|
|
519
|
+
input: toolCall.function.arguments
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
toolCall.hasFinished = true;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const toolCall = toolCalls[index];
|
|
529
|
+
if (toolCall.hasFinished) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (toolCallDelta.function?.arguments != null) {
|
|
534
|
+
toolCall.function.arguments += toolCallDelta.function.arguments;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
controller.enqueue({
|
|
538
|
+
type: 'tool-input-delta',
|
|
539
|
+
id: toolCall.id,
|
|
540
|
+
delta: toolCallDelta.function?.arguments ?? ''
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (isParsableJson(toolCall.function.arguments)) {
|
|
544
|
+
controller.enqueue({ type: 'tool-input-end', id: toolCall.id });
|
|
545
|
+
|
|
546
|
+
controller.enqueue({
|
|
547
|
+
type: 'tool-call',
|
|
548
|
+
toolCallId: toolCall.id,
|
|
549
|
+
toolName: toolCall.function.name,
|
|
550
|
+
input: toolCall.function.arguments
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
toolCall.hasFinished = true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
flush(controller) {
|
|
560
|
+
if (isActiveReasoning) {
|
|
561
|
+
controller.enqueue({ type: 'reasoning-end', id: 'reasoning-0' });
|
|
562
|
+
isActiveReasoning = false;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (isActiveText) {
|
|
566
|
+
controller.enqueue({ type: 'text-end', id: 'text-0' });
|
|
567
|
+
isActiveText = false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for (const toolCall of toolCalls.filter((call) => !call.hasFinished)) {
|
|
571
|
+
controller.enqueue({ type: 'tool-input-end', id: toolCall.id });
|
|
572
|
+
controller.enqueue({
|
|
573
|
+
type: 'tool-call',
|
|
574
|
+
toolCallId: toolCall.id,
|
|
575
|
+
toolName: toolCall.function.name,
|
|
576
|
+
input: toolCall.function.arguments
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Extract built-in tool tokens from accumulated tool calls
|
|
581
|
+
const webSearchTokens = extractWebSearchTokens(toolCalls);
|
|
582
|
+
const codeInterpreterTokens = extractCodeInterpreterTokens(toolCalls);
|
|
583
|
+
|
|
584
|
+
const providerMetadata: SharedV3ProviderMetadata | undefined =
|
|
585
|
+
requestId || webSearchTokens != null || codeInterpreterTokens != null
|
|
586
|
+
? {
|
|
587
|
+
[providerOptionsName]: {
|
|
588
|
+
...(requestId ? { requestId } : {}),
|
|
589
|
+
...(webSearchTokens != null ? { webSearchTokens } : {}),
|
|
590
|
+
...(codeInterpreterTokens != null ? { codeInterpreterTokens } : {})
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
: undefined;
|
|
594
|
+
|
|
595
|
+
controller.enqueue({
|
|
596
|
+
type: 'finish',
|
|
597
|
+
finishReason,
|
|
598
|
+
usage: convertKimiUsage(usage, webSearchTokens, codeInterpreterTokens),
|
|
599
|
+
...(providerMetadata ? { providerMetadata } : {})
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
})
|
|
603
|
+
),
|
|
604
|
+
request: { body: streamBody },
|
|
605
|
+
response: { headers: responseHeaders }
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ============================================================================
|
|
611
|
+
// Helper Functions
|
|
612
|
+
// ============================================================================
|
|
613
|
+
|
|
614
|
+
function buildProviderMetadata({
|
|
615
|
+
providerOptionsName,
|
|
616
|
+
responseHeaders,
|
|
617
|
+
webSearchTokens,
|
|
618
|
+
codeInterpreterTokens
|
|
619
|
+
}: {
|
|
620
|
+
providerOptionsName: string;
|
|
621
|
+
responseHeaders?: Record<string, string>;
|
|
622
|
+
webSearchTokens?: number;
|
|
623
|
+
codeInterpreterTokens?: number;
|
|
624
|
+
}): SharedV3ProviderMetadata | undefined {
|
|
625
|
+
const requestId = getKimiRequestId(responseHeaders);
|
|
626
|
+
|
|
627
|
+
if (!requestId && webSearchTokens == null && codeInterpreterTokens == null) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
[providerOptionsName]: {
|
|
633
|
+
...(requestId ? { requestId } : {}),
|
|
634
|
+
...(webSearchTokens != null ? { webSearchTokens } : {}),
|
|
635
|
+
...(codeInterpreterTokens != null ? { codeInterpreterTokens } : {})
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function getPassthroughOptions({
|
|
641
|
+
providerOptions,
|
|
642
|
+
providerOptionsName,
|
|
643
|
+
deprecatedProviderOptionsName,
|
|
644
|
+
knownKeys
|
|
645
|
+
}: {
|
|
646
|
+
providerOptions: LanguageModelV3CallOptions['providerOptions'];
|
|
647
|
+
providerOptionsName: string;
|
|
648
|
+
deprecatedProviderOptionsName: string;
|
|
649
|
+
knownKeys: string[];
|
|
650
|
+
}) {
|
|
651
|
+
const rawOptions = [providerOptions?.[deprecatedProviderOptionsName], providerOptions?.[providerOptionsName]].filter(
|
|
652
|
+
(entry): entry is Record<string, JSONValue | undefined> => entry != null && typeof entry === 'object'
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
const passthrough: Record<string, JSONValue | undefined> = {};
|
|
656
|
+
|
|
657
|
+
for (const options of rawOptions) {
|
|
658
|
+
for (const [key, value] of Object.entries(options ?? {})) {
|
|
659
|
+
if (!knownKeys.includes(key)) {
|
|
660
|
+
passthrough[key] = value;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return passthrough;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
type BuiltinToolOptions = boolean | KimiWebSearchToolOptions | KimiCodeInterpreterToolOptions | undefined;
|
|
669
|
+
|
|
670
|
+
function resolveBuiltinToolConfig<T extends BuiltinToolOptions>(
|
|
671
|
+
settingsConfig: T | undefined,
|
|
672
|
+
optionsConfig: T | undefined
|
|
673
|
+
): T | undefined {
|
|
674
|
+
// Provider options take precedence
|
|
675
|
+
if (optionsConfig != null) {
|
|
676
|
+
if (typeof optionsConfig === 'boolean') {
|
|
677
|
+
return optionsConfig ? ({ enabled: true } as T) : undefined;
|
|
678
|
+
}
|
|
679
|
+
const config = optionsConfig as { enabled: boolean };
|
|
680
|
+
return config.enabled ? optionsConfig : undefined;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Fall back to settings
|
|
684
|
+
if (settingsConfig != null) {
|
|
685
|
+
if (typeof settingsConfig === 'boolean') {
|
|
686
|
+
return settingsConfig ? ({ enabled: true } as T) : undefined;
|
|
687
|
+
}
|
|
688
|
+
const config = settingsConfig as { enabled: boolean };
|
|
689
|
+
return config.enabled ? settingsConfig : undefined;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return undefined;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
type CachingOptions = boolean | KimiCachingConfig | undefined;
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Resolve caching configuration from settings and provider options.
|
|
699
|
+
*/
|
|
700
|
+
function resolveCachingConfig(
|
|
701
|
+
settingsConfig: CachingOptions,
|
|
702
|
+
optionsConfig: CachingOptions
|
|
703
|
+
): KimiCachingConfig | undefined {
|
|
704
|
+
// Provider options take precedence
|
|
705
|
+
const config = optionsConfig ?? settingsConfig;
|
|
706
|
+
|
|
707
|
+
if (config == null) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (typeof config === 'boolean') {
|
|
712
|
+
return config ? { enabled: true } : undefined;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return config.enabled ? config : undefined;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Build HTTP headers for context caching.
|
|
720
|
+
* Kimi uses specific headers to control caching behavior.
|
|
721
|
+
*/
|
|
722
|
+
function buildCachingHeaders(caching: KimiCachingConfig | undefined): Record<string, string> {
|
|
723
|
+
if (!caching?.enabled) {
|
|
724
|
+
return {};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const headers: Record<string, string> = {
|
|
728
|
+
'X-Kimi-Cache': 'enabled'
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
if (caching.cacheKey) {
|
|
732
|
+
headers['X-Kimi-Cache-Key'] = caching.cacheKey;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (caching.ttlSeconds) {
|
|
736
|
+
headers['X-Kimi-Cache-TTL'] = String(caching.ttlSeconds);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (caching.resetCache) {
|
|
740
|
+
headers['X-Kimi-Cache-Reset'] = 'true';
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return headers;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ============================================================================
|
|
747
|
+
// Zod Schemas
|
|
748
|
+
// ============================================================================
|
|
749
|
+
|
|
750
|
+
const kimiTokenUsageSchema = z
|
|
751
|
+
.object({
|
|
752
|
+
prompt_tokens: z.number().nullish(),
|
|
753
|
+
completion_tokens: z.number().nullish(),
|
|
754
|
+
total_tokens: z.number().nullish(),
|
|
755
|
+
prompt_tokens_details: z
|
|
756
|
+
.object({
|
|
757
|
+
cached_tokens: z.number().nullish()
|
|
758
|
+
})
|
|
759
|
+
.nullish(),
|
|
760
|
+
completion_tokens_details: z
|
|
761
|
+
.object({
|
|
762
|
+
reasoning_tokens: z.number().nullish()
|
|
763
|
+
})
|
|
764
|
+
.nullish()
|
|
765
|
+
})
|
|
766
|
+
.nullish();
|
|
767
|
+
|
|
768
|
+
const kimiChatResponseSchema = z.looseObject({
|
|
769
|
+
id: z.string().nullish(),
|
|
770
|
+
created: z.number().nullish(),
|
|
771
|
+
model: z.string().nullish(),
|
|
772
|
+
choices: z.array(
|
|
773
|
+
z.object({
|
|
774
|
+
message: z.object({
|
|
775
|
+
role: z.string().nullish(),
|
|
776
|
+
content: z.union([z.string(), z.array(z.any())]).nullish(),
|
|
777
|
+
reasoning_content: z.string().nullish(),
|
|
778
|
+
reasoning: z.string().nullish(),
|
|
779
|
+
tool_calls: z
|
|
780
|
+
.array(
|
|
781
|
+
z.object({
|
|
782
|
+
id: z.string().nullish(),
|
|
783
|
+
function: z.object({
|
|
784
|
+
name: z.string(),
|
|
785
|
+
arguments: z.string().nullish()
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
)
|
|
789
|
+
.nullish()
|
|
790
|
+
}),
|
|
791
|
+
finish_reason: z.string().nullish()
|
|
792
|
+
})
|
|
793
|
+
),
|
|
794
|
+
usage: kimiTokenUsageSchema
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
const kimiChatChunkBaseSchema = z.looseObject({
|
|
798
|
+
id: z.string().nullish(),
|
|
799
|
+
created: z.number().nullish(),
|
|
800
|
+
model: z.string().nullish(),
|
|
801
|
+
choices: z.array(
|
|
802
|
+
z.object({
|
|
803
|
+
delta: z
|
|
804
|
+
.object({
|
|
805
|
+
role: z.enum(['assistant']).nullish(),
|
|
806
|
+
content: z.string().nullish(),
|
|
807
|
+
reasoning_content: z.string().nullish(),
|
|
808
|
+
reasoning: z.string().nullish(),
|
|
809
|
+
tool_calls: z
|
|
810
|
+
.array(
|
|
811
|
+
z.object({
|
|
812
|
+
index: z.number().nullish(),
|
|
813
|
+
id: z.string().nullish(),
|
|
814
|
+
function: z.object({
|
|
815
|
+
name: z.string().nullish(),
|
|
816
|
+
arguments: z.string().nullish()
|
|
817
|
+
})
|
|
818
|
+
})
|
|
819
|
+
)
|
|
820
|
+
.nullish()
|
|
821
|
+
})
|
|
822
|
+
.nullish(),
|
|
823
|
+
finish_reason: z.string().nullish()
|
|
824
|
+
})
|
|
825
|
+
),
|
|
826
|
+
usage: kimiTokenUsageSchema
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const kimiChatChunkSchema = z.union([kimiChatChunkBaseSchema, kimiErrorSchema]);
|