@ai-sdk/moonshotai 0.0.20 → 0.0.25

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.
@@ -0,0 +1,112 @@
1
+ import type { MetadataExtractor } from '@ai-sdk/openai-compatible';
2
+ import type { JSONValue } from '@ai-sdk/provider';
3
+
4
+ function isRecord(value: unknown): value is Record<string, unknown> {
5
+ return value != null && typeof value === 'object' && !Array.isArray(value);
6
+ }
7
+
8
+ function getFirstChoice(value: Record<string, unknown>) {
9
+ const choices = value.choices;
10
+ return Array.isArray(choices) && isRecord(choices[0])
11
+ ? choices[0]
12
+ : undefined;
13
+ }
14
+
15
+ function getToolCallTypes(value: unknown): Array<'function'> | undefined {
16
+ if (!Array.isArray(value)) {
17
+ return undefined;
18
+ }
19
+
20
+ return value.flatMap(toolCall =>
21
+ isRecord(toolCall) && toolCall.type === 'function' ? [toolCall.type] : [],
22
+ );
23
+ }
24
+
25
+ function extractMetadata(value: unknown): Record<string, JSONValue> {
26
+ if (!isRecord(value)) {
27
+ return {};
28
+ }
29
+
30
+ const choice = getFirstChoice(value);
31
+ const message =
32
+ choice != null && isRecord(choice.message) ? choice.message : undefined;
33
+ const toolCallTypes = getToolCallTypes(message?.tool_calls);
34
+
35
+ return {
36
+ ...(value.object === 'chat.completion' && {
37
+ responseObject: value.object,
38
+ }),
39
+ ...(typeof choice?.index === 'number' && {
40
+ choiceIndex: choice.index,
41
+ }),
42
+ ...(message?.role === 'assistant' && {
43
+ messageRole: message.role,
44
+ }),
45
+ ...(toolCallTypes != null && { toolCallTypes }),
46
+ };
47
+ }
48
+
49
+ export const moonshotAIChatMetadataExtractor: MetadataExtractor = {
50
+ async extractMetadata({ parsedBody }) {
51
+ return { moonshotai: extractMetadata(parsedBody) };
52
+ },
53
+
54
+ createStreamExtractor() {
55
+ let responseObject: 'chat.completion.chunk' | undefined;
56
+ let choiceIndex: number | undefined;
57
+ let messageRole: 'assistant' | undefined;
58
+ const toolCallTypes = new Map<number, 'function'>();
59
+
60
+ return {
61
+ processChunk(parsedChunk) {
62
+ if (!isRecord(parsedChunk)) {
63
+ return;
64
+ }
65
+
66
+ if (parsedChunk.object === 'chat.completion.chunk') {
67
+ responseObject = parsedChunk.object;
68
+ }
69
+
70
+ const choice = getFirstChoice(parsedChunk);
71
+ if (typeof choice?.index === 'number') {
72
+ choiceIndex = choice.index;
73
+ }
74
+
75
+ const delta =
76
+ choice != null && isRecord(choice.delta) ? choice.delta : undefined;
77
+ if (delta?.role === 'assistant') {
78
+ messageRole = delta.role;
79
+ }
80
+
81
+ if (Array.isArray(delta?.tool_calls)) {
82
+ for (const [fallbackIndex, toolCall] of delta.tool_calls.entries()) {
83
+ if (!isRecord(toolCall) || toolCall.type !== 'function') {
84
+ continue;
85
+ }
86
+
87
+ const index =
88
+ typeof toolCall.index === 'number'
89
+ ? toolCall.index
90
+ : fallbackIndex;
91
+ toolCallTypes.set(index, toolCall.type);
92
+ }
93
+ }
94
+ },
95
+
96
+ buildMetadata() {
97
+ return {
98
+ moonshotai: {
99
+ ...(responseObject != null && { responseObject }),
100
+ ...(choiceIndex != null && { choiceIndex }),
101
+ ...(messageRole != null && { messageRole }),
102
+ ...(toolCallTypes.size > 0 && {
103
+ toolCallTypes: [...toolCallTypes.entries()]
104
+ .sort(([left], [right]) => left - right)
105
+ .map(([, type]) => type),
106
+ }),
107
+ },
108
+ };
109
+ },
110
+ };
111
+ },
112
+ };
@@ -1,34 +1,212 @@
1
+ import type { LanguageModelV2FunctionTool } from '@ai-sdk/provider';
1
2
  import { z } from 'zod/v4';
2
3
 
3
4
  export type MoonshotAIChatModelId =
5
+ | 'moonshot-v1-auto'
4
6
  | 'moonshot-v1-8k'
5
7
  | 'moonshot-v1-32k'
6
8
  | 'moonshot-v1-128k'
