@ai-sdk/anthropic 4.0.41 → 4.0.44

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.
@@ -155,12 +155,6 @@ Anthropic language models support asynchronous text generation through the
155
155
  Use the experimental text batch APIs to start a batch, poll its status, and
156
156
  stream its results:
157
157
 
158
- <Note>
159
- Anthropic Message Batches do not support the `speed` option. Explicit
160
- `anthropicBeta` values must be configured when starting the batch rather than
161
- on an individual request.
162
- </Note>
163
-
164
158
  ```ts
165
159
  import { anthropic } from '@ai-sdk/anthropic';
166
160
  import {
@@ -175,8 +169,8 @@ const model = anthropic('claude-haiku-4-5');
175
169
  const batch = await startTextBatch({
176
170
  model,
177
171
  requests: [
178
- { id: 'first', prompt: 'What is the capital of France?' },
179
- { id: 'second', prompt: 'What is the capital of Germany?' },
172
+ { id: 'capital-france', prompt: 'What is the capital of France?' },
173
+ { id: 'capital-germany', prompt: 'What is the capital of Germany?' },
180
174
  ],
181
175
  });
182
176
 
@@ -195,6 +189,21 @@ for await (const item of getBatchResults({ model, batch })) {
195
189
  }
196
190
  ```
197
191
 
192
+ `startTextBatch` returns a serializable batch reference. Persist this reference
193
+ to check the batch status or retrieve its results from another process. Results
194
+ can arrive in a different order from the input requests, so match each result by
195
+ its `id`.
196
+
197
+ #### Batch Limitations
198
+
199
+ <Note>
200
+ Anthropic Message Batches do not support the `speed` option or completion
201
+ webhooks. Explicit `anthropicBeta` values must be configured when starting the
202
+ batch rather than on an individual request. When you provide a `webhookUrl`,
203
+ the provider returns an unsupported warning and starts the batch without a
204
+ webhook.
205
+ </Note>
206
+
198
207
  ### Structured Outputs and Tool Input Streaming
199
208
 
200
209
  Tool call streaming is enabled by default. You can opt out by setting the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/anthropic",
3
- "version": "4.0.41",
3
+ "version": "4.0.44",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -35,16 +35,16 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@ai-sdk/provider": "4.0.7",
39
- "@ai-sdk/provider-utils": "5.0.29"
38
+ "@ai-sdk/provider": "4.0.8",
39
+ "@ai-sdk/provider-utils": "5.0.32"
40
40
  },
41
41
  "devDependencies": {
42
+ "@ai-sdk/test-server": "2.0.1",
42
43
  "@types/node": "22.19.19",
44
+ "@vercel/ai-tsconfig": "0.0.0",
43
45
  "tsup": "^8.5.1",
44
46
  "typescript": "5.8.3",
45
- "zod": "3.25.76",
46
- "@ai-sdk/test-server": "2.0.1",
47
- "@vercel/ai-tsconfig": "0.0.0"
47
+ "zod": "3.25.76"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "zod": "^3.25.76 || ^4.1.8"
@@ -1368,6 +1368,10 @@ export const anthropicChunkSchema = lazySchema(() =>
1368
1368
  error: z.object({
1369
1369
  type: z.string(),
1370
1370
  message: z.string(),
1371
+ code: z.union([z.string(), z.number()]).nullish(),
1372
+ statusCode: z.number().nullish(),
1373
+ isRetryable: z.boolean().nullish(),
1374
+ data: z.unknown().nullish(),
1371
1375
  }),
1372
1376
  }),
