@ai-sdk/moonshotai 3.0.38 → 3.0.41

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.
@@ -15,6 +15,7 @@ import {
15
15
  createJsonErrorResponseHandler,
16
16
  createJsonResponseHandler,
17
17
  createLanguageModelResponseMetadata as getResponseMetadata,
18
+ createProviderStreamError,
18
19
  generateId,
19
20
  isCustomReasoning,
20
21
  mapReasoningToProviderEffort,
@@ -36,13 +37,16 @@ import {
36
37
  moonshotAIChatChunkSchema,
37
38
  moonshotAIChatResponseSchema,
38
39
  moonshotAIErrorSchema,
40
+ type MoonshotAIChatLogprob,
39
41
  type MoonshotAIChatTokenUsage,
40
42
  } from './moonshotai-chat-api-types';
41
43
  import {
42
- getModelThinkingKeepSupport,
44
+ getMoonshotAIModelFamily,
45
+ isMoonshotAIKimiModel,
43
46
  moonshotaiLanguageModelOptions,
44
47
  type MoonshotAIChatModelId,
45
48
  } from './moonshotai-chat-options';
49
+ import { normalizeJsonSchemaForMFJS } from './normalize-json-schema-for-mfjs';
46
50
  import { prepareTools } from './moonshotai-prepare-tools';
47
51
 
48
52
  export type MoonshotAIChatConfig = {
@@ -54,6 +58,54 @@ export type MoonshotAIChatConfig = {
54
58
  supportsStructuredOutputs?: boolean;
55
59
  };
56
60
 
61
+ function createMoonshotAIStreamError(
62
+ error: { message: string; type?: string | null; code?: string | null },
63
+ data: unknown,
64
+ ) {
65
+ return createProviderStreamError({
66
+ message: error.message,
67
+ type: error.type ?? undefined,
68
+ code: error.code ?? undefined,
69
+ ...getMoonshotAIStreamErrorMetadata(error.type),
70
+ data,
71
+ });
72
+ }
73
+
74
+ function getMoonshotAIStreamErrorMetadata(type?: string | null): {
75
+ statusCode?: number;
76
+ isRetryable?: boolean;
77
+ } {
78
+ switch (type) {
79
+ case 'rate_limit_exceeded':
80
+ case 'rate_limit_error':
81
+ return { statusCode: 429, isRetryable: true };
82
+ case 'server_error':
83
+ case 'api_error':
84
+ case 'internal_server_error':
85
+ return { statusCode: 500, isRetryable: true };
86
+ case 'overloaded_error':
87
+ case 'service_unavailable':
88
+ return { statusCode: 503, isRetryable: true };
89
+ case 'timeout':
90
+ case 'timeout_error':
91
+ return { statusCode: 504, isRetryable: true };
92
+ case 'authentication_error':
93
+ case 'invalid_api_key':
94
+ return { statusCode: 401, isRetryable: false };
95
+ case 'permission_error':
96
+ return { statusCode: 403, isRetryable: false };
97
+ case 'not_found_error':
98
+ case 'model_not_found':
99
+ return { statusCode: 404, isRetryable: false };
100
+ case 'bad_request':
101
+ case 'context_length_exceeded':
102
+ case 'invalid_request_error':
103
+ return { statusCode: 400, isRetryable: false };
104
+ default:
105
+ return {};
106
+ }
107
+ }
108
+
57
109
  export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
58
110
  readonly specificationVersion = 'v4';
59
111
 
@@ -125,8 +177,6 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
125
177
  schema: moonshotaiLanguageModelOptions,
126
178
  })) ?? {};
127
179
 
128
- const messages = convertToMoonshotAIChatMessages(prompt);
129
-
130
180
  const allWarnings: SharedV4Warning[] = [];
131
181
  if (topK != null) {
132
182
  allWarnings.push({ type: 'unsupported', feature: 'topK' });
@@ -135,57 +185,217 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
135
185
  allWarnings.push({ type: 'unsupported', feature: 'seed' });
136
186
  }
137
187
 