7
- | 'kimi-k2'
8
- | 'kimi-k2-0905'
9
- | 'kimi-k2-thinking'
10
- | 'kimi-k2-thinking-turbo'
11
- | 'kimi-k2-turbo'
9
+ | 'moonshot-v1-8k-vision-preview'
10
+ | 'moonshot-v1-32k-vision-preview'
11
+ | 'moonshot-v1-128k-vision-preview'
12
12
  | 'kimi-k2.5'
13
+ | 'kimi-k2.6'
14
+ | 'kimi-k2.7-code'
15
+ | 'kimi-k2.7-code-highspeed'
13
16
  | 'kimi-k3'
14
17
  | (string & {});
15
18
 
19
+ export type MoonshotAIModelFamily =
20
+ | 'kimi-k2.5'
21
+ | 'kimi-k2.6'
22
+ | 'kimi-k2.7'
23
+ | 'kimi-k3'
24
+ | 'moonshot-v1'
25
+ | 'unknown';
26
+
27
+ export function getMoonshotAIModelFamily(
28
+ modelId: MoonshotAIChatModelId,
29
+ ): MoonshotAIModelFamily {
30
+ if (modelId === 'kimi-k2.5') return 'kimi-k2.5';
31
+ if (modelId === 'kimi-k2.6') return 'kimi-k2.6';
32
+ if (modelId === 'kimi-k2.7-code' || modelId === 'kimi-k2.7-code-highspeed') {
33
+ return 'kimi-k2.7';
34
+ }
35
+ if (modelId === 'kimi-k3') return 'kimi-k3';
36
+ if (modelId.startsWith('moonshot-v1-')) return 'moonshot-v1';
37
+ return 'unknown';
38
+ }
39
+
40
+ export function isMoonshotAIKimiModel(modelId: MoonshotAIChatModelId): boolean {
41
+ return getMoonshotAIModelFamily(modelId).startsWith('kimi-');
42
+ }
43
+
16
44
  export const moonshotaiProviderOptions = z.object({
17
45
  /**
18
- * Reasoning effort for Kimi K3. Currently, only `max` is supported.
46
+ * Whether to use strict JSON schema validation for structured outputs.
47
+ *
48
+ * @default true
49
+ */
50
+ strictJsonSchema: z.boolean().optional(),
51
+
52
+ /**
53
+ * Whether to return log probabilities for generated tokens.
54
+ */
55
+ logprobs: z.boolean().optional(),
56
+
57
+ /**
58
+ * Number of most likely tokens to return at each token position.
59
+ *
60
+ * Setting this option automatically enables `logprobs`.
61
+ */
62
+ topLogprobs: z.number().int().min(0).max(20).optional(),
63
+
64
+ /**
65
+ * Reasoning effort for Kimi K3. Supports `low`, `high`, and `max`;
66
+ * defaults to `max`.
67
+ */
68
+ reasoningEffort: z.enum(['low', 'high', 'max']).optional(),
69
+
70
+ /**
71
+ * Static predicted content that can accelerate responses when much of the
72
+ * output is known ahead of time.
19
73
  */
20
- reasoningEffort: z.literal('max').optional(),
74
+ prediction: z
75
+ .object({
76
+ type: z.literal('content'),
77
+ content: z.union([
78
+ z.string(),
79
+ z.array(z.object({ type: z.literal('text'), text: z.string() })),
80
+ ]),
81
+ })
82
+ .optional(),
21
83
 
84
+ /**
85
+ * Thinking configuration for Kimi K2.x models. Kimi K2.5 and K2.6 support
86
+ * enabling or disabling thinking. Kimi K2.7 Code always has thinking
87
+ * enabled.
88
+ */
22
89
  thinking: z
23
90
  .object({
24
91
  type: z.enum(['enabled', 'disabled']).optional(),
92
+ /**
93
+ * @deprecated Moonshot Chat Completions does not support thinking
94
+ * budgets. Accepted for backwards compatibility, then omitted with a
95
+ * warning.
96
+ */
25
97
  budgetTokens: z.number().int().min(1024).optional(),
26
98
  })
27
99
  .optional(),
28
100
 
101
+ /**
102
+ * Controls preserved reasoning behavior in multi-turn conversations.
103
+ * `disabled` and `interleaved` are compatibility values that leave the
104
+ * request unchanged. `preserved` maps to `thinking.keep: 'all'` for Kimi
105
+ * K2.6. Kimi K2.7 and K3 preserve reasoning by default.
106
+ */
29
107
  reasoningHistory: z.enum(['disabled', 'interleaved', 'preserved']).optional(),
30
108
  });
31
109
 
