ai 7.0.52 → 7.0.55

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,336 @@
1
+ import {
2
+ UnsupportedFunctionalityError,
3
+ type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
4
+ type Experimental_BatchV4ItemResult as BatchV4ItemResult,
5
+ type LanguageModelV4,
6
+ type LanguageModelV4GenerateResult,
7
+ } from '@ai-sdk/provider';
8
+ import { withUserAgentSuffix } from '@ai-sdk/provider-utils';
9
+ import { InvalidArgumentError } from '../error/invalid-argument-error';
10
+ import { logWarnings } from '../logger/log-warnings';
11
+ import { resolveLanguageModel } from '../model/resolve-model';
12
+ import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt';
13
+ import { prepareLanguageModelCallOptions } from '../prompt/prepare-language-model-call-options';
14
+ import { getTotalTimeoutMs } from '../prompt/request-options';
15
+ import { standardizePrompt } from '../prompt/standardize-prompt';
16
+ import { wrapGatewayError } from '../prompt/wrap-gateway-error';
17
+ import { asLanguageModelUsage } from '../types/usage';
18
+ import { asAsyncIterableStream } from '../util/async-iterable-stream';
19
+ import { mergeAbortSignals } from '../util/merge-abort-signals';
20
+ import { prepareRetries } from '../util/prepare-retries';
21
+ import { VERSION } from '../version';
22
+ import type {
23
+ BatchOperationOptions,
24
+ BatchReference,
25
+ BatchStatus,
26
+ StartTextBatchOptions,
27
+ StartTextBatchResult,
28
+ TextBatchGenerationResult,
29
+ TextBatchItemResult,
30
+ TextBatchRequest,
31
+ } from './batch-types';
32
+
33
+ /**
34
+ * Starts a durable text-generation batch.
35
+ */
36
+ export async function startTextBatch({
37
+ model: modelArg,
38
+ requests,
39
+ providerOptions,
40
+ abortSignal,
41
+ headers,
42
+ timeout,
43
+ }: StartTextBatchOptions): Promise<StartTextBatchResult> {
44
+ validateRequests(requests);
45
+
46
+ const model = resolveBatchLanguageModel(modelArg);
47
+ const operationAbortSignal = mergeAbortSignals(
48
+ abortSignal,
49
+ getTotalTimeoutMs(timeout),
50
+ );
51
+ const supportedUrls = await model.supportedUrls;
52
+ operationAbortSignal?.throwIfAborted();
53
+ const normalizedRequests = [];
54
+
55
+ for (const request of requests) {
56
+ const standardizedPrompt = await standardizePrompt(request);
57
+
58
+ normalizedRequests.push({
59
+ id: request.id,
60
+ options: {
61
+ ...prepareLanguageModelCallOptions(request),
62
+ prompt: await convertToLanguageModelPrompt({
63
+ prompt: standardizedPrompt,
64
+ supportedUrls,
65
+ download: undefined,
66
+ provider: model.provider.split('.')[0],
67
+ }),
68
+ providerOptions: request.providerOptions,
69
+ },
70
+ });
71
+ operationAbortSignal?.throwIfAborted();
72
+ }
73
+
74
+ const headersWithUserAgent = withUserAgentSuffix(
75
+ headers ?? {},
76
+ `ai/${VERSION}`,
77
+ );
78
+ try {
79
+ const result = await model.experimental_doStartBatch({
80
+ requests: normalizedRequests,
81
+ providerOptions,
82
+ abortSignal: operationAbortSignal,
83
+ headers: headersWithUserAgent,
84
+ });
85
+ const { batchId, warnings, ...status } = result;
86
+
87
+ logWarnings({
88
+ warnings: warnings.map(({ warning }) => warning),
89
+ provider: model.provider,
90
+ model: model.modelId,
91
+ });
92
+
93
+ return {
94
+ version: 1,
95
+ type: 'text',
96
+ id: batchId,
97
+ provider: model.provider,
98
+ modelId: model.modelId,
99
+ ...status,
100
+ warnings,
101
+ };
102
+ } catch (error) {
103
+ throw wrapGatewayError(error);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Retrieves the latest normalized status for a durable batch.
109
+ */
110
+ export async function getBatchStatus({
111
+ model: modelArg,
112
+ batch,
113
+ providerOptions,
114
+ maxRetries,
115
+ abortSignal,
116
+ headers,
117
+ timeout,
118
+ }: BatchOperationOptions): Promise<BatchStatus> {
119
+ const model = resolveBatchLanguageModel(modelArg);
120
+ validateBatchReference({ model, batch });
121
+
122
+ const operationAbortSignal = mergeAbortSignals(
123
+ abortSignal,
124
+ getTotalTimeoutMs(timeout),
125
+ );
126
+ const { retry } = prepareRetries({
127
+ maxRetries,
128
+ abortSignal: operationAbortSignal,
129
+ });
130
+
131
+ try {
132
+ const status = await retry(() =>
133
+ model.experimental_doGetBatchStatus({
134
+ batchId: batch.id,
135
+ providerOptions,
136
+ abortSignal: operationAbortSignal,
137
+ headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
138
+ }),
139
+ );
140
+
141
+ return status;
142
+ } catch (error) {
143
+ throw wrapGatewayError(error);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Streams complete terminal results for the requests in a durable batch.
149
+ */
150
+ export function getBatchResults({
151
+ model: modelArg,
152
+ batch,
153
+ providerOptions,
154
+ maxRetries,
155
+ abortSignal,
156
+ headers,
157
+ timeout,
158
+ }: BatchOperationOptions) {
159
+ const model = resolveBatchLanguageModel(modelArg);
160
+ validateBatchReference({ model, batch });
161
+
162
+ const streamAbortController = new AbortController();
163
+ const operationAbortSignal = mergeAbortSignals(
164
+ abortSignal,
165
+ getTotalTimeoutMs(timeout),
166
+ streamAbortController.signal,
167
+ );
168
+ const { retry } = prepareRetries({
169
+ maxRetries,
170
+ abortSignal: operationAbortSignal,
171
+ });
172
+ const transformer: Transformer<
173
+ BatchV4ItemResult<LanguageModelV4GenerateResult>,
174
+ TextBatchItemResult
175
+ > & { cancel?: (reason?: unknown) => void } = {
176
+ transform(item, controller) {
177
+ controller.enqueue(convertBatchItemResult(item));
178
+ },
179
+
180
+ cancel(reason) {
181
+ streamAbortController.abort(
182
+ reason ?? new Error('Batch results stream was cancelled.'),
183
+ );
184
+ },
185
+ };
186
+ const transform = new TransformStream<
187
+ BatchV4ItemResult<LanguageModelV4GenerateResult>,
188
+ TextBatchItemResult
189
+ >(transformer);
190
+
191
+ void (async () => {
192
+ try {
193
+ const stream = await retry(() =>
194
+ model.experimental_doGetBatchResults({
195
+ batchId: batch.id,
196
+ providerOptions,
197
+ abortSignal: operationAbortSignal,
198
+ headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
199
+ }),
200
+ );
201
+
202
+ await stream.pipeTo(transform.writable, {
203
+ signal: operationAbortSignal,
204
+ });
205
+ } catch (error) {
206
+ await transform.writable.abort(wrapGatewayError(error)).catch(() => {});
207
+ }
208
+ })();
209
+
210
+ return asAsyncIterableStream(transform.readable);
211
+ }
212
+
213
+ function resolveBatchLanguageModel(
214
+ modelArg: StartTextBatchOptions['model'],
215
+ ): BatchLanguageModelV4 {
216
+ const model = resolveLanguageModel(modelArg);
217
+
218
+ if (!isBatchLanguageModel(model)) {
219
+ throw new UnsupportedFunctionalityError({
220
+ functionality: 'batch processing',
221
+ message: `The ${model.provider} model "${model.modelId}" does not support batch processing.`,
222
+ });
223
+ }
224
+
225
+ return model;
226
+ }
227
+
228
+ function isBatchLanguageModel(
229
+ model: LanguageModelV4,
230
+ ): model is BatchLanguageModelV4 {
231
+ const candidate = model as Partial<BatchLanguageModelV4>;
232
+ return (
233
+ typeof candidate.experimental_doStartBatch === 'function' &&
234
+ typeof candidate.experimental_doGetBatchStatus === 'function' &&
235
+ typeof candidate.experimental_doGetBatchResults === 'function'
236
+ );
237
+ }
238
+
239
+ function validateRequests(requests: ReadonlyArray<TextBatchRequest>) {
240
+ if (requests.length === 0) {
241
+ throw new InvalidArgumentError({
242
+ parameter: 'requests',
243
+ value: requests,
244
+ message: 'requests must not be empty',
245
+ });
246
+ }
247
+
248
+ const ids = new Set<string>();
249
+
250
+ for (const request of requests) {
251
+ if (request.id.trim().length === 0) {
252
+ throw new InvalidArgumentError({
253
+ parameter: 'requests',
254
+ value: requests,
255
+ message: 'request IDs must not be empty',
256
+ });
257
+ }
258
+
259
+ if (ids.has(request.id)) {
260
+ throw new InvalidArgumentError({
261
+ parameter: 'requests',
262
+ value: requests,
263
+ message: `request IDs must be unique; duplicate ID "${request.id}"`,
264
+ });
265
+ }
266
+
267
+ ids.add(request.id);
268
+ }
269
+ }
270
+
271
+ function validateBatchReference({
272
+ model,
273
+ batch,
274
+ }: {
275
+ model: BatchLanguageModelV4;
276
+ batch: BatchReference;
277
+ }) {
278
+ if (batch.version !== 1 || batch.type !== 'text') {
279
+ throw new InvalidArgumentError({
280
+ parameter: 'batch',
281
+ value: batch,
282
+ message: 'batch must be a supported text batch reference',
283
+ });
284
+ }
285
+
286
+ if (batch.provider !== model.provider || batch.modelId !== model.modelId) {
287
+ throw new InvalidArgumentError({
288
+ parameter: 'model',
289
+ value: model,
290
+ message:
291
+ `model ${model.provider}:${model.modelId} is not compatible with ` +
292
+ `batch ${batch.provider}:${batch.modelId}`,
293
+ });
294
+ }
295
+ }
296
+
297
+ function convertBatchItemResult(
298
+ item: BatchV4ItemResult<LanguageModelV4GenerateResult>,
299
+ ): TextBatchItemResult {
300
+ if (item.status !== 'succeeded') {
301
+ return item;
302
+ }
303
+
304
+ return {
305
+ id: item.id,
306
+ status: 'succeeded',
307
+ ...convertGenerateResult(item.result),
308
+ };
309
+ }
310
+
311
+ function convertGenerateResult(
312
+ result: LanguageModelV4GenerateResult,
313
+ ): TextBatchGenerationResult {
314
+ return {
315
+ text: result.content
316
+ .filter(
317
+ (part): part is Extract<typeof part, { type: 'text' }> =>
318
+ part.type === 'text',
319
+ )
320
+ .map(part => part.text)
321
+ .join(''),
322
+ finishReason: result.finishReason.unified,
323
+ rawFinishReason: result.finishReason.raw,
324
+ usage: asLanguageModelUsage(result.usage),
325
+ ...(result.response != null
326
+ ? {
327
+ response: {
328
+ id: result.response.id,
329
+ timestamp: result.response.timestamp?.toISOString(),
330
+ modelId: result.response.modelId,
331
+ },
332
+ }
333
+ : {}),
334
+ providerMetadata: result.providerMetadata,
335
+ };
336
+ }
@@ -0,0 +1,19 @@
1
+ export {
2
+ startTextBatch as experimental_startTextBatch,
3
+ getBatchResults as experimental_getBatchResults,
4
+ getBatchStatus as experimental_getBatchStatus,
5
+ } from './batch';
6
+ export type {
7
+ BatchError as Experimental_BatchError,
8
+ BatchLanguageModel as Experimental_BatchLanguageModel,
9
+ BatchOperationOptions as Experimental_BatchOperationOptions,
10
+ BatchReference as Experimental_BatchReference,
11
+ BatchStatus as Experimental_BatchStatus,
12
+ StartTextBatchOptions as Experimental_StartTextBatchOptions,
13
+ StartTextBatchResult as Experimental_StartTextBatchResult,
14
+ TextBatch as Experimental_TextBatch,
15
+ TextBatchGenerationResult as Experimental_TextBatchGenerationResult,
16
+ TextBatchItemResult as Experimental_TextBatchItemResult,
17
+ TextBatchReference as Experimental_TextBatchReference,
18
+ TextBatchRequest as Experimental_TextBatchRequest,
19
+ } from './batch-types';
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ export {
32
32
 
33
33
  // directory exports
34
34
  export * from './agent';
35
+ export * from './batch';
35
36
  export * from './embed';
36
37
  export * from './error';
37
38
  export * from './generate-image';
@@ -0,0 +1,44 @@
1
+ import type { LanguageModelV4Prompt } from '@ai-sdk/provider';
2
+ import { asArray } from '@ai-sdk/provider-utils';
3
+ import type { Instructions } from '../prompt/prompt';
4
+ import type { LanguageModelMiddleware } from '../types';
5
+
6
+ /**
7
+ * Applies default instructions to a language model when a call does not
8
+ * already contain a system message.
9
+ */
10
+ export function defaultInstructionsMiddleware({
11
+ instructions,
12
+ }: {
13
+ /**
14
+ * Default instructions to prepend to calls that do not contain a system
15
+ * message.
16
+ */
17
+ instructions: Instructions;
18
+ }): LanguageModelMiddleware {
19
+ const defaultSystemMessages: LanguageModelV4Prompt =
20
+ typeof instructions === 'string'
21
+ ? [{ role: 'system', content: instructions }]
22
+ : asArray(instructions).map(message => ({
23
+ role: 'system',
24
+ content: message.content,
25
+ providerOptions: message.providerOptions,
26
+ }));
27
+
28
+ return {
29
+ specificationVersion: 'v4',
30
+ transformParams: async ({ params }) => {
31
+ if (
32
+ defaultSystemMessages.length === 0 ||
33
+ params.prompt.some(message => message.role === 'system')
34
+ ) {
35
+ return params;
36
+ }
37
+
38
+ return {
39
+ ...params,
40
+ prompt: [...defaultSystemMessages, ...params.prompt],
41
+ };
42
+ },
43
+ };
44
+ }
@@ -1,4 +1,5 @@
1
1
  export { defaultEmbeddingSettingsMiddleware } from './default-embedding-settings-middleware';
2
+ export { defaultInstructionsMiddleware } from './default-instructions-middleware';
2
3
  export { defaultSettingsMiddleware } from './default-settings-middleware';
3
4
  export { extractJsonMiddleware } from './extract-json-middleware';
4
5
  export { extractReasoningMiddleware } from './extract-reasoning-middleware';
package/src/ui/chat.ts CHANGED
@@ -655,7 +655,10 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
655
655
  try {
656
656
  const response = {
657
657
  state: createStreamingUIMessageState({
658
- lastMessage: this.state.snapshot(lastMessage),
658
+ lastMessage:
659
+ trigger === 'regenerate-message'
660
+ ? undefined
661
+ : this.state.snapshot(lastMessage),
659
662
  messageId: this.generateId(),
660
663
  }),
661
664
  abortController: new AbortController(),
@@ -62,8 +62,10 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
62
62
  parts: message.parts.filter(
63
63
  part =>
64
64
  !isToolUIPart(part) ||
65
- (part.state !== 'input-streaming' &&
66
- part.state !== 'input-available'),
65
+ part.state === 'approval-responded' ||
66
+ part.state === 'output-available' ||
67
+ part.state === 'output-error' ||
68
+ part.state === 'output-denied',
67
69
  ),
68
70
  }));
69
71
  }
@@ -78,7 +78,15 @@ export function asAsyncIterableStream<T>(
78
78
  return { done: true, value: undefined };
79
79
  }
80
80
 
81
- const { done, value } = await reader.read();
81
+ let result: ReadableStreamReadResult<T>;
82
+ try {
83
+ result = await reader.read();
84
+ } catch (error) {
85
+ await cleanup(false);
86
+ throw error;
87
+ }
88
+
89
+ const { done, value } = result;
82
90
 
83
91
  if (done) {
84
92
  await cleanup(true);