@ai-sdk/moonshotai 2.0.48 → 2.0.51

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.
@@ -34,10 +34,12 @@ import {
34
34
  type MoonshotAIChatTokenUsage,
35
35
  } from './moonshotai-chat-api-types';
36
36
  import {
37
- getModelThinkingKeepSupport,
37
+ getMoonshotAIModelFamily,
38
+ isMoonshotAIKimiModel,
38
39
  moonshotaiLanguageModelOptions,
39
40
  type MoonshotAIChatModelId,
40
41
  } from './moonshotai-chat-options';
42
+ import { normalizeJsonSchemaForMFJS } from './normalize-json-schema-for-mfjs';
41
43
  import { prepareTools } from './moonshotai-prepare-tools';
42
44
 
43
45
  export type MoonshotAIChatConfig = {
@@ -115,30 +117,150 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
115
117
  allWarnings.push({ type: 'unsupported', feature: 'seed' });
116
118
  }
117
119
 
120
+ const supportsSamplingOptions = !isMoonshotAIKimiModel(this.modelId);
121
+
122
+ if (!supportsSamplingOptions && temperature != null) {
123
+ allWarnings.push({
124
+ type: 'unsupported',
125
+ feature: 'temperature',
126
+ details: `temperature is fixed by model "${this.modelId}" and has been omitted.`,
127
+ });
128
+ }
129
+ if (!supportsSamplingOptions && topP != null) {
130
+ allWarnings.push({
131
+ type: 'unsupported',
132
+ feature: 'topP',
133
+ details: `topP is fixed by model "${this.modelId}" and has been omitted.`,
134
+ });
135
+ }
136
+ if (!supportsSamplingOptions && frequencyPenalty != null) {
137
+ allWarnings.push({
138
+ type: 'unsupported',
139
+ feature: 'frequencyPenalty',
140
+ details: `frequencyPenalty is fixed by model "${this.modelId}" and has been omitted.`,
141
+ });
142
+ }
143
+ if (!supportsSamplingOptions && presencePenalty != null) {
144
+ allWarnings.push({
145
+ type: 'unsupported',
146
+ feature: 'presencePenalty',
147
+ details: `presencePenalty is fixed by model "${this.modelId}" and has been omitted.`,
148
+ });
149
+ }
150
+
118
151
  const {
119
152
  tools: moonshotTools,
120
153
  toolChoice: moonshotToolChoice,
121
154
  toolWarnings,
122
- } = prepareTools({ tools, toolChoice });
123
-
124
- // Thinking is configured through explicit provider options only.
125
- const thinking = moonshotOptions.thinking;
126
-
127
- // Moonshot has no reasoning_history field; the API silently ignores it
128
- // (verified against the live API). Preserved Thinking maps to
129
- // thinking.keep, which only accepts 'all' and only on some models
130
- // (verified: k2.6, k2.7-code, k3 accept it; k2.5 rejects it). Other
131
- // reasoningHistory values use the server default.
132
- let keep: 'all' | undefined;
133
- if (moonshotOptions.reasoningHistory === 'preserved') {
134
- if (getModelThinkingKeepSupport(this.modelId)) {
135
- keep = 'all';
136
- } else {
155
+ } = prepareTools({ tools, toolChoice, modelId: this.modelId });
156
+
157
+ const modelFamily = getMoonshotAIModelFamily(this.modelId);
158
+ const requestedThinking = moonshotOptions.thinking;
159
+ const requestedReasoningEffort = moonshotOptions.reasoningEffort;
160
+ const preserveReasoning = moonshotOptions.reasoningHistory === 'preserved';
161
+
162
+ if (requestedThinking?.budgetTokens != null) {
163
+ allWarnings.push({
164
+ type: 'other',
165
+ message:
166
+ 'providerOptions.moonshotai.thinking.budgetTokens is deprecated because Moonshot Chat Completions does not support budget_tokens. The option has been omitted.',
167
+ });
168
+ }
169
+
170
+ let thinking: { type: 'enabled' | 'disabled'; keep?: 'all' } | undefined;
171
+ let reasoningEffort: 'low' | 'high' | 'max' | undefined;
172
+
173
+ const warnUnsupportedReasoningEffort = () => {
174
+ if (requestedReasoningEffort != null) {
137
175
  allWarnings.push({
138
176
  type: 'unsupported',
139
- feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
177
+ feature: 'reasoningEffort',
178
+ details: `reasoningEffort is only supported by Kimi K3 and has been omitted for model "${this.modelId}".`,
140
179
  });
141
180
  }
181
+ };
182
+
183
+ switch (modelFamily) {
184
+ case 'kimi-k3': {
185
+ if (requestedThinking != null) {
186
+ allWarnings.push({
187
+ type: 'unsupported',
188
+ feature: 'thinking',
189
+ details:
190
+ 'Kimi K3 always reasons and does not accept the thinking field. The option has been omitted.',
191
+ });
192
+ }
193
+ reasoningEffort = requestedReasoningEffort;
194
+ break;
195
+ }
196
+ case 'kimi-k2.7': {
197
+ warnUnsupportedReasoningEffort();
198
+ if (requestedThinking?.type === 'disabled') {
199
+ allWarnings.push({
200
+ type: 'unsupported',
201
+ feature: 'thinking.type "disabled"',
202
+ details: 'Kimi K2.7 thinking cannot be disabled.',
203
+ });
204
+ } else if (requestedThinking?.type === 'enabled') {
205
+ thinking = { type: 'enabled' };
206
+ }
207
+ break;
208
+ }
209
+ case 'kimi-k2.6': {
210
+ warnUnsupportedReasoningEffort();
211
+ const thinkingType = requestedThinking?.type;
212
+ if (thinkingType != null || preserveReasoning) {
213
+ thinking = {
214
+ type: thinkingType ?? 'enabled',
215
+ ...(preserveReasoning ? { keep: 'all' as const } : {}),
216
+ };
217
+ }
218
+ break;
219
+ }
220
+ case 'kimi-k2.5': {
221
+ warnUnsupportedReasoningEffort();
222
+ const thinkingType = requestedThinking?.type;
223
+ if (thinkingType != null) {
224
+ thinking = { type: thinkingType };
225
+ }
226
+ if (preserveReasoning) {
227
+ allWarnings.push({
228
+ type: 'unsupported',
229
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
230
+ });
231
+ }
232
+ break;
233
+ }
234
+ case 'moonshot-v1': {
235
+ warnUnsupportedReasoningEffort();
236
+ if (requestedThinking != null) {
237
+ allWarnings.push({
238
+ type: 'unsupported',
239
+ feature: 'thinking',
240
+ details: `thinking is not supported by model "${this.modelId}" and has been omitted.`,
241
+ });
242
+ }
243
+ if (preserveReasoning) {
244
+ allWarnings.push({
245
+ type: 'unsupported',
246
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
247
+ });
248
+ }
249
+ break;
250
+ }
251
+ case 'unknown': {
252
+ reasoningEffort = requestedReasoningEffort;
253
+ if (requestedThinking?.type != null) {
254
+ thinking = { type: requestedThinking.type };
255
+ }
256
+ if (preserveReasoning) {
257
+ allWarnings.push({
258
+ type: 'unsupported',
259
+ feature: `reasoningHistory 'preserved' is not supported by model "${this.modelId}"`,
260
+ });
261
+ }
262
+ break;
263
+ }
142
264
  }
143
265
 
144
266
  let response_format: Record<string, unknown> | undefined;
@@ -158,10 +280,8 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
158
280
  type: 'json_schema',
159
281
  json_schema: {
160
282
  name: responseFormat.name ?? 'response',
161
- schema: schemaWithoutDollarSchema,
162
- ...(responseFormat.description != null && {
163
- description: responseFormat.description,
164
- }),
283
+ strict: moonshotOptions.strictJsonSchema ?? true,
284
+ schema: normalizeJsonSchemaForMFJS(schemaWithoutDollarSchema),
165
285
  },
166
286
  };