32
- export type MoonshotAIProviderOptions = z.infer<
33
- typeof moonshotaiProviderOptions
110
+ export type MoonshotAIProviderOptions = {
111
+ /**
112
+ * Whether to use strict JSON schema validation for structured outputs.
113
+ *
114
+ * @default true
115
+ */
116
+ strictJsonSchema?: boolean;
117
+
118
+ /** Whether to return log probabilities for generated tokens. */
119
+ logprobs?: boolean;
120
+
121
+ /**
122
+ * Number of most likely tokens to return at each token position.
123
+ * Setting this option automatically enables `logprobs`.
124
+ */
125
+ topLogprobs?: number;
126
+
127
+ /** Reasoning effort for Kimi K3. */
128
+ reasoningEffort?: 'low' | 'high' | 'max';
129
+
130
+ /**
131
+ * Static predicted content that can accelerate responses when much of the
132
+ * output is known ahead of time.
133
+ */
134
+ prediction?: {
135
+ type: 'content';
136
+ content: string | Array<{ type: 'text'; text: string }>;
137
+ };
138
+
139
+ /** Controls thinking on Kimi K2.5 and K2.6. K2.7 is always enabled. */
140
+ thinking?: {
141
+ type?: 'enabled' | 'disabled';
142
+
143
+ /**
144
+ * @deprecated Moonshot Chat Completions does not support thinking budgets.
145
+ * This value is ignored with a warning.
146
+ */
147
+ budgetTokens?: number;
148
+ };
149
+
150
+ /**
151
+ * Controls preserved reasoning behavior in multi-turn conversations.
152
+ * `disabled` and `interleaved` are compatibility values that leave the
153
+ * request unchanged. `preserved` maps to `thinking.keep: 'all'` for Kimi
154
+ * K2.6. Kimi K2.7 and K3 preserve reasoning by default.
155
+ */
156
+ reasoningHistory?: 'disabled' | 'interleaved' | 'preserved';
157
+ };
158
+
159
+ export const moonshotaiMessageProviderOptions = z.object({
160
+ /**
161
+ * The name of the participant represented by the message.
162
+ *
163
+ * Supported on system, user, and assistant messages.
164
+ */
165
+ name: z.string().optional(),
166
+ });
167
+
168
+ export type MoonshotAIMessageProviderOptions = z.infer<
169
+ typeof moonshotaiMessageProviderOptions
170
+ >;
171
+
172
+ export const moonshotaiAssistantMessageProviderOptions =
173
+ moonshotaiMessageProviderOptions.extend({
174
+ /**
175
+ * Whether the assistant message content is a partial response that Moonshot
176
+ * should continue. Only supported on the final assistant message and cannot
177
+ * be combined with JSON object response format.
178
+ */
179
+ partial: z.literal(true).optional(),
180
+ });
181
+
182
+ export type MoonshotAIAssistantMessageProviderOptions = z.infer<
183
+ typeof moonshotaiAssistantMessageProviderOptions
34
184
  >;
185
+
186
+ const moonshotaiDynamicToolSchema = z.object({
187
+ type: z.literal('function'),
188
+ name: z.string(),
189
+ description: z.string().optional(),
190
+ inputSchema: z.record(z.string(), z.unknown()),
191
+ strict: z.boolean().optional(),
192
+ });
193
+
194
+ export const moonshotaiAllMessageProviderOptions =
195
+ moonshotaiAssistantMessageProviderOptions.extend({
196
+ /** Function tools to load at this point in a Kimi K3 conversation. */
197
+ tools: z.array(moonshotaiDynamicToolSchema).optional(),
198
+ });
199
+
200
+ export type MoonshotAISystemMessageProviderOptions =
201
+ MoonshotAIMessageProviderOptions & {
202
+ /** Function tools to load at this point in a Kimi K3 conversation. */
203
+ tools?: Array<
204
+ Pick<
205
+ LanguageModelV2FunctionTool,
206
+ 'type' | 'name' | 'description' | 'inputSchema'
207
+ > & {
208
+ /** Whether Moonshot should enforce the tool input schema strictly. */
209
+ strict?: boolean;
210
+ }
211
+ >;
212
+ };
@@ -21,6 +21,7 @@ const moonshotaiErrorSchema = z.object({
21
21
  error: z.object({
22
22
  message: z.string(),
23
23
  type: z.string().nullish(),
24
+ code: z.string().nullish(),
24
25
  }),
25
26
  });
26
27
 