188
+ const supportsSamplingOptions = !isMoonshotAIKimiModel(this.modelId);
189
+
190
+ if (!supportsSamplingOptions && temperature != null) {
191
+ allWarnings.push({
192
+ type: 'unsupported',
193
+ feature: 'temperature',
194
+ details: `temperature is fixed by model "${this.modelId}" and has been omitted.`,
195
+ });
196
+ }
197
+ if (!supportsSamplingOptions && topP != null) {
198
+ allWarnings.push({
199
+ type: 'unsupported',
200
+ feature: 'topP',
201
+ details: `topP is fixed by model "${this.modelId}" and has been omitted.`,
202
+ });
203
+ }
204
+ if (!supportsSamplingOptions && frequencyPenalty != null) {
205
+ allWarnings.push({
206
+ type: 'unsupported',
207
+ feature: 'frequencyPenalty',
208
+ details: `frequencyPenalty is fixed by model "${this.modelId}" and has been omitted.`,
209
+ });
210
+ }
211
+ if (!supportsSamplingOptions && presencePenalty != null) {
212
+ allWarnings.push({
213
+ type: 'unsupported',
214
+ feature: 'presencePenalty',
215
+ details: `presencePenalty is fixed by model "${this.modelId}" and has been omitted.`,
216
+ });
217
+ }
218
+
138
219
  const {
139
220
  tools: moonshotTools,
140
221
  toolChoice: moonshotToolChoice,
141
222
  toolWarnings,
142
- } = prepareTools({ tools, toolChoice });
143
-
144
- // Thinking is configured through explicit provider options only.
145
- const thinking = moonshotOptions.thinking;
146
-
147
- // Moonshot has no reasoning_history field; the API silently ignores it
148
- // (verified against the live API). Preserved Thinking maps to
149
- // thinking.keep, which only accepts 'all' and only on some models
150
- // (verified: k2.6, k2.7-code, k3 accept it; k2.5 rejects it). Other
151
- // reasoningHistory values use the server default.
152
- let keep: 'all' | undefined;
153
- if (moonshotOptions.reasoningHistory === 'preserved') {
154
- if (getModelThinkingKeepSupport(this.modelId)) {
155
- keep = 'all';
156
- } else {
223
+ } = prepareTools({ tools, toolChoice, modelId: this.modelId });
224
+
225
+ const modelFamily = getMoonshotAIModelFamily(this.modelId);
226
+ const requestedThinking = moonshotOptions.thinking;
227
+ const requestedReasoningEffort = moonshotOptions.reasoningEffort;
228
+ const preserveReasoning = moonshotOptions.reasoningHistory === 'preserved';
229
+
230
+ if (requestedThinking?.budgetTokens != null) {
231
+ allWarnings.push({
232
+ type: 'deprecated',
233
+ setting: 'providerOptions.moonshotai.thinking.budgetTokens',
234
+ message:
235
+ 'Moonshot Chat Completions does not support budget_tokens. Remove budgetTokens; the option has been omitted.',
236
+ });
237
+ }
238
+
239
+ let thinking: { type: 'enabled' | 'disabled'; keep?: 'all' } | undefined;
240
+ let reasoningEffort: 'low' | 'high' | 'max' | undefined;
241
+
242
+ const warnUnsupportedReasoningEffort = () => {
243
+ if (requestedReasoningEffort != null) {
157
244
  allWarnings.push({
158
245
  type: 'unsupported',
159
- feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
246
+ feature: 'reasoningEffort',
247
+ details: `reasoningEffort is only supported by Kimi K3 and has been omitted for model "${this.modelId}".`,
160
248
  });
161
249
  }
162
- }
250
+ };
163
251
 
