@ai-sdk/moonshotai 3.0.37 → 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.
@@ -1,27 +1,167 @@
1
1
  import {
2
+ InvalidPromptError,
2
3
  UnsupportedFunctionalityError,
4
+ type LanguageModelV4FilePart,
3
5
  type LanguageModelV4Prompt,
6
+ type SharedV4Warning,
4
7
  } from '@ai-sdk/provider';
5
8
  import {
6
9
  convertBase64ToUint8Array,
7
10
  convertToBase64,
8
11
  getTopLevelMediaType,
12
+ parseProviderOptions,
13
+ resolveProviderReference,
9
14
  resolveFullMediaType,
10
15
  } from '@ai-sdk/provider-utils';
11
16
 
12
17
  import type { MoonshotAIMessages } from './moonshotai-chat-api-types';
18
+ import {
19
+ getMoonshotAIModelFamily,
20
+ moonshotaiAllMessageProviderOptions,
21
+ type MoonshotAIChatModelId,
22
+ } from './moonshotai-chat-options';
23
+ import { prepareTools } from './moonshotai-prepare-tools';
24
+
25
+ const supportedImageMediaTypes = [
26
+ 'image/jpeg',
27
+ 'image/png',
28
+ 'image/gif',
29
+ 'image/webp',
30
+ 'image/bmp',
31
+ 'image/heic',
32
+ 'image/heif',
33
+ ] as const;
34
+
35
+ const supportedVideoMediaTypes = [
36
+ 'video/mp4',
37
+ 'video/mpeg',
38
+ 'video/mov',
39
+ 'video/avi',
40
+ 'video/x-flv',
41
+ 'video/mpg',
42
+ 'video/webm',
43
+ 'video/wmv',
44
+ 'video/3gpp',
45
+ ] as const;
46
+
47
+ function formatMediaUrl({
48
+ part,
49
+ supportedMediaTypes,
50
+ topLevelMediaType,
51
+ }: {
52
+ part: LanguageModelV4FilePart;
53
+ supportedMediaTypes: readonly string[];
54
+ topLevelMediaType: 'image' | 'video';
55
+ }): string {
56
+ if (part.data.type !== 'url' && part.data.type !== 'data') {
57
+ throw new UnsupportedFunctionalityError({
58
+ functionality: `file part data type ${part.data.type}`,
59
+ });
60
+ }
61
+
62
+ const mediaType =
63
+ part.data.type === 'url' ? part.mediaType : resolveFullMediaType({ part });
64
+
65
+ if (
66
+ !supportedMediaTypes.includes(mediaType) &&
67
+ !(
68
+ part.data.type === 'url' &&
69
+ (mediaType === topLevelMediaType ||
70
+ mediaType === `${topLevelMediaType}/*`)
71
+ )
72
+ ) {
73
+ throw new UnsupportedFunctionalityError({
74
+ functionality: `file part media type ${mediaType}`,
75
+ });
76
+ }
77
+
78
+ return part.data.type === 'url'
79
+ ? part.data.url.toString()
80
+ : `data:${mediaType};base64,${convertToBase64(part.data.data)}`;
81
+ }
13
82
 
14
83
  // Moonshot AI chat completions accepts text, image_url, and video_url content
15
84
  // parts only. Anything else (audio, PDF, other file types) throws here rather
16
85
  // than being rejected by the API with a 400.
