ai 7.0.107 → 7.0.109

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,8 +1,20 @@
1
- import type {
2
- LanguageModelV2,
3
- LanguageModelV3,
4
- LanguageModelV4,
1
+ import {
2
+ type LanguageModelV2,
3
+ type LanguageModelV3,
4
+ type LanguageModelV3Content,
5
+ type LanguageModelV3DataContent,
6
+ type LanguageModelV3Prompt,
7
+ type LanguageModelV3StreamPart,
8
+ type LanguageModelV3ToolResultOutput,
9
+ type LanguageModelV4,
10
+ type LanguageModelV4CallOptions,
11
+ type LanguageModelV4Content,
12
+ type LanguageModelV4Prompt,
13
+ type LanguageModelV4StreamPart,
14
+ type LanguageModelV4ToolResultOutput,
15
+ type SharedV4FileData,
5
16
  } from '@ai-sdk/provider';
17
+ import { convertUint8ArrayToBase64 } from '@ai-sdk/provider-utils';
6
18
  import { asLanguageModelV3 } from './as-language-model-v3';
7
19
 
8
20
  export function asLanguageModelV4(
@@ -18,8 +30,156 @@ export function asLanguageModelV4(
18
30
 
19
31
  return new Proxy(v3Model, {
20
32
  get(target, prop: keyof LanguageModelV3) {
21
- if (prop === 'specificationVersion') return 'v4';
22
- return target[prop];
33
+ switch (prop) {
34
+ case 'specificationVersion':
35
+ return 'v4';
36
+ case 'doGenerate':
37
+ return async (options: LanguageModelV4CallOptions) => {
38
+ const result = await target.doGenerate({
39
+ ...options,
40
+ prompt: convertV4PromptToV3(options.prompt),
41
+ });
42
+
43
+ return {
44
+ ...result,
45
+ content: result.content.map(convertV3ContentToV4),
46
+ };
47
+ };
48
+ case 'doStream':
49
+ return async (options: LanguageModelV4CallOptions) => {
50
+ const result = await target.doStream({
51
+ ...options,
52
+ prompt: convertV4PromptToV3(options.prompt),
53
+ });
54
+
55
+ return {
56
+ ...result,
57
+ stream: convertV3StreamToV4(result.stream),
58
+ };
59
+ };
60
+ default:
61
+ return target[prop];
62
+ }
23
63
  },
24
64
  }) as unknown as LanguageModelV4;
25
65
  }
66
+
67
+ function convertV4PromptToV3(
68
+ prompt: LanguageModelV4Prompt,
69
+ ): LanguageModelV3Prompt {
70
+ return prompt.map(message => {
71
+ if (message.role === 'system') {
72
+ return message;
73
+ }
74
+
75
+ return {
76
+ ...message,
77
+ content: message.content.map(part => {
78
+ switch (part.type) {
79
+ case 'file':
80
+ return {
81
+ ...part,
82
+ data: convertV4FileDataToV3(part.data),
83
+ };
84
+ case 'tool-result':
85
+ return {
86
+ ...part,
87
+ output: convertV4ToolResultOutputToV3(part.output),
88
+ };
89
+ default:
90
+ return part;
91
+ }
92
+ }),
93
+ };
94
+ }) as LanguageModelV3Prompt;
95
+ }
96
+
97
+ function convertV4FileDataToV3(
98
+ data: SharedV4FileData,
99
+ ): LanguageModelV3DataContent {
100
+ switch (data.type) {
101
+ case 'data':
102
+ return data.data;
103
+ case 'url':
104
+ return data.url;
105
+ case 'reference':
106
+ case 'text':
107
+ // pass through unsupported types as is
108
+ return data as unknown as LanguageModelV3DataContent;
109
+ }
110
+ }
111
+
112
+ function convertV4ToolResultOutputToV3(
113
+ output: LanguageModelV4ToolResultOutput,
114
+ ): LanguageModelV3ToolResultOutput {
115
+ if (output.type !== 'content') {
116
+ return output;
117
+ }
118
+
119
+ return {
120
+ ...output,
121
+ value: output.value.map(part => {
122
+ if (part.type !== 'file') {
123
+ return part;
124
+ }
125
+
126
+ switch (part.data.type) {
127
+ case 'data':
128
+ return {
129
+ type: 'file-data' as const,
130
+ data:
131
+ typeof part.data.data === 'string'
132
+ ? part.data.data
133
+ : convertUint8ArrayToBase64(part.data.data),
134
+ mediaType: part.mediaType,
135
+ filename: part.filename,
136
+ providerOptions: part.providerOptions,
137
+ };
138
+ case 'url':
139
+ return {
140
+ type: 'file-url' as const,
141
+ url: part.data.url.toString(),
142
+ providerOptions: part.providerOptions,
143
+ };
144
+ case 'reference':
145
+ return {
146
+ type: 'file-id' as const,
147
+ fileId: part.data.reference,
148
+ providerOptions: part.providerOptions,
149
+ };
150
+ case 'text':
151
+ return part;
152
+ }
153
+ }),
154
+ } as LanguageModelV3ToolResultOutput;
155
+ }
156
+
157
+ function convertV3ContentToV4(
158
+ content: LanguageModelV3Content,
159
+ ): LanguageModelV4Content {
160
+ return content.type === 'file'
161
+ ? {
162
+ ...content,
163
+ data: { type: 'data', data: content.data },
164
+ }
165
+ : content;
166
+ }
167
+
168
+ function convertV3StreamToV4(
169
+ stream: ReadableStream<LanguageModelV3StreamPart>,
170
+ ): ReadableStream<LanguageModelV4StreamPart> {
171
+ return stream.pipeThrough(
172
+ new TransformStream<LanguageModelV3StreamPart, LanguageModelV4StreamPart>({
173
+ transform(chunk, controller) {
174
+ controller.enqueue(
175
+ chunk.type === 'file'
176
+ ? {
177
+ ...chunk,
178
+ data: { type: 'data', data: chunk.data },
179
+ }
180
+ : chunk,
181
+ );
182
+ },
183
+ }),
184
+ );
185
+ }
@@ -30,6 +30,7 @@ export async function callCompletionApi({
30
30
  setLoading,
31
31
  setError,
32
32
  setAbortController,
33
+ getAbortController,
33
34
  onFinish,
34
35
  onError,
35
36
  fetch = getOriginalFetch(),
@@ -44,15 +45,19 @@ export async function callCompletionApi({
44
45
  setLoading: (loading: boolean) => void;
45
46
  setError: (error: Error | undefined) => void;
46
47
  setAbortController: (abortController: AbortController | null) => void;
48
+ getAbortController?: () => AbortController | null | undefined;
47
49
  onFinish: ((prompt: string, completion: string) => void) | undefined;
48
50
  onError: ((error: Error) => void) | undefined;
49
51
  fetch: ReturnType<typeof getOriginalFetch> | undefined;
50
52
  }) {
53
+ const abortController = new AbortController();
54
+ const isCurrentRequest = () =>
55
+ getAbortController == null || getAbortController() === abortController;
56
+
51
57
  try {
52
58
  setLoading(true);
53
59
  setError(undefined);
54
60
 
55
- const abortController = new AbortController();
56
61
  setAbortController(abortController);
57
62
 
58
63
  // Empty the completion immediately.
@@ -100,7 +105,9 @@ export async function callCompletionApi({
100
105
  stream: response.body,
101
106
  onTextPart: chunk => {
102
107
  result += chunk;
103
- setCompletion(result);
108
+ if (isCurrentRequest()) {
109
+ setCompletion(result);
110
+ }
104
111
  },
105
112
  });
106
113
  break;
@@ -120,7 +127,9 @@ export async function callCompletionApi({
120
127
  const streamPart = part.value;
121
128
  if (streamPart.type === 'text-delta') {
122
129
  result += streamPart.delta;
123
- setCompletion(result);
130
+ if (isCurrentRequest()) {
131
+ setCompletion(result);
132
+ }
124
133
  } else if (streamPart.type === 'error') {
125
134
  throw new UIMessageStreamError({
126
135
  chunkType: 'error',
@@ -151,12 +160,10 @@ export async function callCompletionApi({
151
160
  onFinish(prompt, result);
152
161
  }
153
162
 
154
- setAbortController(null);
155
163
  return result;
156
164
  } catch (err) {
157
165
  // Ignore abort errors as they are expected.
158
166
  if ((err as any).name === 'AbortError') {
159
- setAbortController(null);
160
167
  return null;
161
168
  }
162
169
 
@@ -166,8 +173,14 @@ export async function callCompletionApi({
166
173
  }
167
174
  }
168
175
 
169
- setError(err as Error);
176
+ if (isCurrentRequest()) {
177
+ setError(err as Error);
178
+ }
170
179
  } finally {
171
- setLoading(false);
180
+ // A newer request may have started while this one was settling.
181
+ if (isCurrentRequest()) {
182
+ setAbortController(null);
183
+ setLoading(false);
184
+ }
172
185
  }
173
186
  }