@@ -69,6 +70,21 @@ export interface MoonshotAIProvider extends ProviderV2 {
69
70
 
70
71
  const defaultBaseURL = 'https://api.moonshot.ai/v1';
71
72
 
73
+ export function getModelStructuredOutputSupport(
74
+ modelId: MoonshotAIChatModelId,
75
+ ): boolean {
76
+ return (
77
+ modelId.startsWith('kimi-k') ||
78
+ modelId === 'moonshot-v1-8k' ||
79
+ modelId === 'moonshot-v1-32k' ||
80
+ modelId === 'moonshot-v1-128k' ||
81
+ modelId === 'moonshot-v1-auto' ||
82
+ modelId === 'moonshot-v1-8k-vision-preview' ||
83
+ modelId === 'moonshot-v1-32k-vision-preview' ||
84
+ modelId === 'moonshot-v1-128k-vision-preview'
85
+ );
86
+ }
87
+
72
88
  export function createMoonshotAI(
73
89
  options: MoonshotAIProviderSettings = {},
74
90
  ): MoonshotAIProvider {
@@ -105,6 +121,7 @@ export function createMoonshotAI(
105
121
  ...getCommonModelConfig('chat'),
106
122
  includeUsage: true,
107
123
  errorStructure: moonshotaiErrorStructure,
124
+ supportsStructuredOutputs: getModelStructuredOutputSupport(modelId),
108
125
  });
109
126
  };
110
127
 
@@ -0,0 +1,103 @@
1
+ import { UnsupportedFunctionalityError } from '@ai-sdk/provider';
2
+
3
+ const SCHEMA_ARRAY_KEYS = ['allOf', 'anyOf', 'oneOf', 'prefixItems'] as const;
4
+ const SCHEMA_MAP_KEYS = [
5
+ 'properties',
6
+ 'patternProperties',
7
+ '$defs',
8
+ 'dependentSchemas',
9
+ ] as const;
10
+ const SCHEMA_SINGLE_KEYS = [
11
+ 'additionalProperties',
12
+ 'propertyNames',
13
+ 'items',
14
+ 'contains',
15
+ 'not',
16
+ 'if',
17
+ 'then',
18
+ 'else',
19
+ ] as const;
20
+
21
+ function isRecord(value: unknown): value is Record<string, unknown> {
22
+ return value != null && typeof value === 'object' && !Array.isArray(value);
23
+ }
24
+
25
+ /**
26
+ * Normalizes a JSON Schema to the subset Moonshot's MFJS validator accepts:
27
+ * `object` root required, tuple `items` become `prefixItems`, and `type` next
28
+ * to `anyOf` moves into the branches. Everything else passes through. The
29
+ * full original schema is still used for AI SDK result validation.
30
+ */
31
+ export function normalizeJsonSchemaForMFJS(schema: unknown): unknown {
32
+ return normalizeDefinition(schema, true);
33
+ }
34
+
35
+ function normalizeDefinition(definition: unknown, isRoot: boolean): unknown {
36
+ if (typeof definition === 'boolean' || !isRecord(definition)) {
37
+ if (isRoot) {
38
+ throw new UnsupportedFunctionalityError({
39
+ functionality:
40
+ 'tool parameters must be a JSON Schema object with type "object" for moonshotai (MFJS)',
41
+ });
42
+ }
43
+ return definition;
44
+ }
45
+
46
+ if (isRoot && definition.type !== 'object') {
47
+ throw new UnsupportedFunctionalityError({
48
+ functionality:
49
+ 'tool parameters must be a JSON Schema object with type "object" for moonshotai (MFJS)',
50
+ });
51
+ }
52
+
53
+ const result: Record<string, unknown> = { ...definition };
54
+
55
+ if (Array.isArray(result.items)) {
56
+ const tuple = result.items;
57
+ result.prefixItems = [
58
+ ...(Array.isArray(result.prefixItems) ? result.prefixItems : []),
59
+ ...tuple.map(item => normalizeDefinition(item, false)),
60
+ ];
61
+ delete result.items;
62
+ } else if (isRecord(result.items)) {
63
+ result.items = normalizeDefinition(result.items, false);
64
+ }
65
+
66
+ if (typeof result.type === 'string' && Array.isArray(result.anyOf)) {
67
+ const parentType = result.type;
68
+ delete result.type;
69
+ result.anyOf = result.anyOf.map(branch =>
70
+ isRecord(branch) && branch.type == null
71
+ ? { type: parentType, ...branch }
72
+ : branch,
73
+ );
74
+ }
75
+
76
+ for (const key of SCHEMA_ARRAY_KEYS) {
77
+ const value = result[key];
78
+ if (Array.isArray(value)) {
79
+ result[key] = value.map(item => normalizeDefinition(item, false));
80
+ }
81
+ }
82
+
83
+ for (const key of SCHEMA_MAP_KEYS) {
84
+ const value = result[key];
85
+ if (isRecord(value)) {
86
+ result[key] = Object.fromEntries(
87
+ Object.entries(value).map(([key, item]) => [
88
+ key,
89
+ normalizeDefinition(item, false),
90
+ ]),
91
+ );
92
+ }
93
+ }
94
+
95
+ for (const key of SCHEMA_SINGLE_KEYS) {
96
+ const value = result[key];
97
+ if (isRecord(value) || typeof value === 'boolean') {
98
+ result[key] = normalizeDefinition(value, false);
99
+ }
100
+ }
101
+
102
+ return result;
103
+ }