17
- export function convertToMoonshotAIChatMessages(
18
- prompt: LanguageModelV4Prompt,
19
- ): MoonshotAIMessages {
86
+ export async function convertToMoonshotAIChatMessages({
87
+ modelId,
88
+ prompt,
89
+ providerOptionsName = 'moonshotai',
90
+ responseFormat,
91
+ }: {
92
+ modelId?: MoonshotAIChatModelId;
93
+ prompt: LanguageModelV4Prompt;
94
+ providerOptionsName?: string;
95
+ responseFormat?: Record<string, unknown>;
96
+ }): Promise<{
97
+ messages: MoonshotAIMessages;
98
+ warnings: Array<SharedV4Warning>;
99
+ }> {
20
100
  const messages: MoonshotAIMessages = [];
21
- for (const { role, content } of prompt) {
101
+ const warnings: Array<SharedV4Warning> = [];
102
+ const modelFamily =
103
+ modelId == null ? 'unknown' : getMoonshotAIModelFamily(modelId);
104
+
105
+ for (const [index, { role, content, providerOptions }] of prompt.entries()) {
106
+ const moonshotMessageOptions = await parseProviderOptions({
107
+ provider: providerOptionsName,
108
+ providerOptions,
109
+ schema: moonshotaiAllMessageProviderOptions,
110
+ });
111
+
112
+ if (moonshotMessageOptions?.partial === true && role !== 'assistant') {
113
+ throw new InvalidPromptError({
114
+ prompt,
115
+ message:
116
+ 'Moonshot AI Partial Mode requires `partial: true` on an assistant message.',
117
+ });
118
+ }
119
+
120
+ if (moonshotMessageOptions?.tools != null && role !== 'system') {
121
+ throw new InvalidPromptError({
122
+ prompt,
123
+ message:
124
+ 'Moonshot dynamic tools must be configured on a system message.',
125
+ });
126
+ }
127
+
22
128
  switch (role) {
23
129
  case 'system': {
24
- messages.push({ role: 'system', content });
130
+ if (moonshotMessageOptions?.tools?.length) {
131
+ if (content.length > 0) {
132
+ throw new InvalidPromptError({
133
+ prompt,
134
+ message:
135
+ 'A Moonshot dynamic-tool system message must use empty content because the API forbids content alongside tools.',
136
+ });
137
+ }
138
+
139
+ if (modelFamily !== 'kimi-k3' && modelFamily !== 'unknown') {
140
+ warnings.push({
141
+ type: 'unsupported',
142
+ feature: `dynamic tool loading for model "${modelId}"`,
143
+ details:
144
+ 'Moonshot documents dynamic tool loading only for Kimi K3. The dynamic system message has been omitted.',
145
+ });
146
+ break;
147
+ }
148
+
149
+ const { tools, toolWarnings } = prepareTools({
150
+ modelId: modelId ?? 'custom-model',
151
+ tools: moonshotMessageOptions.tools,
152
+ });
153
+ warnings.push(...toolWarnings);
154
+ messages.push({ role: 'system', tools: tools ?? [] });
155
+ break;
156
+ }
157
+
158
+ messages.push({
159
+ role: 'system',
160
+ content,
161
+ ...(moonshotMessageOptions?.name != null && {
162
+ name: moonshotMessageOptions.name,
163
+ }),
164
+ });
25
165
  break;
26
166
  }
27
167
 
@@ -30,6 +170,9 @@ export function convertToMoonshotAIChatMessages(
30
170
  messages.push({
31
171
  role: 'user',
32
172
  content: content[0].text,
173
+ ...(moonshotMessageOptions?.name != null && {
174
+ name: moonshotMessageOptions.name,
175
+ }),
33
176
  });
34
177
  break;
35
178
  }
@@ -42,29 +185,61 @@ export function convertToMoonshotAIChatMessages(
42
185
  return { type: 'text', text: part.text };
43
186
  }
44
187
  case 'file': {
188
+ const topLevel = getTopLevelMediaType(part.mediaType);
189
+
45
190
  switch (part.data.type) {
46
191
  case 'reference': {
47
- throw new UnsupportedFunctionalityError({
48
- functionality: 'file parts with provider references',
192
+ if (topLevel !== 'image' && topLevel !== 'video') {
193
+ throw new UnsupportedFunctionalityError({
194
+ functionality: `file part media type ${part.mediaType}`,
195
+ });
196
+ }
197
+
198
+ const reference = resolveProviderReference({
199
+ reference: part.data.reference,
200
+ provider: 'moonshotai',
49
201
  });
202
+
203
+ if (!reference.startsWith('ms://')) {
204
+ throw new UnsupportedFunctionalityError({
205
+ functionality:
206
+ 'Moonshot file provider references without an ms:// URL',
207
+ });
208
+ }
209
+
210
+ return topLevel === 'image'
211
+ ? {
212
+ type: 'image_url' as const,
213
+ image_url: { url: reference },
214
+ }
215
+ : {
216
+ type: 'video_url' as const,
217
+ video_url: { url: reference },
218
+ };
50
219
  }
51
220
  case 'text': {
221
+ if (topLevel === 'text') {
222
+ return {
223
+ type: 'text' as const,
224
+ text: part.data.text,
225
+ };
226
+ }
227
+
52
228
  throw new UnsupportedFunctionalityError({
53
- functionality: 'text file parts',
229
+ functionality: `file part media type ${part.mediaType}`,
54
230
  });
55
231
  }
56
232
  case 'url':
57
233
  case 'data': {
58
- const topLevel = getTopLevelMediaType(part.mediaType);
59
-
60
234
  if (topLevel === 'image') {
61
235
  return {
62
236
  type: 'image_url' as const,
63
237
  image_url: {
64
- url:
65
- part.data.type === 'url'
66
- ? part.data.url.toString()
67
- : `data:${resolveFullMediaType({ part })};base64,${convertToBase64(part.data.data)}`,
238
+ url: formatMediaUrl({
239
+ part,
240
+ supportedMediaTypes: supportedImageMediaTypes,
241
+ topLevelMediaType: 'image',
242
+ }),
68
243
  },
69
244
  };
70
245
  }
@@ -73,10 +248,11 @@ export function convertToMoonshotAIChatMessages(
73
248
  return {
74
249
  type: 'video_url' as const,
75
250
  video_url: {
76
- url:
77
- part.data.type === 'url'
78
- ? part.data.url.toString()
79
- : `data:${resolveFullMediaType({ part })};base64,${convertToBase64(part.data.data)}`,
251
+ url: formatMediaUrl({
252
+ part,
253
+ supportedMediaTypes: supportedVideoMediaTypes,
254
+ topLevelMediaType: 'video',
255
+ }),
80
256
  },
81
257
  };
82
258
  }
@@ -105,12 +281,33 @@ export function convertToMoonshotAIChatMessages(
105
281
  }
106
282
  }
107
283
  }),
284
+ ...(moonshotMessageOptions?.name != null && {
285
+ name: moonshotMessageOptions.name,
286
+ }),
108
287
  });
109
288
 
110
289
  break;
111
290
  }
112
291
 
113
292
  case 'assistant': {
293
+ if (moonshotMessageOptions?.partial === true) {
294
+ if (index !== prompt.length - 1) {
295
+ throw new InvalidPromptError({
296
+ prompt,
297
+ message:
298
+ 'Moonshot AI Partial Mode requires the partial assistant message to be the final message.',
299
+ });
300
+ }
301
+
302
+ if (responseFormat?.type === 'json_object') {
303
+ throw new InvalidPromptError({
304
+ prompt,
305
+ message:
306
+ 'Moonshot AI Partial Mode cannot be combined with JSON object response format.',
307
+ });
308
+ }
309
+ }
310
+
114
311
  let text = '';
115
312
  let reasoning = '';
116
313
  const toolCalls: Array<{
@@ -146,6 +343,12 @@ export function convertToMoonshotAIChatMessages(
146
343
  messages.push({
147
344
  role: 'assistant',
148
345
  content: toolCalls.length > 0 ? text || null : text,
346
+ ...(moonshotMessageOptions?.name != null && {
347
+ name: moonshotMessageOptions.name,
348
+ }),
349
+ ...(moonshotMessageOptions?.partial === true && {
350
+ partial: true,
351
+ }),
149
352
  ...(reasoning.length > 0 ? { reasoning_content: reasoning } : {}),
150
353
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
151
354
  });
@@ -154,6 +357,13 @@ export function convertToMoonshotAIChatMessages(
154
357
  }
155
358
 
156
359
  case 'tool': {
360
+ if (moonshotMessageOptions?.name != null) {
361
+ warnings.push({
362
+ type: 'unsupported',
363
+ feature: 'message name on tool messages',
364
+ });
365
+ }
366
+
157
367
  for (const toolResponse of content) {
158
368
  if (toolResponse.type === 'tool-approval-response') {
159
369
  continue;
@@ -193,5 +403,5 @@ export function convertToMoonshotAIChatMessages(
193
403
  }
194
404
  }
195
405
 
196
- return messages;
406
+ return { messages, warnings };
197
407
  }
package/src/index.ts CHANGED
@@ -4,8 +4,11 @@ export type {
4
4
  MoonshotAIProviderSettings,
5
5
  } from './moonshotai-provider';
6
6
  export type {
7
+ MoonshotAIAssistantMessageProviderOptions,
7
8
  MoonshotAIChatModelId,
8
9
  MoonshotAILanguageModelOptions,
10
+ MoonshotAIMessageProviderOptions,
11
+ MoonshotAISystemMessageProviderOptions,
9
12
  /** @deprecated Use `MoonshotAILanguageModelOptions` instead. */
10
13
  MoonshotAILanguageModelOptions as MoonshotAIProviderOptions,
11
14
  } from './moonshotai-chat-options';
@@ -9,14 +9,31 @@ export type MoonshotAIMessage =
9
9
  | MoonshotAIAssistantMessage
10
10
  | MoonshotAIToolMessage;
11
11
 
12
- export interface MoonshotAISystemMessage {
13
- role: 'system';
14
- content: string;
12
+ export type MoonshotAISystemMessage =
13
+ | {
14
+ role: 'system';
15
+ content: string;
16
+ name?: string;
17
+ }
18
+ | {
19
+ role: 'system';
20
+ tools: Array<MoonshotAIFunctionTool>;
21
+ };
22
+
23
+ export interface MoonshotAIFunctionTool {
24
+ type: 'function';
25
+ function: {
26
+ name: string;
27
+ description: string | undefined;
28
+ parameters: unknown;
29
+ strict?: boolean;
30
+ };
15
31
  }
16
32
 
17
33
  export interface MoonshotAIUserMessage {
18
34
  role: 'user';
19
35
  content: string | Array<MoonshotAIContentPart>;
36
+ name?: string;
20
37
  }
21
38
 
22
39
  export type MoonshotAIContentPart =
@@ -42,6 +59,8 @@ export interface MoonshotAIContentPartVideo {
42
59
  export interface MoonshotAIAssistantMessage {
43
60
  role: 'assistant';
44
61
  content?: string | null;
62
+ name?: string;
63
+ partial?: true;
45
64
  reasoning_content?: string;
46
65
  tool_calls?: Array<MoonshotAIMessageToolCall>;
47
66
  }
@@ -64,19 +83,20 @@ export interface MoonshotAIToolMessage {
64
83
  // Schemas below are limited versions focused on what the implementation
65
84
  // needs. This limits breakages when the API changes and increases efficiency.
66
85
 
86
+ // Loose, nested objects included: the parsed value is returned as `usage.raw`.
67
87
  const tokenUsageSchema = z
68
- .object({
88
+ .looseObject({
69
89
  prompt_tokens: z.number().nullish(),
70
90
  completion_tokens: z.number().nullish(),
71
91
  cached_tokens: z.number().nullish(),
72
92
  total_tokens: z.number().nullish(),
73
93
  prompt_tokens_details: z
74
- .object({
94
+ .looseObject({
75
95
  cached_tokens: z.number().nullish(),
76
96
  })
77
97
  .nullish(),
78
98
  completion_tokens_details: z
79
- .object({
99
+ .looseObject({
80
100
  reasoning_tokens: z.number().nullish(),
81
101
  })
82
102
  .nullish(),
@@ -89,17 +109,41 @@ export const moonshotAIErrorSchema = z.object({
89
109
  error: z.object({
90
110
  message: z.string(),
91
111
  type: z.string().nullish(),
112
+ code: z.string().nullish(),
92
113
  }),
93
114
  });
94
115
 
95
116
  export type MoonshotAIErrorData = z.infer<typeof moonshotAIErrorSchema>;
96
117
 
118
+ const moonshotAIChatLogprobSchema = z.object({
119
+ token: z.string(),
120
+ logprob: z.number(),
121
+ bytes: z.array(z.number()).nullable(),
122
+ top_logprobs: z.array(
123
+ z.object({
124
+ token: z.string(),
125
+ logprob: z.number(),
126
+ bytes: z.array(z.number()).nullable(),
127
+ }),
128
+ ),
129
+ });
130
+
131
+ const moonshotAIChatLogprobsSchema = z
132
+ .object({
133
+ content: z.array(moonshotAIChatLogprobSchema).nullish(),
134
+ })
135
+ .nullish();
136
+
137
+ export type MoonshotAIChatLogprob = z.infer<typeof moonshotAIChatLogprobSchema>;
138
+
97
139
  export const moonshotAIChatResponseSchema = z.object({
98
140
  id: z.string().nullish(),
99
141
  created: z.number().nullish(),
100
142
  model: z.string().nullish(),
143
+ object: z.literal('chat.completion').nullish(),
101
144
  choices: z.array(
102
145
  z.object({
146
+ index: z.number().nullish(),
103
147
  message: z.object({
104
148
  role: z.literal('assistant').nullish(),
105
149
  content: z.string().nullish(),
@@ -108,6 +152,7 @@ export const moonshotAIChatResponseSchema = z.object({
108
152
  .array(
109
153
  z.object({
110
154
  id: z.string().nullish(),
155
+ type: z.literal('function').nullish(),
111
156
  function: z.object({
112
157
  name: z.string(),
113
158
  arguments: z.string(),
@@ -116,6 +161,7 @@ export const moonshotAIChatResponseSchema = z.object({
116
161
  )
117
162
  .nullish(),
118
163
  }),
164
+ logprobs: moonshotAIChatLogprobsSchema,
119
165
  finish_reason: z.string().nullish(),
120
166
  }),
121
167
  ),
@@ -129,8 +175,10 @@ export const moonshotAIChatChunkSchema = lazySchema(() =>
129
175
  id: z.string().nullish(),
130
176
  created: z.number().nullish(),
131
177
  model: z.string().nullish(),
178
+ object: z.literal('chat.completion.chunk').nullish(),
132
179
  choices: z.array(
133
180
  z.object({
181
+ index: z.number().nullish(),
134
182
  delta: z
135
183
  .object({
136
184
  role: z.literal('assistant').nullish(),
@@ -139,8 +187,9 @@ export const moonshotAIChatChunkSchema = lazySchema(() =>
139
187
  tool_calls: z
140
188
  .array(
141
189
  z.object({
142
- index: z.number(),
190
+ index: z.number().nullish(),
143
191
  id: z.string().nullish(),
192
+ type: z.literal('function').nullish(),
144
193
  function: z.object({
145
194
  name: z.string().nullish(),
146
195
  arguments: z.string().nullish(),
@@ -150,7 +199,9 @@ export const moonshotAIChatChunkSchema = lazySchema(() =>
150
199
  .nullish(),
151
200
  })
152
201
  .nullish(),
202
+ logprobs: moonshotAIChatLogprobsSchema,
153
203
  finish_reason: z.string().nullish(),
204
+ usage: tokenUsageSchema,
154
205
  }),
155
206
  ),
156
207
  usage: tokenUsageSchema,