167
287
  } else {
@@ -172,29 +292,21 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
172
292
  return {
173
293
  args: {
174
294
  model: this.modelId,
175
- max_tokens: maxOutputTokens,
176
- temperature,
177
- top_p: topP,
178
- frequency_penalty: frequencyPenalty,
179
- presence_penalty: presencePenalty,
295
+ max_completion_tokens: maxOutputTokens,
296
+ temperature: supportsSamplingOptions ? temperature : undefined,
297
+ top_p: supportsSamplingOptions ? topP : undefined,
298
+ frequency_penalty: supportsSamplingOptions
299
+ ? frequencyPenalty
300
+ : undefined,
301
+ presence_penalty: supportsSamplingOptions ? presencePenalty : undefined,
180
302
  response_format,
181
303
  stop: stopSequences,
182
304
  messages,
183
305
  tools: moonshotTools,
184
306
  tool_choice: moonshotToolChoice,
185
- ...(thinking != null || keep != null
186
- ? {
187
- thinking: {
188
- ...(thinking?.type != null && { type: thinking.type }),
189
- ...(thinking?.budgetTokens !== undefined && {
190
- budget_tokens: thinking.budgetTokens,
191
- }),
192
- ...(keep != null && { keep }),
193
- },
194
- }
195
- : {}),
196
- ...(moonshotOptions.reasoningEffort != null && {
197
- reasoning_effort: moonshotOptions.reasoningEffort,
307
+ ...(thinking != null ? { thinking } : {}),
308
+ ...(reasoningEffort != null && {
309
+ reasoning_effort: reasoningEffort,
198
310
  }),
199
311
  ...(moonshotOptions.promptCacheKey != null && {
200
312
  prompt_cache_key: moonshotOptions.promptCacheKey,
@@ -314,7 +426,8 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
314
426
  unified: 'other',
315
427
  raw: undefined,
316
428
  };
317
- let usage: MoonshotAIChatTokenUsage | undefined = undefined;
429
+ let topLevelUsage: MoonshotAIChatTokenUsage | undefined = undefined;
430
+ let choiceUsage: MoonshotAIChatTokenUsage | undefined = undefined;
318
431
  let isFirstChunk = true;
319
432
  let isActiveReasoning = false;
320
433
  let isActiveText = false;
@@ -360,11 +473,15 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
360
473
  }
361
474
 
362
475
  if (value.usage != null) {
363
- usage = value.usage;
476
+ topLevelUsage = value.usage;
364
477
  }
365
478
 
366
479
  const choice = value.choices[0];
367
480
 
481
+ if (choice?.usage != null) {
482
+ choiceUsage = choice.usage;
483
+ }
484
+
368
485
  if (choice?.finish_reason != null) {
369
486
  finishReason = {
370
487
  unified: mapMoonshotAIFinishReason(choice.finish_reason),
@@ -428,8 +545,11 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
428
545
  isActiveReasoning = false;
429
546
  }
430
547
 
431
- for (const toolCallDelta of delta.tool_calls) {
432
- const index = toolCallDelta.index;
548
+ for (const [
549
+ fallbackIndex,
550
+ toolCallDelta,
551
+ ] of delta.tool_calls.entries()) {
552
+ const index = toolCallDelta.index ?? fallbackIndex;
433
553
 
434
554
  if (toolCalls[index] == null) {
435
555
  if (toolCallDelta.id == null) {
@@ -531,7 +651,7 @@ export class MoonshotAIChatLanguageModel implements LanguageModelV3 {
531
651
  controller.enqueue({
532
652
  type: 'finish',
533
653
  finishReason,
534
- usage: convertMoonshotAIChatUsage(usage),
654
+ usage: convertMoonshotAIChatUsage(topLevelUsage ?? choiceUsage),
535
655
  });
536
656
  },
537
657
  }),
@@ -4,6 +4,10 @@ export type MoonshotAIChatModelId =
4
4
  | 'moonshot-v1-8k'
5
5
  | 'moonshot-v1-32k'
6
6
  | 'moonshot-v1-128k'
7
+ | 'moonshot-v1-auto'
8
+ | 'moonshot-v1-8k-vision-preview'
9
+ | 'moonshot-v1-32k-vision-preview'
10
+ | 'moonshot-v1-128k-vision-preview'
7
11
  | 'kimi-k2'
8
12
  | 'kimi-k2-0905'
9
13
  | 'kimi-k2-thinking'
@@ -13,7 +17,39 @@ export type MoonshotAIChatModelId =
13
17
  | 'kimi-k3'
14
18
  | (string & {});
15
19
 
20
+ export function isMoonshotAIKimiModel(modelId: MoonshotAIChatModelId): boolean {
21
+ return getMoonshotAIModelFamily(modelId).startsWith('kimi-');
22
+ }
23
+
24
+ export type MoonshotAIModelFamily =
25
+ | 'kimi-k2.5'
26
+ | 'kimi-k2.6'
27
+ | 'kimi-k2.7'
28
+ | 'kimi-k3'
29
+ | 'moonshot-v1'
30
+ | 'unknown';
31
+
32
+ export function getMoonshotAIModelFamily(
33
+ modelId: MoonshotAIChatModelId,
34
+ ): MoonshotAIModelFamily {
35
+ if (modelId === 'kimi-k2.5') return 'kimi-k2.5';
36
+ if (modelId === 'kimi-k2.6') return 'kimi-k2.6';
37
+ if (modelId === 'kimi-k2.7-code' || modelId === 'kimi-k2.7-code-highspeed') {
38
+ return 'kimi-k2.7';
39
+ }
40
+ if (modelId === 'kimi-k3') return 'kimi-k3';
41
+ if (modelId.startsWith('moonshot-v1-')) return 'moonshot-v1';
42
+ return 'unknown';
43
+ }
44
+
16
45
  export const moonshotaiLanguageModelOptions = z.object({
46
+ /**
47
+ * Whether to use strict JSON schema validation for structured outputs.
48
+ *
49
+ * @default true
50
+ */
51
+ strictJsonSchema: z.boolean().optional(),
52
+
17
53
  /**
18
54
  * Reasoning effort for Kimi K3.
19
55
  */
@@ -22,6 +58,8 @@ export const moonshotaiLanguageModelOptions = z.object({
22
58
  thinking: z
23
59
  .object({
24
60
  type: z.enum(['enabled', 'disabled']).optional(),
61
+ // Accepted so existing callers receive a migration warning. It remains
62
+ // in the public compatibility type below as a deprecated property.
25
63
  budgetTokens: z.number().int().min(1024).optional(),
26
64
  })
27
65
  .optional(),
@@ -41,21 +79,29 @@ export const moonshotaiLanguageModelOptions = z.object({
41
79
  safetyIdentifier: z.string().optional(),
42
80
  });
43
81
 
44
- export type MoonshotAILanguageModelOptions = z.infer<
45
- typeof moonshotaiLanguageModelOptions
46
- >;
82
+ export type MoonshotAILanguageModelOptions = {
83
+ /**
84
+ * Whether to use strict JSON schema validation for structured outputs.
85
+ *
86
+ * @default true
87
+ */
88
+ strictJsonSchema?: boolean;
47
89
 
48
- /**
49
- * Whether the model accepts `thinking.keep` (Preserved Thinking). Verified
50
- * against the live API: kimi-k2.6, kimi-k2.7-code(+highspeed), and kimi-k3
51
- * accept `keep: 'all'`; other models reject it with a 400.
52
- */
53
- export function getModelThinkingKeepSupport(
54
- modelId: MoonshotAIChatModelId,
55
- ): boolean {
56
- return (
57
- modelId === 'kimi-k2.6' ||
58
- modelId === 'kimi-k3' ||
59
- modelId.startsWith('kimi-k2.7-code')
60
- );
61
- }
90
+ /** Reasoning effort for Kimi K3. */
91
+ reasoningEffort?: 'low' | 'high' | 'max';
92
+
93
+ /** Controls thinking on Kimi K2.5 and K2.6. K2.7 is always enabled. */
94
+ thinking?: {
95
+ type?: 'enabled' | 'disabled';
96
+
97
+ /**
98
+ * @deprecated Moonshot Chat Completions does not support thinking budgets.
99
+ * This value is ignored with a warning.
100
+ */
101
+ budgetTokens?: number;
102
+ };
103
+
104
+ reasoningHistory?: 'disabled' | 'interleaved' | 'preserved';
105
+ promptCacheKey?: string;
106
+ safetyIdentifier?: string;
107
+ };
@@ -3,14 +3,17 @@ import {
3
3
  type LanguageModelV3CallOptions,
4
4
  type SharedV3Warning,
5
5
  } from '@ai-sdk/provider';
6
+ import type { MoonshotAIChatModelId } from './moonshotai-chat-options';
6
7
  import { normalizeJsonSchemaForMFJS } from './normalize-json-schema-for-mfjs';
7
8
 
8
9
  export function prepareTools({
9
10
  tools,
10
11
  toolChoice,
12
+ modelId,
11
13
  }: {
12
14
  tools: LanguageModelV3CallOptions['tools'];
13
15
  toolChoice?: LanguageModelV3CallOptions['toolChoice'];
16
+ modelId: MoonshotAIChatModelId;
14
17
  }): {
15
18
  tools:
16
19
  | undefined
@@ -78,7 +81,25 @@ export function prepareTools({
78
81
  switch (type) {
79
82
  case 'auto':
80
83
  case 'none':
84
+ return { tools: moonshotTools, toolChoice: type, toolWarnings };
81
85
  case 'required':
86
+ if (
87
+ modelId === 'kimi-k2.6' ||
88
+ modelId === 'kimi-k2.7-code' ||
89
+ modelId === 'kimi-k2.7-code-highspeed'
90
+ ) {
91
+ toolWarnings.push({
92
+ type: 'unsupported',
93
+ feature: `tool choice "required" for model "${modelId}"`,
94
+ details:
95
+ 'Moonshot AI rejects required tool choice for this model. The setting has been omitted; use "auto" or select a specific tool instead.',
96
+ });
97
+ return {
98
+ tools: moonshotTools,
99
+ toolChoice: undefined,
100
+ toolWarnings,
101
+ };
102
+ }
82
103
  return { tools: moonshotTools, toolChoice: type, toolWarnings };
83
104
  case 'tool':
84
105
  return {
@@ -58,8 +58,16 @@ const defaultBaseURL = 'https://api.moonshot.ai/v1';
58
58
  export function getModelStructuredOutputSupport(
59
59
  modelId: MoonshotAIChatModelId,
60
60
  ): boolean {
61
- if (modelId.startsWith('kimi-k')) return true;
62
- return false;
61
+ return (
62
+ modelId.startsWith('kimi-k') ||
63
+ modelId === 'moonshot-v1-8k' ||
64
+ modelId === 'moonshot-v1-32k' ||
65
+ modelId === 'moonshot-v1-128k' ||
66
+ modelId === 'moonshot-v1-auto' ||
67
+ modelId === 'moonshot-v1-8k-vision-preview' ||
68
+ modelId === 'moonshot-v1-32k-vision-preview' ||
69
+ modelId === 'moonshot-v1-128k-vision-preview'
70
+ );
63
71
  }
64
72
 
65
73
  export function createMoonshotAI(