164
- // Map the generic reasoning call option to Moonshot's reasoning_effort
165
- // (explicit provider options win). 'none' cannot disable Moonshot
166
- // thinking from here; use thinking: { type: 'disabled' } instead.
167
- if (reasoning === 'none') {
168
- allWarnings.push({
169
- type: 'unsupported',
170
- feature:
171
- 'reasoning "none" (use providerOptions.moonshotai.thinking to control thinking)',
172
- });
252
+ switch (modelFamily) {
253
+ case 'kimi-k3': {
254
+ if (requestedThinking != null) {
255
+ allWarnings.push({
256
+ type: 'unsupported',
257
+ feature: 'thinking',
258
+ details:
259
+ 'Kimi K3 always reasons and does not accept the thinking field. The option has been omitted.',
260
+ });
261
+ }
262
+ if (reasoning === 'none') {
263
+ allWarnings.push({
264
+ type: 'unsupported',
265
+ feature: 'reasoning "none"',
266
+ details: 'Kimi K3 reasoning cannot be disabled.',
267
+ });
268
+ }
269
+ reasoningEffort =
270
+ requestedReasoningEffort ??
271
+ (isCustomReasoning(reasoning) && reasoning !== 'none'
272
+ ? mapReasoningToProviderEffort({
273
+ reasoning,
274
+ effortMap: {
275
+ minimal: 'low',
276
+ low: 'low',
277
+ medium: 'high',
278
+ high: 'high',
279
+ xhigh: 'max',
280
+ },
281
+ warnings: allWarnings,
282
+ })
283
+ : undefined);
284
+ break;
285
+ }
286
+ case 'kimi-k2.7': {
287
+ warnUnsupportedReasoningEffort();
288
+ if (requestedThinking?.type === 'disabled' || reasoning === 'none') {
289
+ allWarnings.push({
290
+ type: 'unsupported',
291
+ feature:
292
+ requestedThinking?.type === 'disabled'
293
+ ? 'thinking.type "disabled"'
294
+ : 'reasoning "none"',
295
+ details: 'Kimi K2.7 thinking cannot be disabled.',
296
+ });
297
+ } else if (requestedThinking?.type === 'enabled') {
298
+ thinking = { type: 'enabled' };
299
+ }
300
+ break;
301
+ }
302
+ case 'kimi-k2.6': {
303
+ warnUnsupportedReasoningEffort();
304
+ const thinkingType =
305
+ requestedThinking?.type ??
306
+ (isCustomReasoning(reasoning)
307
+ ? reasoning === 'none'
308
+ ? 'disabled'
309
+ : 'enabled'
310
+ : undefined);
311
+ if (thinkingType != null || preserveReasoning) {
312
+ thinking = {
313
+ type: thinkingType ?? 'enabled',
314
+ ...(preserveReasoning ? { keep: 'all' as const } : {}),
315
+ };
316
+ }
317
+ break;
318
+ }
319
+ case 'kimi-k2.5': {
320
+ warnUnsupportedReasoningEffort();
321
+ const thinkingType =
322
+ requestedThinking?.type ??
323
+ (isCustomReasoning(reasoning)
324
+ ? reasoning === 'none'
325
+ ? 'disabled'
326
+ : 'enabled'
327
+ : undefined);
328
+ if (thinkingType != null) {
329
+ thinking = { type: thinkingType };
330
+ }
331
+ if (preserveReasoning) {
332
+ allWarnings.push({
333
+ type: 'unsupported',
334
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
335
+ });
336
+ }
337
+ break;
338
+ }
339
+ case 'moonshot-v1': {
340
+ warnUnsupportedReasoningEffort();
341
+ if (requestedThinking != null) {
342
+ allWarnings.push({
343
+ type: 'unsupported',
344
+ feature: 'thinking',
345
+ details: `thinking is not supported by model "${this.modelId}" and has been omitted.`,
346
+ });
347
+ }
348
+ if (isCustomReasoning(reasoning) && reasoning !== 'none') {
349
+ allWarnings.push({
350
+ type: 'unsupported',
351
+ feature: 'reasoning',
352
+ details: `reasoning is not supported by model "${this.modelId}".`,
353
+ });
354
+ }
355
+ if (preserveReasoning) {
356
+ allWarnings.push({
357
+ type: 'unsupported',
358
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
359
+ });
360
+ }
361
+ break;
362
+ }
363
+ case 'unknown': {
364
+ if (reasoning === 'none') {
365
+ allWarnings.push({
366
+ type: 'unsupported',
367
+ feature: 'reasoning "none"',
368
+ details:
369
+ 'Use providerOptions.moonshotai.thinking to control thinking on custom models.',
370
+ });
371
+ }
372
+ reasoningEffort =
373
+ requestedReasoningEffort ??
374
+ (isCustomReasoning(reasoning) && reasoning !== 'none'
375
+ ? mapReasoningToProviderEffort({
376
+ reasoning,
377
+ effortMap: {
378
+ minimal: 'low',
379
+ low: 'low',
380
+ medium: 'high',
381
+ high: 'high',
382
+ xhigh: 'max',
383
+ },
384
+ warnings: allWarnings,
385
+ })
386
+ : undefined);
387
+ if (requestedThinking?.type != null) {
388
+ thinking = { type: requestedThinking.type };
389
+ }
390
+ if (preserveReasoning) {
391
+ allWarnings.push({
392
+ type: 'unsupported',
393
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
394
+ });
395
+ }
396
+ break;
397
+ }
173
398
  }
174
- const reasoningEffort =
175
- moonshotOptions.reasoningEffort ??
176
- (isCustomReasoning(reasoning) && reasoning !== 'none'
177
- ? mapReasoningToProviderEffort({
178
- reasoning,
179
- effortMap: {
180
- minimal: 'low',
181
- low: 'low',
182
- medium: 'high',
183
- high: 'high',
184
- xhigh: 'max',
185
- },
186
- warnings: allWarnings,
187
- })
188
- : undefined);
189
399
 
190
400
  let response_format: Record<string, unknown> | undefined;
191
401
  if (responseFormat?.type === 'json') {
@@ -204,10 +414,8 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
204
414
  type: 'json_schema',
205
415
  json_schema: {
206
416
  name: responseFormat.name ?? 'response',
207
- schema: schemaWithoutDollarSchema,
208
- ...(responseFormat.description != null && {
209
- description: responseFormat.description,
210
- }),
417
+ strict: moonshotOptions.strictJsonSchema ?? true,
418
+ schema: normalizeJsonSchemaForMFJS(schemaWithoutDollarSchema),
211
419
  },
212
420
  };
213
421
  } else {
@@ -215,30 +423,39 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
215
423
  }
