@ai-sdk/alibaba 1.0.0

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,63 @@
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
+ | { type: 'image_url'; image_url: { url: string } };
30
+
31
+ export interface AlibabaChatAssistantMessage {
32
+ role: 'assistant';
33
+ content: string | null | Array<AlibabaChatAssistantMessageContent>;
34
+ tool_calls?: Array<{
35
+ id: string;
36
+ type: 'function';
37
+ function: { name: string; arguments: string };
38
+ }>;
39
+ }
40
+
41
+ export type AlibabaChatAssistantMessageContent = {
42
+ type: 'text';
43
+ text: string;
44
+ cache_control?: AlibabaCacheControl;
45
+ };
46
+
47
+ export interface AlibabaChatToolMessage {
48
+ role: 'tool';
49
+ tool_call_id: string;
50
+ content: string | Array<AlibabaChatToolMessageContent>;
51
+ }
52
+
53
+ export type AlibabaChatToolMessageContent = {
54
+ type: 'text';
55
+ text: string;
56
+ cache_control?: AlibabaCacheControl;
57
+ };
58
+
59
+ export type AlibabaChatToolChoice =
60
+ | { type: 'function'; function: { name: string } }
61
+ | 'auto'
62
+ | 'none'
63
+ | 'required';
@@ -0,0 +1,9 @@
1
+ import { 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,137 @@
1
+ import {
2
+ LanguageModelV3,
3
+ NoSuchModelError,
4
+ ProviderV3,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ createJsonErrorResponseHandler,
8
+ FetchFunction,
9
+ loadApiKey,
10
+ withoutTrailingSlash,
11
+ withUserAgentSuffix,
12
+ } from '@ai-sdk/provider-utils';
13
+ import { z } from 'zod/v4';
14
+ import { AlibabaLanguageModel } from './alibaba-chat-language-model';
15
+ import { AlibabaChatModelId } from './alibaba-chat-options';
16
+ import { VERSION } from './version';
17
+
18
+ export type AlibabaErrorData = z.infer<typeof alibabaErrorDataSchema>;
19
+
20
+ const alibabaErrorDataSchema = z.object({
21
+ error: z.object({
22
+ message: z.string(),
23
+ code: z.string().nullish(),
24
+ type: z.string().nullish(),
25
+ }),
26
+ });
27
+
28
+ export const alibabaFailedResponseHandler = createJsonErrorResponseHandler({
29
+ errorSchema: alibabaErrorDataSchema,
30
+ errorToMessage: data => data.error.message,
31
+ });
32
+
33
+ export interface AlibabaProvider extends ProviderV3 {
34
+ (modelId: AlibabaChatModelId): LanguageModelV3;
35
+
36
+ /**
37
+ * Creates a model for text generation.
38
+ */
39
+ languageModel(modelId: AlibabaChatModelId): LanguageModelV3;
40
+
41
+ /**
42
+ * Creates a chat model for text generation.
43
+ */
44
+ chatModel(modelId: AlibabaChatModelId): LanguageModelV3;
45
+ }
46
+
47
+ export interface AlibabaProviderSettings {
48
+ /**
49
+ * Use a different URL prefix for API calls, e.g. to use proxy servers or regional endpoints.
50
+ * The default prefix is `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`.
51
+ */
52
+ baseURL?: string;
53
+
54
+ /**
55
+ * API key that is being sent using the `Authorization` header.
56
+ * It defaults to the `ALIBABA_API_KEY` environment variable.
57
+ */
58
+ apiKey?: string;
59
+
60
+ /**
61
+ * Custom headers to include in the requests.
62
+ */
63
+ headers?: Record<string, string>;
64
+
65
+ /**
66
+ * Custom fetch implementation. You can use it as a middleware to intercept requests,
67
+ * or to provide a custom fetch implementation for e.g. testing.
68
+ */
69
+ fetch?: FetchFunction;
70
+
71
+ /**
72
+ * Include usage information in streaming responses.
73
+ * When enabled, token usage will be included in the final chunk.
74
+ *
75
+ * @default true
76
+ */
77
+ includeUsage?: boolean;
78
+ }
79
+
80
+ /**
81
+ * Create an Alibaba Cloud (Qwen) provider instance.
82
+ */
83
+ export function createAlibaba(
84
+ options: AlibabaProviderSettings = {},
85
+ ): AlibabaProvider {
86
+ const baseURL =
87
+ withoutTrailingSlash(options.baseURL) ??
88
+ 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1';
89
+
90
+ const getHeaders = () =>
91
+ withUserAgentSuffix(
92
+ {
93
+ Authorization: `Bearer ${loadApiKey({
94
+ apiKey: options.apiKey,
95
+ environmentVariableName: 'ALIBABA_API_KEY',
96
+ description: 'Alibaba Cloud (DashScope)',
97
+ })}`,
98
+ ...options.headers,
99
+ },
100
+ `ai-sdk/alibaba/${VERSION}`,
101
+ );
102
+
103
+ const createLanguageModel = (modelId: AlibabaChatModelId) =>
104
+ new AlibabaLanguageModel(modelId, {
105
+ provider: 'alibaba.chat',
106
+ baseURL,
107
+ headers: getHeaders,
108
+ fetch: options.fetch,
109
+ includeUsage: options.includeUsage ?? true,
110
+ });
111
+
112
+ const provider = function (modelId: AlibabaChatModelId) {
113
+ if (new.target) {
114
+ throw new Error(
115
+ 'The Alibaba model function cannot be called with the new keyword.',
116
+ );
117
+ }
118
+
119
+ return createLanguageModel(modelId);
120
+ };
121
+
122
+ provider.specificationVersion = 'v3' as const;
123
+ provider.languageModel = createLanguageModel;
124
+ provider.chatModel = createLanguageModel;
125
+
126
+ provider.imageModel = (modelId: string) => {
127
+ throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
128
+ };
129
+
130
+ provider.embeddingModel = (modelId: string) => {
131
+ throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });
132
+ };
133
+
134
+ return provider;
135
+ }
136
+
137
+ export const alibaba = createAlibaba();
@@ -0,0 +1,37 @@
1
+ import { LanguageModelV3Usage } from '@ai-sdk/provider';
2
+ import { convertOpenAICompatibleChatUsage } from '@ai-sdk/openai-compatible/internal';
3
+
4
+ export type AlibabaUsage = {
5
+ prompt_tokens?: number | null;
6
+ completion_tokens?: number | null;
7
+ total_tokens?: number | null;
8
+ prompt_tokens_details?: {
9
+ cached_tokens?: number | null;
10
+ cache_creation_input_tokens?: number | null;
11
+ } | null;
12
+ completion_tokens_details?: {
13
+ reasoning_tokens?: number | null;
14
+ } | null;
15
+ };
16
+
17
+ export function convertAlibabaUsage(
18
+ usage: AlibabaUsage | undefined | null,
19
+ ): LanguageModelV3Usage {
20
+ const baseUsage = convertOpenAICompatibleChatUsage(usage);
21
+
22
+ const cacheWriteTokens =
23
+ usage?.prompt_tokens_details?.cache_creation_input_tokens ?? 0;
24
+ const noCacheTokens =
25
+ (baseUsage.inputTokens.total ?? 0) -
26
+ (baseUsage.inputTokens.cacheRead ?? 0) -
27
+ cacheWriteTokens;
28
+
29
+ return {
30
+ ...baseUsage,
31
+ inputTokens: {
32
+ ...baseUsage.inputTokens,
33
+ cacheWrite: cacheWriteTokens,
34
+ noCache: noCacheTokens,
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,183 @@
1
+ import {
2
+ LanguageModelV3DataContent,
3
+ LanguageModelV3Prompt,
4
+ UnsupportedFunctionalityError,
5
+ } from '@ai-sdk/provider';
6
+ import { convertToBase64 } from '@ai-sdk/provider-utils';
7
+ import { AlibabaChatPrompt } from './alibaba-chat-prompt';
8
+ import { CacheControlValidator } from './get-cache-control';
9
+
10
+ function formatImageUrl({
11
+ data,
12
+ mediaType,
13
+ }: {
14
+ data: LanguageModelV3DataContent;
15
+ mediaType: string;
16
+ }): string {
17
+ return data instanceof URL
18
+ ? data.toString()
19
+ : `data:${mediaType};base64,${convertToBase64(data as Uint8Array)}`;
20
+ }
21
+
22
+ export function convertToAlibabaChatMessages({
23
+ prompt,
24
+ cacheControlValidator,
25
+ }: {
26
+ prompt: LanguageModelV3Prompt;
27
+ cacheControlValidator?: CacheControlValidator;
28
+ }): AlibabaChatPrompt {
29
+ const messages: AlibabaChatPrompt = [];
30
+
31
+ for (const { role, content, ...message } of prompt) {
32
+ switch (role) {
33
+ case 'system': {
34
+ const cacheControl = cacheControlValidator?.getCacheControl(
35
+ message.providerOptions,
36
+ );
37
+
38
+ // If cache_control is present, convert to array format
39
+ if (cacheControl) {
40
+ messages.push({
41
+ role: 'system',
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: content,
46
+ cache_control: cacheControl,
47
+ },
48
+ ],
49
+ });
50
+ } else {
51
+ messages.push({ role: 'system', content });
52
+ }
53
+ break;
54
+ }
55
+
56
+ case 'user': {
57
+ // Single text part -> use string content
58
+ if (content.length === 1 && content[0].type === 'text') {
59
+ messages.push({
60
+ role: 'user',
61
+ content: content[0].text,
62
+ });
63
+ break;
64
+ }
65
+
66
+ // Multi-part content
67
+ messages.push({
68
+ role: 'user',
69
+ content: content.map(part => {
70
+ switch (part.type) {
71
+ case 'text': {
72
+ return { type: 'text', text: part.text };
73
+ }
74
+
75
+ case 'file': {
76
+ if (part.mediaType.startsWith('image/')) {
77
+ const mediaType =
78
+ part.mediaType === 'image/*'
79
+ ? 'image/jpeg'
80
+ : part.mediaType;
81
+
82
+ return {
83
+ type: 'image_url',
84
+ image_url: {
85
+ url: formatImageUrl({ data: part.data, mediaType }),
86
+ },
87
+ };
88
+ } else {
89
+ throw new UnsupportedFunctionalityError({
90
+ functionality: 'Only image file parts are supported',
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }),
96
+ });
97
+ break;
98
+ }
99
+
100
+ case 'assistant': {
101
+ let text = '';
102
+ const toolCalls: Array<{
103
+ id: string;
104
+ type: 'function';
105
+ function: { name: string; arguments: string };
106
+ }> = [];
107
+
108
+ for (const part of content) {
109
+ switch (part.type) {
110
+ case 'text': {
111
+ text += part.text;
112
+ break;
113
+ }
114
+ case 'tool-call': {
115
+ toolCalls.push({
116
+ id: part.toolCallId,
117
+ type: 'function',
118
+ function: {
119
+ name: part.toolName,
120
+ arguments: JSON.stringify(part.input),
121
+ },
122
+ });
123
+ break;
124
+ }
125
+ case 'reasoning': {
126
+ // Reasoning content is handled separately in the response
127
+ // but may appear in assistant messages during multi-turn conversations
128
+ text += part.text;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
+ messages.push({
135
+ role: 'assistant',
136
+ content: text || null,
137
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
138
+ });
139
+
140
+ break;
141
+ }
142
+
143
+ case 'tool': {
144
+ for (const toolResponse of content) {
145
+ if (toolResponse.type === 'tool-approval-response') {
146
+ continue;
147
+ }
148
+ const output = toolResponse.output;
149
+
150
+ let contentValue: string;
151
+ switch (output.type) {
152
+ case 'text':
153
+ case 'error-text':
154
+ contentValue = output.value;
155
+ break;
156
+ case 'execution-denied':
157
+ contentValue = output.reason ?? 'Tool execution denied.';
158
+ break;
159
+ case 'content':
160
+ case 'json':
161
+ case 'error-json':
162
+ contentValue = JSON.stringify(output.value);
163
+ break;
164
+ }
165
+
166
+ messages.push({
167
+ role: 'tool',
168
+ tool_call_id: toolResponse.toolCallId,
169
+ content: contentValue,
170
+ });
171
+ }
172
+ break;
173
+ }
174
+
175
+ default: {
176
+ const _exhaustiveCheck: never = role;
177
+ throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
178
+ }
179
+ }
180
+ }
181
+
182
+ return messages;
183
+ }
@@ -0,0 +1,49 @@
1
+ import { SharedV3Warning, SharedV3ProviderMetadata } from '@ai-sdk/provider';
2
+ import { AlibabaCacheControl } from './alibaba-chat-prompt';
3
+
4
+ // Alibaba allows a maximum of 4 cache breakpoints per request
5
+ const MAX_CACHE_BREAKPOINTS = 4;
6
+
7
+ function getCacheControl(
8
+ providerMetadata: SharedV3ProviderMetadata | undefined,
9
+ ): AlibabaCacheControl | undefined {
10
+ const alibaba = providerMetadata?.alibaba;
11
+
12
+ const cacheControlValue = alibaba?.cacheControl ?? alibaba?.cache_control;
13
+
14
+ // Pass through value assuming it is of the correct type.
15
+ // The Alibaba API will validate the value.
16
+ return cacheControlValue as AlibabaCacheControl | undefined;
17
+ }
18
+
19
+ export class CacheControlValidator {
20
+ private breakpointCount = 0;
21
+ private warnings: SharedV3Warning[] = [];
22
+
23
+ getCacheControl(
24
+ providerMetadata: SharedV3ProviderMetadata | undefined,
25
+ ): AlibabaCacheControl | undefined {
26
+ const cacheControlValue = getCacheControl(providerMetadata);
27
+
28
+ if (!cacheControlValue) {
29
+ return undefined;
30
+ }
31
+
32
+ // Validate cache breakpoint limit
33
+ this.breakpointCount++;
34
+ if (this.breakpointCount > MAX_CACHE_BREAKPOINTS) {
35
+ this.warnings.push({
36
+ type: 'unsupported',
37
+ feature: 'cacheControl breakpoint limit',
38
+ details: `Maximum ${MAX_CACHE_BREAKPOINTS} cache breakpoints exceeded (found ${this.breakpointCount}). This breakpoint will be ignored.`,
39
+ });
40
+ return undefined;
41
+ }
42
+
43
+ return cacheControlValue;
44
+ }
45
+
46
+ getWarnings(): SharedV3Warning[] {
47
+ return this.warnings;
48
+ }
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { createAlibaba, alibaba } from './alibaba-provider';
2
+ export type {
3
+ AlibabaProvider,
4
+ AlibabaProviderSettings,
5
+ } from './alibaba-provider';
6
+ export type {
7
+ AlibabaChatModelId,
8
+ AlibabaProviderOptions,
9
+ } from './alibaba-chat-options';
10
+ export type { AlibabaCacheControl } from './alibaba-chat-prompt';
11
+ export type { AlibabaUsage } from './convert-alibaba-usage';
12
+ export { VERSION } from './version';
package/src/version.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare const __PACKAGE_VERSION__: string;
2
+
3
+ export const VERSION = __PACKAGE_VERSION__;