1373
1377
  z.object({
@@ -20,9 +20,11 @@ import {
20
20
  combineHeaders,
21
21
  createEventSourceResponseHandler,
22
22
  createJsonResponseHandler,
23
+ createProviderStreamError,
23
24
  createToolNameMapping,
24
25
  generateId,
25
26
  isCustomReasoning,
27
+ isProviderStreamError,
26
28
  mapReasoningToProviderBudget,
27
29
  mapReasoningToProviderEffort,
28
30
  parseProviderOptions,
@@ -69,6 +71,53 @@ import { CacheControlValidator } from './get-cache-control';
69
71
  import { mapAnthropicStopReason } from './map-anthropic-stop-reason';
70
72
  import { sanitizeJsonSchema } from './sanitize-json-schema';
71
73
 
74
+ function createAnthropicStreamError(error: {
75
+ message: string;
76
+ type: string;
77
+ code?: string | number | null;
78
+ statusCode?: number | null;
79
+ isRetryable?: boolean | null;
80
+ data?: unknown;
81
+ }) {
82
+ const inferredMetadata = getAnthropicStreamErrorMetadata(error.type);
83
+
84
+ return createProviderStreamError({
85
+ message: error.message,
86
+ type: error.type,
87
+ code: error.code ?? undefined,
88
+ statusCode: error.statusCode ?? inferredMetadata.statusCode,
89
+ isRetryable: error.isRetryable ?? inferredMetadata.isRetryable,
90
+ data: 'data' in error ? error.data : error,
91
+ });
92
+ }
93
+
94
+ function getAnthropicStreamErrorMetadata(type: string): {
95
+ statusCode?: number;
96
+ isRetryable?: boolean;
97
+ } {
98
+ switch (type) {
99
+ case 'api_error':
100
+ return { statusCode: 500, isRetryable: true };
101
+ case 'overloaded_error':
102
+ return { statusCode: 529, isRetryable: true };
103
+ case 'rate_limit_error':
104
+ return { statusCode: 429, isRetryable: true };
105
+ case 'request_too_large':
106
+ return { statusCode: 413, isRetryable: false };
107
+ case 'authentication_error':
108
+ return { statusCode: 401, isRetryable: false };
109
+ case 'permission_error':
110
+ return { statusCode: 403, isRetryable: false };
111
+ case 'not_found_error':
112
+ return { statusCode: 404, isRetryable: false };
113
+ case 'billing_error':
114
+ case 'invalid_request_error':
115
+ return { statusCode: 400, isRetryable: false };
116
+ default:
117
+ return {};
118
+ }
119
+ }
120
+
72
121
  function createCitationSource(
73
122
  citation: Citation,
74
123
  citationDocuments: Array<{
@@ -2662,7 +2711,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2662
2711
  }
2663
2712
 
2664
2713
  case 'error': {
2665
- controller.enqueue({ type: 'error', error: value.error });
2714
+ controller.enqueue({
2715
+ type: 'error',
2716
+ error: createAnthropicStreamError(value.error),
2717
+ });
2666
2718
  return;
2667
2719
  }
2668
2720
 
@@ -2693,16 +2745,20 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2693
2745
  // We handle the case where the first chunk is an error here and transform
2694
2746
  // it into an APICallError.
2695
2747
  if (result.value?.type === 'error') {
2696
- const error = result.value.error as { message: string; type: string };
2748
+ const error = result.value.error;
2749
+
2750
+ if (!isProviderStreamError(error)) {
2751
+ throw new Error('Expected a normalized Anthropic stream error');
2752
+ }
2697
2753
 
2698
2754
  throw new APICallError({
2699
2755
  message: error.message,
2700
2756
  url,
2701
2757
  requestBodyValues: body,
2702
- statusCode: error.type === 'overloaded_error' ? 529 : 500,
2758
+ statusCode: error.statusCode ?? 500,
2703
2759
  responseHeaders,
2704
- responseBody: JSON.stringify(error),
2705
- isRetryable: error.type === 'overloaded_error',
2760
+ responseBody: JSON.stringify(error.data),
2761
+ isRetryable: error.isRetryable ?? false,
2706
2762
  });
2707
2763
  }
2708
2764
  } finally {
@@ -1,6 +1,6 @@
1
1
  import {
2
- EmptyResponseBodyError,
3
2
  InvalidArgumentError,
3
+ InvalidResponseDataError,
4
4
  UnsupportedFunctionalityError,
5
5
  type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
6
6
  type Experimental_BatchV4ItemResult as BatchV4ItemResult,
@@ -14,11 +14,12 @@ import {
14
14
  import {
15
15
  combineHeaders,
16
16
  convertAsyncIteratorToReadableStream,
17
+ createJsonLinesResponseHandler,
17
18
  createJsonResponseHandler,
18
19
  getFromApi,
19
20
  lazySchema,
21
+ normalizeBatchRequestCounts,
20
22
  normalizeHeaders,
21
- parseJSON,
22
23
  parseProviderOptions,
23
24
  postJsonToApi,
24
25
  resolve,
@@ -27,7 +28,6 @@ import {
27
28
  WORKFLOW_SERIALIZE,
28
29
  zodSchema,
29
30
  type InferSchema,
30
- type ResponseHandler,
31
31
  } from '@ai-sdk/provider-utils';
32
32
  import { z } from 'zod/v4';
33
33
  import {
@@ -145,6 +145,7 @@ export class AnthropicMessagesBatchLanguageModel
145
145
  providerOptions,
146
146
  headers,
147
147
  abortSignal,
148
+ webhookUrl,
148
149
  }: Parameters<
149
150
  BatchLanguageModelV4['experimental_doStartBatch']
150
151
  >[0]): Promise<BatchV4StartResult> {
@@ -161,7 +162,19 @@ export class AnthropicMessagesBatchLanguageModel
161
162
  custom_id: string;
162
163
  params: Record<string, unknown>;
163
164
  }> = [];
164
- const batchWarnings: BatchV4StartResult['warnings'] = [];
165
+ const batchWarnings: BatchV4StartResult['warnings'] =
166
+ webhookUrl == null
167
+ ? []
168
+ : [
169
+ {
170
+ warning: {
171
+ type: 'unsupported',
172
+ feature: 'webhookUrl',
173
+ details:
174
+ 'The Anthropic Message Batches API does not support completion webhooks.',
175
+ },
176
+ },
177
+ ];
165
178
 
166
179
  for (const request of requests) {
167
180
  const requestBetas = await getAnthropicBatchProviderBetas({
@@ -243,26 +256,28 @@ export class AnthropicMessagesBatchLanguageModel
243
256
  }
244
257
 
245
258
  if (batch.results_url == null) {
246
- throw new InvalidArgumentError({
247
- argument: 'batchId',
248
- message: `Anthropic batch "${options.batchId}" does not have a results URL.`,
259
+ throw new InvalidResponseDataError({
260
+ data: batch,
261
+ message: `Anthropic batch "${options.batchId}" completed without batch output.`,
249
262
  });
250
263
  }
251
264
 
252
- const { value: stream } = await getFromApi({
265
+ const { value: lines } = await getFromApi({
253
266
  url: batch.results_url,
254
267
  validateUrl: true,
255
268
  credentialedOrigin: this.config.baseURL,
256
269
  trustedOrigin: this.config.baseURL,
257
270
  headers: await this.getBatchHeaders(options.headers),
258
271
  failedResponseHandler: anthropicFailedResponseHandler,
259
- successfulResponseHandler: rawStreamResponseHandler,
272
+ successfulResponseHandler: createJsonLinesResponseHandler(
273
+ anthropicBatchResultLineSchema,
274
+ ),
260
275
  abortSignal: options.abortSignal,
261
276
  fetch: this.config.fetch,
262
277
  });
263
278
 
264
279
  return convertAsyncIteratorToReadableStream(
265
- this.iterateBatchResults(stream),
280
+ this.iterateBatchResults(lines),
266
281
  );
267
282
  }
268
283
 
@@ -285,9 +300,9 @@ export class AnthropicMessagesBatchLanguageModel
285
300
  }
286
301
 
287
302
  private async *iterateBatchResults(
288
- stream: ReadableStream<Uint8Array>,
303
+ lines: AsyncIterable<AnthropicBatchResultLine>,
289
304
  ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
290
- for await (const line of parseJsonLines(stream)) {
305
+ for await (const line of lines) {
291
306
  yield await convertAnthropicBatchResult(line);
292
307
  }
293
308
  }
@@ -440,24 +455,17 @@ function mapAnthropicBatchStatus(rawStatus: string): BatchV4Status['status'] {
440
455
  function convertAnthropicRequestCounts(
441
456
  counts: AnthropicBatchResponse['request_counts'],
442
457
  ): BatchV4Status['requestCounts'] | undefined {
443
- const values = [
444
- counts.processing,
445
- counts.succeeded,
446
- counts.errored,
447
- counts.canceled,
448
- counts.expired,
449
- ];
450
-
451
- if (values.some(value => !Number.isFinite(value) || value < 0)) {
452
- return undefined;
453
- }
454
-
455
- return {
456
- total: values.reduce((total, value) => total + value, 0),
458
+ return normalizeBatchRequestCounts({
459
+ total:
460
+ counts.processing +
461
+ counts.succeeded +
462
+ counts.errored +
463
+ counts.canceled +
464
+ counts.expired,
457
465
  pending: counts.processing,
458
466
  completed: counts.succeeded,
459
467
  failed: counts.errored + counts.canceled + counts.expired,
460
- };
468
+ });
461
469
  }
462
470
 
463
471
  async function convertAnthropicBatchResult(
@@ -517,7 +525,7 @@ async function convertAnthropicBatchResult(
517
525
  message:
518
526
  `Anthropic returned a "${unsupportedPart.type}" content block, ` +
519
527
  'but tool content is not supported in AI SDK text batches.',
520
- code: 'unsupported_tool_content',
528
+ code: 'unsupported_content',
521
529
  },
522
530
  };
523
531
  }
@@ -693,64 +701,3 @@ function mapAnthropicStopDetails(
693
701
  : {}),
694
702
  };
695
703
  }
696
-
697
- const rawStreamResponseHandler: ResponseHandler<
698
- ReadableStream<Uint8Array>
699
- > = async ({ response }) => {
700
- if (response.body == null) {
701
- throw new EmptyResponseBodyError();
702
- }
703
-
704
- return { value: response.body };
705
- };
706
-
707
- async function* parseJsonLines(
708
- stream: ReadableStream<Uint8Array>,
709
- ): AsyncGenerator<AnthropicBatchResultLine> {
710
- const reader = stream.getReader();
711
- const decoder = new TextDecoder();
712
- let buffer = '';
713
- let finished = false;
714
-
715
- try {
716
- while (true) {
717
- const { done, value } = await reader.read();
718
-
719
- if (done) {
720
- finished = true;
721
- buffer += decoder.decode();
722
- break;
723
- }
724
-
725
- buffer += decoder.decode(value, { stream: true });
726
-
727
- let lineEnd = buffer.indexOf('\n');
728
- while (lineEnd !== -1) {
729
- const line = buffer.slice(0, lineEnd).replace(/\r$/, '');
730
- buffer = buffer.slice(lineEnd + 1);
731
-
732
- if (line.trim().length > 0) {
733
- yield await parseJSON({
734
- text: line,
735
- schema: anthropicBatchResultLineSchema,
736
- });
737
- }
738
-
739
- lineEnd = buffer.indexOf('\n');
740
- }
741
- }
742
-
743
- const finalLine = buffer.replace(/\r$/, '');
744
- if (finalLine.trim().length > 0) {
745
- yield await parseJSON({
746
- text: finalLine,
747
- schema: anthropicBatchResultLineSchema,
748
- });
749
- }
750
- } finally {
751
- if (!finished) {
752
- await reader.cancel().catch(() => {});
753
- }
754
- reader.releaseLock();
755
- }
756
- }
@@ -22,6 +22,17 @@ interface AnthropicSkillsConfig {
22
22
  fetch?: FetchFunction;
23
23
  }
24
24
 
25
+ function encodePathSegment(value: string): string {
26
+ const encodedValue = encodeURIComponent(value);
27
+
28
+ // URL parsing normalizes both literal and percent-encoded dot segments.
29
+ return encodedValue === '.'
30
+ ? '%252E'
31
+ : encodedValue === '..'
32
+ ? '%252E%252E'
33
+ : encodedValue;
34
+ }
35
+
25
36
  export class AnthropicSkills implements SkillsV4 {
26
37
  readonly specificationVersion = 'v4';
27
38
 
@@ -47,7 +58,7 @@ export class AnthropicSkills implements SkillsV4 {
47
58
  headers: Record<string, string | undefined>;
48
59
  }): Promise<{ name?: string; description?: string }> {
49
60
  const { value: versionResponse } = await getFromApi({
50
- url: `${this.config.baseURL}/skills/${skillId}/versions/${version}`,
61
+ url: `${this.config.baseURL}/skills/${encodePathSegment(skillId)}/versions/${encodePathSegment(version)}`,
51
62
  validateUrl: false,
52
63
  headers,
53
64
  failedResponseHandler: anthropicFailedResponseHandler,