@ai-sdk/alibaba 2.0.0-beta.6 → 2.0.0-beta.60

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.
@@ -3,34 +3,39 @@ import {
3
3
  mapOpenAICompatibleFinishReason,
4
4
  prepareTools,
5
5
  } from '@ai-sdk/openai-compatible/internal';
6
- import {
7
- InvalidResponseDataError,
8
- type LanguageModelV4,
9
- type LanguageModelV4CallOptions,
10
- type LanguageModelV4Content,
11
- type LanguageModelV4FinishReason,
12
- type LanguageModelV4GenerateResult,
13
- type LanguageModelV4StreamPart,
14
- type LanguageModelV4StreamResult,
15
- type SharedV4Warning,
6
+ import type {
7
+ LanguageModelV4,
8
+ LanguageModelV4CallOptions,
9
+ LanguageModelV4Content,
10
+ LanguageModelV4FinishReason,
11
+ LanguageModelV4GenerateResult,
12
+ LanguageModelV4StreamPart,
13
+ LanguageModelV4StreamResult,
14
+ SharedV4Warning,
16
15
  } from '@ai-sdk/provider';
17
16
  import {
18
17
  combineHeaders,
19
18
  createEventSourceResponseHandler,
20
19
  createJsonResponseHandler,
21
20
  generateId,
22
- isParsableJson,
21
+ isCustomReasoning,
22
+ mapReasoningToProviderBudget,
23
23
  parseProviderOptions,
24
24
  postJsonToApi,
25
+ serializeModelOptions,
26
+ StreamingToolCallTracker,
27
+ WORKFLOW_SERIALIZE,
28
+ WORKFLOW_DESERIALIZE,
29
+ type InferSchema,
25
30
  type ParseResult,
26
31
  } from '@ai-sdk/provider-utils';
27
32
  import { z } from 'zod/v4';
28
33
  import {
29
- alibabaLanguageModelOptions,
34
+ alibabaLanguageModelChatOptions,
30
35
  type AlibabaChatModelId,
31
- } from './alibaba-chat-options';
36
+ } from './alibaba-chat-language-model-options';
32
37
  import type { AlibabaConfig } from './alibaba-config';
33
- import { alibabaFailedResponseHandler } from './alibaba-provider';
38
+ import { alibabaFailedResponseHandler } from './alibaba-error';
34
39
  import { convertAlibabaUsage } from './convert-alibaba-usage';
35
40
  import { convertToAlibabaChatMessages } from './convert-to-alibaba-chat-messages';
36
41
  import { CacheControlValidator } from './get-cache-control';
@@ -44,12 +49,26 @@ import { CacheControlValidator } from './get-cache-control';
44
49
  * - Thinking budget control (thinking_budget)
45
50
  * - Prompt caching (cached_tokens tracking)
46
51
  */
