@ai-sdk/alibaba 0.0.0-6b196531-20260710185421

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,568 @@
1
+ import {
2
+ getResponseMetadata,
3
+ mapOpenAICompatibleFinishReason,
4
+ prepareTools,
5
+ } from '@ai-sdk/openai-compatible/internal';
6
+ import type {
7
+ LanguageModelV4,
8
+ LanguageModelV4CallOptions,
9
+ LanguageModelV4Content,
10
+ LanguageModelV4FinishReason,
11
+ LanguageModelV4GenerateResult,
12
+ LanguageModelV4StreamPart,
13
+ LanguageModelV4StreamResult,
14
+ SharedV4Warning,
15
+ } from '@ai-sdk/provider';
16
+ import {
17
+ combineHeaders,
18
+ createEventSourceResponseHandler,
19
+ createJsonResponseHandler,
20
+ generateId,
21
+ isCustomReasoning,
22
+ mapReasoningToProviderBudget,
23
+ parseProviderOptions,
24
+ postJsonToApi,
25
+ serializeModelOptions,
26
+ StreamingToolCallTracker,
27
+ WORKFLOW_SERIALIZE,
28
+ WORKFLOW_DESERIALIZE,
29
+ type InferSchema,
30
+ type ParseResult,
31
+ } from '@ai-sdk/provider-utils';
32
+ import { z } from 'zod/v4';
33
+ import {
34
+ alibabaLanguageModelChatOptions,
35
+ type AlibabaChatModelId,
36
+ } from './alibaba-chat-language-model-options';
37
+ import type { AlibabaConfig } from './alibaba-config';
38
+ import { alibabaFailedResponseHandler } from './alibaba-error';
39
+ import { convertAlibabaUsage } from './convert-alibaba-usage';
40
+ import { convertToAlibabaChatMessages } from './convert-to-alibaba-chat-messages';
41
+ import { CacheControlValidator } from './get-cache-control';
42
+
43
+ /**
44
+ * Alibaba language model implementation.
45
+ *
46
+ * Implements LanguageModelV4 interface for Alibaba Cloud's Qwen models.
47
+ * Supports OpenAI-compatible chat completions API with Alibaba-specific features:
48
+ * - Reasoning/thinking mode (enable_thinking, reasoning_content)
49
+ * - Thinking budget control (thinking_budget)
50
+ * - Prompt caching (cached_tokens tracking)
51
+ */
52
+ export class AlibabaChatLanguageModel implements LanguageModelV4 {
53
+ readonly specificationVersion = 'v4';
54
+ readonly modelId: AlibabaChatModelId;
55
+
56
+ private readonly config: AlibabaConfig;
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
+
72
+ constructor(modelId: AlibabaChatModelId, config: AlibabaConfig) {
73
+ this.modelId = modelId;
74
+ this.config = config;
75
+ }
76
+
77
+ get provider(): string {
78
+ return this.config.provider;
79
+ }
80
+
81
+ readonly supportedUrls: Record<string, RegExp[]> = {
82
+ 'image/*': [/^https?:\/\/.*$/],
83
+ };
84
+
85
+ /**
86
+ * Builds request arguments for Alibaba API call.
87
+ * Converts AI SDK options to Alibaba API format.
88
+ */
89
+ private async getArgs({
90
+ prompt,
91
+ maxOutputTokens,
92
+ temperature,
93
+ topP,
94
+ topK,
95
+ frequencyPenalty,
96
+ presencePenalty,
97
+ stopSequences,
98
+ responseFormat,
99
+ seed,
100
+ reasoning,
101
+ providerOptions,
102
+ tools,
103
+ toolChoice,
104
+ }: LanguageModelV4CallOptions) {
105
+ const warnings: SharedV4Warning[] = [];
106
+
107
+ const cacheControlValidator = new CacheControlValidator();
108
+
109
+ const alibabaOptions = await parseProviderOptions({
110
+ provider: 'alibaba',
111
+ providerOptions,
112
+ schema: alibabaLanguageModelChatOptions,
113
+ });
114
+
115
+ // Warn about unsupported features
116
+ if (frequencyPenalty != null) {
117
+ warnings.push({ type: 'unsupported', feature: 'frequencyPenalty' });
118
+ }
119
+
120
+ // Build base request arguments
121
+ const baseArgs = {
122
+ model: this.modelId,
123
+ max_tokens: maxOutputTokens,
124
+ temperature,
125
+ top_p: topP,
126
+ top_k: topK,
127
+ presence_penalty: presencePenalty,
128
+ stop: stopSequences,
129
+ seed,
130
+ response_format:
131
+ responseFormat?.type === 'json'
132
+ ? responseFormat.schema != null
133
+ ? {
134
+ type: 'json_schema',
135
+ json_schema: {
136
+ schema: responseFormat.schema,
137
+ name: responseFormat.name ?? 'response',
138
+ description: responseFormat.description,
139
+ },
140
+ }
141
+ : { type: 'json_object' }
142
+ : undefined,
143
+
144
+ ...resolveAlibabaThinking({
145
+ reasoning,
146
+ alibabaOptions,
147
+ warnings,
148
+ }),
149
+
150
+ // Convert messages with cache control support
151
+ messages: convertToAlibabaChatMessages({
152
+ prompt,
153
+ cacheControlValidator,
154
+ }),
155
+ };
156
+
157
+ // Prepare tools
158
+ const {
159
+ tools: alibabaTools,
160
+ toolChoice: alibabaToolChoice,
161
+ toolWarnings,
162
+ } = prepareTools({ tools, toolChoice });
163
+
164
+ warnings.push(...cacheControlValidator.getWarnings());
165
+
166
+ return {
167
+ args: {
168
+ ...baseArgs,
169
+ tools: alibabaTools,
170
+ tool_choice: alibabaToolChoice,
171
+ ...(alibabaTools != null &&
172
+ alibabaOptions?.parallelToolCalls !== undefined
173
+ ? { parallel_tool_calls: alibabaOptions.parallelToolCalls }
174
+ : {}),
175
+ },
176
+ warnings: [...warnings, ...toolWarnings],
177
+ };
178
+ }
179
+
180
+ async doGenerate(
181
+ options: LanguageModelV4CallOptions,
182
+ ): Promise<LanguageModelV4GenerateResult> {
183
+ const { args, warnings } = await this.getArgs(options);
184
+
185
+ const {
186
+ responseHeaders,
187
+ value: response,
188
+ rawValue: rawResponse,
189
+ } = await postJsonToApi({
190
+ url: `${this.config.baseURL}/chat/completions`,
191
+ headers: combineHeaders(this.config.headers?.(), options.headers),
192
+ body: args,
193
+ failedResponseHandler: alibabaFailedResponseHandler,
194
+ successfulResponseHandler: createJsonResponseHandler(
195
+ alibabaChatResponseSchema,
196
+ ),
197
+ abortSignal: options.abortSignal,
198
+ fetch: this.config.fetch,
199
+ });
200
+
201
+ const choice = response.choices[0];
202
+ const content: Array<LanguageModelV4Content> = [];
203
+
204
+ // text content:
205
+ const text = choice.message.content;
206
+ if (text != null && text.length > 0) {
207
+ content.push({ type: 'text', text });
208
+ }
209
+
210
+ // reasoning content (Alibaba uses 'reasoning_content' field):
211
+ const reasoning = choice.message.reasoning_content;
212
+ if (reasoning != null && reasoning.length > 0) {
213
+ content.push({
214
+ type: 'reasoning',
215
+ text: reasoning,
216
+ });
217
+ }
218
+
219
+ // tool calls:
220
+ if (choice.message.tool_calls != null) {
221
+ for (const toolCall of choice.message.tool_calls) {
222
+ content.push({
223
+ type: 'tool-call',
224
+ toolCallId: toolCall.id ?? generateId(),
225
+ toolName: toolCall.function.name,
226
+ input: toolCall.function.arguments!,
227
+ });
228
+ }
229
+ }
230
+
231
+ return {
232
+ content,
233
+ finishReason: {
234
+ unified: mapOpenAICompatibleFinishReason(choice.finish_reason),
235
+ raw: choice.finish_reason ?? undefined,
236
+ },
237
+ usage: convertAlibabaUsage(response.usage),
238
+ request: { body: JSON.stringify(args) },
239
+ response: {
240
+ ...getResponseMetadata(response),
241
+ headers: responseHeaders,
242
+ body: rawResponse,
243
+ },
244
+ warnings,
245
+ };
246
+ }
247
+
248
+ async doStream(
249
+ options: LanguageModelV4CallOptions,
250
+ ): Promise<LanguageModelV4StreamResult> {
251
+ const { args, warnings } = await this.getArgs(options);
252
+ const body = {
253
+ ...args,
254
+ stream: true,
255
+ stream_options: this.config.includeUsage
256
+ ? { include_usage: true }
257
+ : undefined,
258
+ };
259
+
260
+ const { responseHeaders, value: response } = await postJsonToApi({
261
+ url: `${this.config.baseURL}/chat/completions`,
262
+ headers: combineHeaders(this.config.headers?.(), options.headers),
263
+ body,
264
+ failedResponseHandler: alibabaFailedResponseHandler,
265
+ successfulResponseHandler: createEventSourceResponseHandler(
266
+ alibabaChatChunkSchema,
267
+ ),
268
+ abortSignal: options.abortSignal,
269
+ fetch: this.config.fetch,
270
+ });
271
+
272
+ // Track state across chunks
273
+ let finishReason: LanguageModelV4FinishReason = {
274
+ unified: 'other',
275
+ raw: undefined,
276
+ };
277
+ let usage: z.infer<typeof alibabaUsageSchema> | undefined = undefined;
278
+
279
+ let isFirstChunk = true;
280
+ let activeText = false;
281
+ let activeReasoningId: string | null = null;
282
+
283
+ let toolCallTracker: StreamingToolCallTracker;
284
+
285
+ return {
286
+ stream: response.pipeThrough(
287
+ new TransformStream<
288
+ ParseResult<z.infer<typeof alibabaChatChunkSchema>>,
289
+ LanguageModelV4StreamPart
290
+ >({
291
+ start(controller) {
292
+ toolCallTracker = new StreamingToolCallTracker(controller, {
293
+ generateId,
294
+ });
295
+ controller.enqueue({ type: 'stream-start', warnings });
296
+ },
297
+
298
+ transform(chunk, controller) {
299
+ // Emit raw chunk if requested (before anything else)
300
+ if (options.includeRawChunks) {
301
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
302
+ }
303
+
304
+ // Handle parse errors
305
+ if (!chunk.success) {
306
+ controller.enqueue({ type: 'error', error: chunk.error });
307
+ return;
308
+ }
309
+
310
+ const value = chunk.value;
311
+
312
+ // Emit response metadata on first chunk
313
+ if (isFirstChunk) {
314
+ isFirstChunk = false;
315
+ controller.enqueue({
316
+ type: 'response-metadata',
317
+ ...getResponseMetadata(value),
318
+ });
319
+ }
320
+
321
+ // Track usage (appears in final chunk)
322
+ if (value.usage != null) {
323
+ usage = value.usage;
324
+ }
325
+
326
+ // Skip processing if no choices (usage-only chunk)
327
+ if (value.choices.length === 0) {
328
+ return;
329
+ }
330
+
331
+ const choice = value.choices[0];
332
+ const delta = choice.delta;
333
+
334
+ // Handle reasoning content streaming (Alibaba thinking mode)
335
+ if (
336
+ delta.reasoning_content != null &&
337
+ delta.reasoning_content.length > 0
338
+ ) {
339
+ if (activeReasoningId == null) {
340
+ // End any active text before starting reasoning
341
+ if (activeText) {
342
+ controller.enqueue({ type: 'text-end', id: '0' });
343
+ activeText = false;
344
+ }
345
+
346
+ activeReasoningId = generateId();
347
+ controller.enqueue({
348
+ type: 'reasoning-start',
349
+ id: activeReasoningId,
350
+ });
351
+ }
352
+
353
+ controller.enqueue({
354
+ type: 'reasoning-delta',
355
+ id: activeReasoningId,
356
+ delta: delta.reasoning_content,
357
+ });
358
+ }
359
+
360
+ // Handle text content streaming
361
+ if (delta.content != null && delta.content.length > 0) {
362
+ // End any active reasoning before starting text
363
+ if (activeReasoningId != null) {
364
+ controller.enqueue({
365
+ type: 'reasoning-end',
366
+ id: activeReasoningId,
367
+ });
368
+ activeReasoningId = null;
369
+ }
370
+
371
+ if (!activeText) {
372
+ controller.enqueue({ type: 'text-start', id: '0' });
373
+ activeText = true;
374
+ }
375
+
376
+ controller.enqueue({
377
+ type: 'text-delta',
378
+ id: '0',
379
+ delta: delta.content,
380
+ });
381
+ }
382
+
383
+ // Handle tool call streaming
384
+ if (delta.tool_calls != null) {
385
+ // End any active reasoning or text before tool calls
386
+ if (activeReasoningId != null) {
387
+ controller.enqueue({
388
+ type: 'reasoning-end',
389
+ id: activeReasoningId,
390
+ });
391
+ activeReasoningId = null;
392
+ }
393
+ if (activeText) {
394
+ controller.enqueue({ type: 'text-end', id: '0' });
395
+ activeText = false;
396
+ }
397
+
398
+ for (const toolCallDelta of delta.tool_calls) {
399
+ toolCallTracker.processDelta(toolCallDelta);
400
+ }
401
+ }
402
+
403
+ // Track finish reason
404
+ if (choice.finish_reason != null) {
405
+ finishReason = {
406
+ unified: mapOpenAICompatibleFinishReason(choice.finish_reason),
407
+ raw: choice.finish_reason,
408
+ };
409
+ }
410
+ },
411
+
412
+ flush(controller) {
413
+ if (activeReasoningId != null) {
414
+ controller.enqueue({
415
+ type: 'reasoning-end',
416
+ id: activeReasoningId,
417
+ });
418
+ }
419
+
420
+ if (activeText) {
421
+ controller.enqueue({ type: 'text-end', id: '0' });
422
+ }
423
+
424
+ toolCallTracker.flush();
425
+
426
+ controller.enqueue({
427
+ type: 'finish',
428
+ finishReason,
429
+ usage: convertAlibabaUsage(usage),
430
+ });
431
+ },
432
+ }),
433
+ ),
434
+ request: { body: JSON.stringify(body) },
435
+ response: { headers: responseHeaders },
436
+ };
437
+ }
438
+ }
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
+
486
+ /**
487
+ * Reference for schemas below:
488
+ * https://www.alibabacloud.com/help/en/model-studio/qwen-api-via-openai-chat-completions
489
+ */
490
+ const alibabaUsageSchema = z.object({
491
+ prompt_tokens: z.number(),
492
+ completion_tokens: z.number(),
493
+ total_tokens: z.number(),
494
+ prompt_tokens_details: z
495
+ .object({
496
+ cached_tokens: z.number().nullish(),
497
+ cache_creation_input_tokens: z.number().nullish(),
498
+ })
499
+ .nullish(),
500
+ completion_tokens_details: z
501
+ .object({
502
+ reasoning_tokens: z.number().nullish(),
503
+ })
504
+ .nullish(),
505
+ });
506
+
507
+ const alibabaChatResponseSchema = z.object({
508
+ id: z.string().nullish(),
509
+ created: z.number().nullish(),
510
+ model: z.string().nullish(),
511
+ choices: z.array(
512
+ z.object({
513
+ message: z.object({
514
+ role: z.literal('assistant').nullish(),
515
+ content: z.string().nullish(),
516
+ reasoning_content: z.string().nullish(), // Alibaba thinking mode
517
+ tool_calls: z
518
+ .array(
519
+ z.object({
520
+ id: z.string(),
521
+ type: z.literal('function'),
522
+ function: z.object({
523
+ name: z.string(),
524
+ arguments: z.string(),
525
+ }),
526
+ }),
527
+ )
528
+ .nullish(),
529
+ }),
530
+ finish_reason: z.string().nullish(),
531
+ index: z.number(),
532
+ }),
533
+ ),
534
+ usage: alibabaUsageSchema.nullish(),
535
+ });
536
+
537
+ const alibabaChatChunkSchema = z.object({
538
+ id: z.string().nullish(),
539
+ created: z.number().nullish(),
540
+ model: z.string().nullish(),
541
+ choices: z.array(
542
+ z.object({
543
+ delta: z.object({
544
+ role: z.enum(['assistant']).nullish(),
545
+ content: z.string().nullish(),
546
+ reasoning_content: z.string().nullish(), // Alibaba thinking mode delta
547
+ tool_calls: z
548
+ .array(
549
+ z.object({
550
+ index: z.number().nullish(), // Index for accumulating tool calls
551
+ id: z.string().nullish(),
552
+ type: z.literal('function').nullish(),
553
+ function: z
554
+ .object({
555
+ name: z.string().nullish(),
556
+ arguments: z.string().nullish(),
557
+ })
558
+ .nullish(),
559
+ }),
560
+ )
561
+ .nullish(),
562
+ }),
563
+ finish_reason: z.string().nullish(),
564
+ index: z.number(),
565
+ }),
566
+ ),
567
+ usage: alibabaUsageSchema.nullish(), // Usage only appears in final chunk
568
+ });
@@ -0,0 +1,67 @@
1
+ export type AlibabaCacheControl = { type: string };
2
+
3
+ export type AlibabaChatPrompt = Array<AlibabaChatMessage>;
4
+
5
+ export type AlibabaChatMessage =
6
+ | AlibabaChatSystemMessage
7
+ | AlibabaChatUserMessage
8
+ | AlibabaChatAssistantMessage
9
+ | AlibabaChatToolMessage;
10
+
11
+ export interface AlibabaChatSystemMessage {
12
+ role: 'system';
13
+ content: string | Array<AlibabaChatSystemMessageContent>;
14
+ }
15
+
16
+ export type AlibabaChatSystemMessageContent = {
17
+ type: 'text';
18
+ text: string;
19
+ cache_control?: AlibabaCacheControl;
20
+ };
21
+
22
+ export interface AlibabaChatUserMessage {
23
+ role: 'user';
24
+ content: string | Array<AlibabaChatUserMessageContent>;
25
+ }
26
+
27
+ export type AlibabaChatUserMessageContent =
28
+ | { type: 'text'; text: string; cache_control?: AlibabaCacheControl }
29
+ | {
30
+ type: 'image_url';
31
+ image_url: { url: string };
32
+ cache_control?: AlibabaCacheControl;
33
+ };
34
+
35
+ export interface AlibabaChatAssistantMessage {
36
+ role: 'assistant';
37
+ content: string | null | Array<AlibabaChatAssistantMessageContent>;
38
+ tool_calls?: Array<{
39
+ id: string;
40
+ type: 'function';
41
+ function: { name: string; arguments: string };
42
+ }>;
43
+ }
44
+
45
+ export type AlibabaChatAssistantMessageContent = {
46
+ type: 'text';
47
+ text: string;
48
+ cache_control?: AlibabaCacheControl;
49
+ };
50
+
51
+ export interface AlibabaChatToolMessage {
52
+ role: 'tool';
53
+ tool_call_id: string;
54
+ content: string | Array<AlibabaChatToolMessageContent>;
55
+ }
56
+
57
+ export type AlibabaChatToolMessageContent = {
58
+ type: 'text';
59
+ text: string;
60
+ cache_control?: AlibabaCacheControl;
61
+ };
62
+
63
+ export type AlibabaChatToolChoice =
64
+ | { type: 'function'; function: { name: string } }
65
+ | 'auto'
66
+ | 'none'
67
+ | 'required';
@@ -0,0 +1,9 @@
1
+ import type { FetchFunction } from '@ai-sdk/provider-utils';
2
+
3
+ export interface AlibabaConfig {
4
+ provider: string;
5
+ baseURL: string;
6
+ headers?: () => Record<string, string | undefined>;
7
+ fetch?: FetchFunction;
8
+ includeUsage?: boolean;
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
+ >;