@ai-sdk/anthropic 4.0.42 → 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.42",
3
+ "version": "4.0.44",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@ai-sdk/provider": "4.0.8",
39
- "@ai-sdk/provider-utils": "5.0.30"
39
+ "@ai-sdk/provider-utils": "5.0.32"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.1",
@@ -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 {
@@ -256,26 +256,28 @@ export class AnthropicMessagesBatchLanguageModel
256
256
  }
257
257
 
258
258
  if (batch.results_url == null) {
259
- throw new InvalidArgumentError({
260
- argument: 'batchId',
261
- 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.`,
262
262
  });
263
263
  }
264
264
 
265
- const { value: stream } = await getFromApi({
265
+ const { value: lines } = await getFromApi({
266
266
  url: batch.results_url,
267
267
  validateUrl: true,
268
268
  credentialedOrigin: this.config.baseURL,
269
269
  trustedOrigin: this.config.baseURL,
270
270
  headers: await this.getBatchHeaders(options.headers),
271
271
  failedResponseHandler: anthropicFailedResponseHandler,
272
- successfulResponseHandler: rawStreamResponseHandler,
272
+ successfulResponseHandler: createJsonLinesResponseHandler(
273
+ anthropicBatchResultLineSchema,
274
+ ),
273
275
  abortSignal: options.abortSignal,
274
276
  fetch: this.config.fetch,
275
277
  });
276
278
 
277
279
  return convertAsyncIteratorToReadableStream(
278
- this.iterateBatchResults(stream),
280
+ this.iterateBatchResults(lines),
279
281
  );
280
282
  }
281
283
 
@@ -298,9 +300,9 @@ export class AnthropicMessagesBatchLanguageModel
298
300
  }
299
301
 
300
302
  private async *iterateBatchResults(
301
- stream: ReadableStream<Uint8Array>,
303
+ lines: AsyncIterable<AnthropicBatchResultLine>,
302
304
  ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
303
- for await (const line of parseJsonLines(stream)) {
305
+ for await (const line of lines) {
304
306
  yield await convertAnthropicBatchResult(line);
305
307
  }
306
308
  }
@@ -453,24 +455,17 @@ function mapAnthropicBatchStatus(rawStatus: string): BatchV4Status['status'] {
453
455
  function convertAnthropicRequestCounts(
454
456
  counts: AnthropicBatchResponse['request_counts'],
455
457
  ): BatchV4Status['requestCounts'] | undefined {
456
- const values = [
457
- counts.processing,
458
- counts.succeeded,
459
- counts.errored,
460
- counts.canceled,
461
- counts.expired,
462
- ];
463
-
464
- if (values.some(value => !Number.isFinite(value) || value < 0)) {
465
- return undefined;
466
- }
467
-
468
- return {
469
- 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,
470
465
  pending: counts.processing,
471
466
  completed: counts.succeeded,
472
467
  failed: counts.errored + counts.canceled + counts.expired,
473
- };
468
+ });
474
469
  }
475
470
 
476
471
  async function convertAnthropicBatchResult(
@@ -530,7 +525,7 @@ async function convertAnthropicBatchResult(
530
525
  message:
531
526
  `Anthropic returned a "${unsupportedPart.type}" content block, ` +
532
527
  'but tool content is not supported in AI SDK text batches.',
533
- code: 'unsupported_tool_content',
528
+ code: 'unsupported_content',
534
529
  },
535
530
  };
536
531
  }
@@ -706,64 +701,3 @@ function mapAnthropicStopDetails(
706
701
  : {}),
707
702
  };
708
703
  }
709
-
710
- const rawStreamResponseHandler: ResponseHandler<
711
- ReadableStream<Uint8Array>
712
- > = async ({ response }) => {
713
- if (response.body == null) {
714
- throw new EmptyResponseBodyError();
715
- }
716
-
717
- return { value: response.body };
718
- };
719
-
720
- async function* parseJsonLines(
721
- stream: ReadableStream<Uint8Array>,
722
- ): AsyncGenerator<AnthropicBatchResultLine> {
723
- const reader = stream.getReader();
724
- const decoder = new TextDecoder();
725
- let buffer = '';
726
- let finished = false;
727
-
728
- try {
729
- while (true) {
730
- const { done, value } = await reader.read();
731
-
732
- if (done) {
733
- finished = true;
734
- buffer += decoder.decode();
735
- break;
736
- }
737
-
738
- buffer += decoder.decode(value, { stream: true });
739
-
740
- let lineEnd = buffer.indexOf('\n');
741
- while (lineEnd !== -1) {
742
- const line = buffer.slice(0, lineEnd).replace(/\r$/, '');
743
- buffer = buffer.slice(lineEnd + 1);
744
-
745
- if (line.trim().length > 0) {
746
- yield await parseJSON({
747
- text: line,
748
- schema: anthropicBatchResultLineSchema,
749
- });
750
- }
751
-
752
- lineEnd = buffer.indexOf('\n');
753
- }
754
- }
755
-
756
- const finalLine = buffer.replace(/\r$/, '');
757
- if (finalLine.trim().length > 0) {
758
- yield await parseJSON({
759
- text: finalLine,
760
- schema: anthropicBatchResultLineSchema,
761
- });
762
- }
763
- } finally {
764
- if (!finished) {
765
- await reader.cancel().catch(() => {});
766
- }
767
- reader.releaseLock();
768
- }
769
- }
@@ -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,