47
- export class AlibabaLanguageModel implements LanguageModelV4 {
52
+ export class AlibabaChatLanguageModel implements LanguageModelV4 {
48
53
  readonly specificationVersion = 'v4';
49
54
  readonly modelId: AlibabaChatModelId;
50
55
 
51
56
  private readonly config: AlibabaConfig;
52
57
 
58
+ static [WORKFLOW_SERIALIZE](model: AlibabaChatLanguageModel) {
59
+ return serializeModelOptions({
60
+ modelId: model.modelId,
61
+ config: model.config,
62
+ });
63
+ }
64
+
65
+ static [WORKFLOW_DESERIALIZE](options: {
66
+ modelId: AlibabaChatModelId;
67
+ config: AlibabaConfig;
68
+ }) {
69
+ return new AlibabaChatLanguageModel(options.modelId, options.config);
70
+ }
71
+
53
72
  constructor(modelId: AlibabaChatModelId, config: AlibabaConfig) {
54
73
  this.modelId = modelId;
55
74
  this.config = config;
@@ -78,6 +97,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
78
97
  stopSequences,
79
98
  responseFormat,
80
99
  seed,
100
+ reasoning,
81
101
  providerOptions,
82
102
  tools,
83
103
  toolChoice,
@@ -89,7 +109,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
89
109
  const alibabaOptions = await parseProviderOptions({
90
110
  provider: 'alibaba',
91
111
  providerOptions,
92
- schema: alibabaLanguageModelOptions,
112
+ schema: alibabaLanguageModelChatOptions,
93
113
  });
94
114
 
95
115
  // Warn about unsupported features
@@ -121,13 +141,11 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
121
141
  : { type: 'json_object' }
122
142
  : undefined,
123
143
 
124
- // Alibaba-specific options
125
- ...(alibabaOptions?.enableThinking != null
126
- ? { enable_thinking: alibabaOptions.enableThinking }
127
- : {}),
128
- ...(alibabaOptions?.thinkingBudget != null
129
- ? { thinking_budget: alibabaOptions.thinkingBudget }
130
- : {}),
144
+ ...resolveAlibabaThinking({
145
+ reasoning,
146
+ alibabaOptions,
147
+ warnings,
148
+ }),
131
149
 
132
150
  // Convert messages with cache control support
133
151
  messages: convertToAlibabaChatMessages({
@@ -170,7 +188,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
170
188
  rawValue: rawResponse,
171
189
  } = await postJsonToApi({
172
190
  url: `${this.config.baseURL}/chat/completions`,
173
- headers: combineHeaders(this.config.headers(), options.headers),
191
+ headers: combineHeaders(this.config.headers?.(), options.headers),
174
192
  body: args,
175
193
  failedResponseHandler: alibabaFailedResponseHandler,
176
194
  successfulResponseHandler: createJsonResponseHandler(
@@ -203,7 +221,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
203
221
  for (const toolCall of choice.message.tool_calls) {
204
222
  content.push({
205
223
  type: 'tool-call',
206
- toolCallId: toolCall.id,
224
+ toolCallId: toolCall.id ?? generateId(),
207
225
  toolName: toolCall.function.name,
208
226
  input: toolCall.function.arguments!,
209
227
  });
@@ -241,7 +259,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
241
259
 
242
260
  const { responseHeaders, value: response } = await postJsonToApi({
243
261
  url: `${this.config.baseURL}/chat/completions`,
244
- headers: combineHeaders(this.config.headers(), options.headers),
262
+ headers: combineHeaders(this.config.headers?.(), options.headers),
245
263
  body,
246
264
  failedResponseHandler: alibabaFailedResponseHandler,
247
265
  successfulResponseHandler: createEventSourceResponseHandler(
@@ -262,13 +280,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
262
280
  let activeText = false;
263
281
  let activeReasoningId: string | null = null;
264
282
 
265
- // Track tool calls for accumulation across chunks
266
- const toolCalls: Array<{
267
- id: string;
268
- type: 'function';
269
- function: { name: string; arguments: string };
270
- hasFinished: boolean;
271
- }> = [];
283
+ let toolCallTracker: StreamingToolCallTracker;
272
284
 
273
285
  return {
274
286
  stream: response.pipeThrough(
@@ -277,6 +289,9 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
277
289
  LanguageModelV4StreamPart
278
290
  >({
279
291
  start(controller) {
292
+ toolCallTracker = new StreamingToolCallTracker(controller, {
293
+ generateId,
294
+ });
280
295
  controller.enqueue({ type: 'stream-start', warnings });
281
296
  },
282
297
 
@@ -381,106 +396,7 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
381
396
  }
382
397
 
383
398
  for (const toolCallDelta of delta.tool_calls) {
384
- const index = toolCallDelta.index ?? toolCalls.length;
385
-
386
- // New tool call - first chunk with id and name
387
- if (toolCalls[index] == null) {
388
- if (toolCallDelta.id == null) {
389
- throw new InvalidResponseDataError({
390
- data: toolCallDelta,
391
- message: `Expected 'id' to be a string.`,
392
- });
393
- }
394
-
395
- if (toolCallDelta.function?.name == null) {
396
- throw new InvalidResponseDataError({
397
- data: toolCallDelta,
398
- message: `Expected 'function.name' to be a string.`,
399
- });
400
- }
401
-
402
- controller.enqueue({
403
- type: 'tool-input-start',
404
- id: toolCallDelta.id,
405
- toolName: toolCallDelta.function.name,
406
- });
407
-
408
- toolCalls[index] = {
409
- id: toolCallDelta.id,
410
- type: 'function',
411
- function: {
412
- name: toolCallDelta.function.name,
413
- arguments: toolCallDelta.function.arguments ?? '',
414
- },
415
- hasFinished: false,
416
- };
417
-
418
- const toolCall = toolCalls[index];
419
-
420
- // Send initial delta if arguments started
421
- if (toolCall.function.arguments.length > 0) {
422
- controller.enqueue({
423
- type: 'tool-input-delta',
424
- id: toolCall.id,
425
- delta: toolCall.function.arguments,
426
- });
427
- }
428
-
429
- // Check if already complete (some providers send full tool call at once)
430
- if (isParsableJson(toolCall.function.arguments)) {
431
- controller.enqueue({
432
- type: 'tool-input-end',
433
- id: toolCall.id,
434
- });
435
-
436
- controller.enqueue({
437
- type: 'tool-call',
438
- toolCallId: toolCall.id,
439
- toolName: toolCall.function.name,
440
- input: toolCall.function.arguments,
441
- });
442
-
443
- toolCall.hasFinished = true;
444
- }
445
-
446
- continue;
447
- }
448
-
449
- // Existing tool call - accumulate arguments
450
- const toolCall = toolCalls[index];
451
-
452
- if (toolCall.hasFinished) {
453
- continue;
454
- }
455
-
456
- // Append arguments if not null (skip arguments: null chunks)
457
- if (toolCallDelta.function?.arguments != null) {
458
- toolCall.function.arguments +=
459
- toolCallDelta.function.arguments;
460
-
461
- controller.enqueue({
462
- type: 'tool-input-delta',
463
- id: toolCall.id,
464
- delta: toolCallDelta.function.arguments,
465
- });
466
- }
467
-
468
- // Check if tool call is now complete
469
- if (isParsableJson(toolCall.function.arguments)) {
470
- controller.enqueue({
471
- type: 'tool-input-end',
472
- id: toolCall.id,
473
- });
474
-
475
- controller.enqueue({
476
- type: 'tool-call',
477
- toolCallId: toolCall.id,
478
- toolName: toolCall.function.name,
479
- input: toolCall.function.arguments,
480
- });
481
-
482
- toolCall.hasFinished = true;
483
- }
399
+ toolCallTracker.processDelta(toolCallDelta);
484
400
  }
485
401
  }
486
402
 
@@ -505,6 +421,8 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
505
421
  controller.enqueue({ type: 'text-end', id: '0' });
506
422
  }
507
423
 
424
+ toolCallTracker.flush();
425
+
508
426
  controller.enqueue({
509
427
  type: 'finish',
510
428
  finishReason,
@@ -519,6 +437,52 @@ export class AlibabaLanguageModel implements LanguageModelV4 {
519
437
  }
520
438
  }
521
439
 
440
+ function resolveAlibabaThinking({
441
+ reasoning,
442
+ alibabaOptions,
443
+ warnings,
444
+ }: {
445
+ reasoning: LanguageModelV4CallOptions['reasoning'];
446
+ alibabaOptions:
447
+ | InferSchema<typeof alibabaLanguageModelChatOptions>
448
+ | undefined;
449
+ warnings: SharedV4Warning[];
450
+ }): { enable_thinking?: boolean; thinking_budget?: number } {
451
+ if (
452
+ alibabaOptions?.enableThinking != null ||
453
+ alibabaOptions?.thinkingBudget != null
454
+ ) {
455
+ return {
456
+ ...(alibabaOptions.enableThinking != null
457
+ ? { enable_thinking: alibabaOptions.enableThinking }
458
+ : {}),
459
+ ...(alibabaOptions.thinkingBudget != null
460
+ ? { thinking_budget: alibabaOptions.thinkingBudget }
461
+ : {}),
462
+ };
463
+ }
464
+
465
+ if (!isCustomReasoning(reasoning)) {
466
+ return {};
467
+ }
468
+
469
+ if (reasoning === 'none') {
470
+ return { enable_thinking: false };
471
+ }
472
+
473
+ const thinkingBudget = mapReasoningToProviderBudget({
474
+ reasoning,
475
+ maxOutputTokens: 16384,
476
+ maxReasoningBudget: 16384,
477
+ warnings,
478
+ });
479
+
480
+ return {
481
+ enable_thinking: true,
482
+ ...(thinkingBudget != null ? { thinking_budget: thinkingBudget } : {}),
483
+ };
484
+ }
485
+
522
486
  /**
523
487
  * Reference for schemas below:
524
488
  * https://www.alibabacloud.com/help/en/model-studio/qwen-api-via-openai-chat-completions
@@ -3,7 +3,7 @@ import type { FetchFunction } from '@ai-sdk/provider-utils';
3
3
  export interface AlibabaConfig {
4
4
  provider: string;
5
5
  baseURL: string;
6
- headers: () => Record<string, string | undefined>;
6
+ headers?: () => Record<string, string | undefined>;
7
7
  fetch?: FetchFunction;
8
8
  includeUsage?: boolean;
9
9
  }
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ export type AlibabaEmbeddingModelId =
4
+ | 'text-embedding-v4'
5
+ | 'text-embedding-v3'
6
+ | (string & {});
7
+
8
+ export const alibabaEmbeddingModelOptions = z.object({
9
+ /**
10
+ * Differentiates query text from document text for asymmetric retrieval tasks.
11
+ * Defaults to `document`.
12
+ */
13
+ textType: z.enum(['query', 'document']).optional(),
14
+
15
+ /**
16
+ * The dimension of the output embedding vectors. Defaults to 1024.
17
+ *
18
+ * `text-embedding-v4` also supports 1536 and 2048 dimensions.
19
+ */
20
+ dimension: z.number().optional(),
21
+
22
+ /**
23
+ * The output vector type. Defaults to `dense`.
24
+ *
25
+ * Sparse-only output is not supported by the AI SDK embedding interface,
26
+ * which requires dense number arrays.
27
+ */
28
+ outputType: z.enum(['dense', 'sparse', 'dense&sparse']).optional(),
29
+ });
30
+
31
+ export type AlibabaEmbeddingModelOptions = z.infer<
32
+ typeof alibabaEmbeddingModelOptions
33
+ >;
@@ -0,0 +1,178 @@
1
+ import {
2
+ TooManyEmbeddingValuesForCallError,
3
+ UnsupportedFunctionalityError,
4
+ type EmbeddingModelV4,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ combineHeaders,
8
+ createJsonErrorResponseHandler,
9
+ createJsonResponseHandler,
10
+ parseProviderOptions,
11
+ postJsonToApi,
12
+ serializeModelOptions,
13
+ WORKFLOW_SERIALIZE,
14
+ WORKFLOW_DESERIALIZE,
15
+ } from '@ai-sdk/provider-utils';
16
+ import { z } from 'zod/v4';
17
+ import {
18
+ alibabaEmbeddingModelOptions,
19
+ type AlibabaEmbeddingModelId,
20
+ } from './alibaba-embedding-model-options';
21
+ import type { AlibabaConfig } from './alibaba-config';
22
+
23
+ // TODO: Add Alibaba multimodal embedding support in a follow-up change.
24
+ const alibabaEmbeddingFailedResponseHandler = createJsonErrorResponseHandler({
25
+ errorSchema: z.object({
26
+ code: z.string().nullish(),
27
+ message: z.string(),
28
+ request_id: z.string().nullish(),
29
+ }),
30
+ errorToMessage: data => data.message,
31
+ });
32
+
33
+ export class AlibabaEmbeddingModel implements EmbeddingModelV4 {
34
+ readonly specificationVersion = 'v4';
35
+ readonly modelId: AlibabaEmbeddingModelId;
36
+ readonly maxEmbeddingsPerCall = 10;
37
+ readonly supportsParallelCalls = false;
38
+
39
+ private readonly config: AlibabaConfig;
40
+
41
+ static [WORKFLOW_SERIALIZE](model: AlibabaEmbeddingModel) {
42
+ return serializeModelOptions({
43
+ modelId: model.modelId,
44
+ config: model.config,
45
+ });
46
+ }
47
+
48
+ static [WORKFLOW_DESERIALIZE](options: {
49
+ modelId: AlibabaEmbeddingModelId;
50
+ config: AlibabaConfig;
51
+ }) {
52
+ return new AlibabaEmbeddingModel(options.modelId, options.config);
53
+ }
54
+
55
+ constructor(modelId: AlibabaEmbeddingModelId, config: AlibabaConfig) {
56
+ this.modelId = modelId;
57
+ this.config = config;
58
+ }
59
+
60
+ get provider(): string {
61
+ return this.config.provider;
62
+ }
63
+
64
+ async doEmbed({
65
+ values,
66
+ headers,
67
+ abortSignal,
68
+ providerOptions,
69
+ }: Parameters<EmbeddingModelV4['doEmbed']>[0]): Promise<
70
+ Awaited<ReturnType<EmbeddingModelV4['doEmbed']>>
71
+ > {
72
+ if (values.length > this.maxEmbeddingsPerCall) {
73
+ throw new TooManyEmbeddingValuesForCallError({
74
+ provider: this.provider,
75
+ modelId: this.modelId,
76
+ maxEmbeddingsPerCall: this.maxEmbeddingsPerCall,
77
+ values,
78
+ });
79
+ }
80
+
81
+ const alibabaOptions = await parseProviderOptions({
82
+ provider: 'alibaba',
83
+ providerOptions,
84
+ schema: alibabaEmbeddingModelOptions,
85
+ });
86
+
87
+ // TODO: Explore first-class sparse embedding support in AI SDK core.
88
+ if (alibabaOptions?.outputType === 'sparse') {
89
+ throw new UnsupportedFunctionalityError({
90
+ functionality: "Alibaba embedding outputType 'sparse'",
91
+ message:
92
+ "Alibaba embedding outputType 'sparse' is not supported because AI SDK embeddings require dense number arrays. Use 'dense' or 'dense&sparse' instead.",
93
+ });
94
+ }
95
+
96
+ const {
97
+ responseHeaders,
98
+ value: response,
99
+ rawValue,
100
+ } = await postJsonToApi({
101
+ url: `${this.config.baseURL}/services/embeddings/text-embedding/text-embedding`,
102
+ headers: combineHeaders(this.config.headers?.(), headers),
103
+ body: {
104
+ model: this.modelId,
105
+ input: {
106
+ texts: values,
107
+ },
108
+ parameters: {
109
+ text_type: alibabaOptions?.textType,
110
+ dimension: alibabaOptions?.dimension,
111
+ output_type: alibabaOptions?.outputType,
112
+ },
113
+ },
114
+ failedResponseHandler: alibabaEmbeddingFailedResponseHandler,
115
+ successfulResponseHandler: createJsonResponseHandler(
116
+ alibabaTextEmbeddingResponseSchema,
117
+ ),
118
+ abortSignal,
119
+ fetch: this.config.fetch,
120
+ });
121
+
122
+ const sortedEmbeddings = response.output.embeddings.sort(
123
+ (a, b) => a.text_index - b.text_index,
124
+ );
125
+ const sparseEmbeddings = sortedEmbeddings
126
+ .map(item =>
127
+ item.sparse_embedding == null
128
+ ? undefined
129
+ : {
130
+ textIndex: item.text_index,
131
+ sparseEmbedding: item.sparse_embedding,
132
+ },
133
+ )
134
+ .filter(item => item != null);
135
+
136
+ return {
137
+ warnings: [],
138
+ embeddings: sortedEmbeddings.map(item => item.embedding),
139
+ usage: response.usage
140
+ ? { tokens: response.usage.total_tokens }
141
+ : undefined,
142
+ providerMetadata:
143
+ sparseEmbeddings.length > 0
144
+ ? {
145
+ alibaba: {
146
+ sparseEmbeddings,
147
+ },
148
+ }
149
+ : undefined,
150
+ response: { headers: responseHeaders, body: rawValue },
151
+ };
152
+ }
153
+ }
154
+
155
+ const alibabaTextEmbeddingSparseEmbeddingSchema = z.object({
156
+ index: z.number(),
157
+ value: z.number(),
158
+ token: z.string().nullish(),
159
+ });
160
+
161
+ const alibabaTextEmbeddingResponseSchema = z.object({
162
+ output: z.object({
163
+ embeddings: z.array(
164
+ z.object({
165
+ embedding: z.array(z.number()),
166
+ text_index: z.number(),
167
+ sparse_embedding: z
168
+ .array(alibabaTextEmbeddingSparseEmbeddingSchema)
169
+ .nullish(),
170
+ }),
171
+ ),
172
+ }),
173
+ usage: z
174
+ .object({
175
+ total_tokens: z.number(),
176
+ })
177
+ .nullish(),
178
+ });
@@ -0,0 +1,17 @@
1
+ import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils';
2
+ import { z } from 'zod/v4';
3
+
4
+ const alibabaErrorDataSchema = z.object({
5
+ error: z.object({
6
+ message: z.string(),
7
+ code: z.string().nullish(),
8
+ type: z.string().nullish(),
9
+ }),
10
+ });
11
+
12
+ export type AlibabaErrorData = z.infer<typeof alibabaErrorDataSchema>;
13
+
14
+ export const alibabaFailedResponseHandler = createJsonErrorResponseHandler({
15
+ errorSchema: alibabaErrorDataSchema,
16
+ errorToMessage: data => data.error.message,
17
+ });