216
424
  }
217
425
 
426
+ const { messages, warnings: messageWarnings } =
427
+ await convertToMoonshotAIChatMessages({
428
+ modelId: this.modelId,
429
+ prompt,
430
+ providerOptionsName: this.providerOptionsName,
431
+ responseFormat: response_format,
432
+ });
433
+ allWarnings.push(...messageWarnings);
434
+
218
435
  return {
219
436
  args: {
220
437
  model: this.modelId,
221
- max_tokens: maxOutputTokens,
222
- temperature,
223
- top_p: topP,
224
- frequency_penalty: frequencyPenalty,
225
- presence_penalty: presencePenalty,
438
+ ...((moonshotOptions.logprobs === true ||
439
+ moonshotOptions.topLogprobs != null) && { logprobs: true }),
440
+ ...(moonshotOptions.topLogprobs != null && {
441
+ top_logprobs: moonshotOptions.topLogprobs,
442
+ }),
443
+ max_completion_tokens: maxOutputTokens,
444
+ temperature: supportsSamplingOptions ? temperature : undefined,
445
+ top_p: supportsSamplingOptions ? topP : undefined,
446
+ frequency_penalty: supportsSamplingOptions
447
+ ? frequencyPenalty
448
+ : undefined,
449
+ presence_penalty: supportsSamplingOptions ? presencePenalty : undefined,
226
450
  response_format,
227
451
  stop: stopSequences,
228
452
  messages,
229
453
  tools: moonshotTools,
230
454
  tool_choice: moonshotToolChoice,
231
- ...(thinking != null || keep != null
232
- ? {
233
- thinking: {
234
- ...(thinking?.type != null && { type: thinking.type }),
235
- ...(thinking?.budgetTokens !== undefined && {
236
- budget_tokens: thinking.budgetTokens,
237
- }),
238
- ...(keep != null && { keep }),
239
- },
240
- }
241
- : {}),
455
+ ...(moonshotOptions.prediction != null && {
456
+ prediction: moonshotOptions.prediction,
457
+ }),
458
+ ...(thinking != null ? { thinking } : {}),
242
459
  ...(reasoningEffort != null && {
243
460
  reasoning_effort: reasoningEffort,
244
461
  }),
@@ -311,6 +528,23 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
311
528
  raw: choice.finish_reason ?? undefined,
312
529
  },
313
530
  usage: convertMoonshotAIChatUsage(responseBody.usage),
531
+ providerMetadata: {
532
+ [this.providerOptionsName]: {
533
+ ...(choice.logprobs != null && { logprobs: choice.logprobs }),
534
+ ...(responseBody.object != null && {
535
+ responseObject: responseBody.object,
536
+ }),
537
+ ...(choice.index != null && { choiceIndex: choice.index }),
538
+ ...(choice.message.role != null && {
539
+ messageRole: choice.message.role,
540
+ }),
541
+ ...(choice.message.tool_calls != null && {
542
+ toolCallTypes: choice.message.tool_calls
543
+ .map(toolCall => toolCall.type)
544
+ .filter(type => type != null),
545
+ }),
546
+ },
547
+ },
314
548
  request: { body: args },
315
549
  response: {
316
550
  ...getResponseMetadata(responseBody),
@@ -355,10 +589,17 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
355
589
  unified: 'other',
356
590
  raw: undefined,
357
591
  };
358
- let usage: MoonshotAIChatTokenUsage | undefined = undefined;
592
+ let topLevelUsage: MoonshotAIChatTokenUsage | undefined = undefined;
593
+ let choiceUsage: MoonshotAIChatTokenUsage | undefined = undefined;
594
+ const contentLogprobs: MoonshotAIChatLogprob[] = [];
595
+ const providerOptionsName = this.providerOptionsName;
359
596
  let isFirstChunk = true;
360
597
  let isActiveReasoning = false;
361
598
  let isActiveText = false;
599
+ let responseObject: 'chat.completion.chunk' | undefined;
600
+ let choiceIndex: number | undefined;
601
+ let messageRole: 'assistant' | undefined;
602
+ const toolCallTypes = new Map<number, 'function'>();
362
603
 
363
604
  return {
364
605
  stream: response.pipeThrough(
@@ -390,7 +631,10 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
390
631
  // handle error chunks:
391
632
  if ('error' in value) {
392
633
  finishReason = { unified: 'error', raw: undefined };
393
- controller.enqueue({ type: 'error', error: value.error.message });
634
+ controller.enqueue({
635
+ type: 'error',
636
+ error: createMoonshotAIStreamError(value.error, value),
637
+ });
394
638
  return;
395
639
  }
396
640
 
@@ -404,11 +648,23 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
404
648
  }
405
649
 
406
650
  if (value.usage != null) {
407
- usage = value.usage;
651
+ topLevelUsage = value.usage;
652
+ }
653
+
654
+ if (value.object != null) {
655
+ responseObject = value.object;
408
656
  }
409
657
 
410
658
  const choice = value.choices[0];
411
659
 
660
+ if (choice?.usage != null) {
661
+ choiceUsage = choice.usage;
662
+ }
663
+
664
+ if (choice?.index != null) {
665
+ choiceIndex = choice.index;
666
+ }
667
+
412
668
  if (choice?.finish_reason != null) {
413
669
  finishReason = {
414
670
  unified: mapMoonshotAIFinishReason(choice.finish_reason),
@@ -416,12 +672,20 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
416
672
  };
417
673
  }
418
674
 
675
+ if (choice?.logprobs?.content != null) {
676
+ contentLogprobs.push(...choice.logprobs.content);
677
+ }
678
+
419
679
  if (choice?.delta == null) {
420
680
  return;
421
681
  }
422
682
 
423
683
  const delta = choice.delta;
424
684
 
685
+ if (delta.role != null) {
686
+ messageRole = delta.role;
687
+ }
688
+
425
689
  // enqueue reasoning before text deltas:
426
690
  const reasoningContent = delta.reasoning_content;
427
691
  if (reasoningContent) {
@@ -472,8 +736,15 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
472
736
  isActiveReasoning = false;
473
737
  }
474
738
 
475
- for (const toolCallDelta of delta.tool_calls) {
476
- toolCallTracker.processDelta(toolCallDelta);
739
+ for (const [index, toolCallDelta] of delta.tool_calls.entries()) {
740
+ const toolCallIndex = toolCallDelta.index ?? index;
741
+ if (toolCallDelta.type != null) {
742
+ toolCallTypes.set(toolCallIndex, toolCallDelta.type);
743
+ }
744
+ toolCallTracker.processDelta({
745
+ ...toolCallDelta,
746
+ index: toolCallIndex,
747
+ });
477
748
  }
478
749
  }
479
750
  },
@@ -492,7 +763,22 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV4 {
492
763
  controller.enqueue({
493
764
  type: 'finish',
494
765
  finishReason,
495
- usage: convertMoonshotAIChatUsage(usage),
766
+ usage: convertMoonshotAIChatUsage(topLevelUsage ?? choiceUsage),
767
+ providerMetadata: {
768
+ [providerOptionsName]: {
769
+ ...(contentLogprobs.length > 0 && {
770
+ logprobs: { content: contentLogprobs },
771
+ }),
772
+ ...(responseObject != null && { responseObject }),
773
+ ...(choiceIndex != null && { choiceIndex }),
774
+ ...(messageRole != null && { messageRole }),
775
+ ...(toolCallTypes.size > 0 && {
776
+ toolCallTypes: [...toolCallTypes.entries()]
777
+ .sort(([left], [right]) => left - right)
778
+ .map(([, type]) => type),
779
+ }),
780
+ },
781
+ },
496
782
  });
497
783
  },
498
784
  }),