@ai-sdk/anthropic 4.0.89 → 4.0.91

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.
@@ -97,10 +97,12 @@ You can use the following optional settings to customize the Anthropic provider
97
97
  It defaults to the `ANTHROPIC_API_KEY` environment variable.
98
98
  Only one of `apiKey` or `authToken` is required.
99
99
 
100
- - **authToken** _string_
100
+ - **authToken** _string | PromiseLike<string> | (() => string | PromiseLike<string>)_
101
101
 
102
102
  Auth token that is being sent using the `Authorization: Bearer` header.
103
- It defaults to the `ANTHROPIC_AUTH_TOKEN` environment variable.
103
+ Pass a function to resolve a token for each request, including short-lived
104
+ tokens managed by an external credential provider. This setting does not
105
+ enable OAuth beta headers; configure any required headers explicitly.
104
106
  Only one of `apiKey` or `authToken` is required.
105
107
 
106
108
  - **headers** _Record<string,string>_
@@ -114,6 +116,76 @@ You can use the following optional settings to customize the Anthropic provider
114
116
  You can use it as a middleware to intercept requests,
115
117
  or to provide a custom fetch implementation for e.g. testing.
116
118
 
119
+ ### Workload identity federation
120
+
121
+ Use an `authToken` callback to connect the Anthropic SDK's token exchange and
122
+ cache helpers to the AI SDK. Configure your service account and federation rule
123
+ first, as described in [Anthropic's workload identity federation guide](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation).
124
+
125
+ The following example uses the credential helpers from `@anthropic-ai/sdk@0.129.0`:
126
+
127
+ <InstallPackages packages="ai @ai-sdk/anthropic @anthropic-ai/sdk@0.129.0" />
128
+
129
+ ```ts
130
+ import { createAnthropic } from '@ai-sdk/anthropic';
131
+ import { identityTokenFromFile } from '@anthropic-ai/sdk/lib/credentials/identity-token';
132
+ import { oidcFederationProvider } from '@anthropic-ai/sdk/lib/credentials/oidc-federation';
133
+ import { TokenCache } from '@anthropic-ai/sdk/lib/credentials/token-cache';
134
+ import { generateText } from 'ai';
135
+
136
+ // Create the cache and provider once, then reuse them across requests.
137
+ const tokenCache = new TokenCache(
138
+ oidcFederationProvider({
139
+ identityTokenProvider: identityTokenFromFile(
140
+ '/var/run/secrets/anthropic.com/token',
141
+ ),
142
+ federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
143
+ organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
144
+ serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID!,
145
+ // Set this when your rule requires a workspace or to select a specific one.
146
+ workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
147
+ baseURL: 'https://api.anthropic.com',
148
+ fetch: globalThis.fetch,
149
+ }),
150
+ );
151
+
152
+ const anthropic = createAnthropic({
153
+ authToken: () => tokenCache.getToken(),
154
+ headers: { 'anthropic-beta': 'oauth-2025-04-20' },
155
+ });
156
+
157
+ const { text } = await generateText({
158
+ model: anthropic('claude-opus-5-5'),
159
+ prompt: 'Explain workload identity federation in one sentence.',
160
+ });
161
+ ```
162
+
163
+ The AI SDK resolves `authToken` before each request and sends the returned token
164
+ as a bearer credential. The Anthropic SDK helpers perform the OIDC exchange,
165
+ cache the access token, and renew it as needed.
166
+
167
+ Set the `oauth-2025-04-20` beta explicitly for these federated OAuth tokens,
168
+ matching Anthropic's SDK. A bearer token or token callback alone does not imply
169
+ workload identity federation. The exchange helper supplies its own token-endpoint
170
+ headers. This example covers Messages API inference; it does not add WIF support
171
+ to the Files, Skills, or Batch APIs, which have separate beta-header handling.
172
+
173
+ Refresh is demand-driven: in the helper version above, `getToken()` starts a
174
+ background exchange when the cached token has at most two minutes remaining,
175
+ and waits for an exchange when at most 30 seconds remain. Concurrent requests
176
+ share the pending exchange. Federation renews access by exchanging another
177
+ identity token; it does not use a refresh token.
178
+
179
+ Your workload's identity provider must supply fresh identity tokens. The file
180
+ helper rereads the file on each exchange but does not rotate its contents.
181
+ Anthropic rejects reuse of assertions with a `jti` by default, so arrange for a
182
+ fresh assertion for each exchange. See the [WIF reference](https://platform.claude.com/docs/en/manage-claude/wif-reference)
183
+ for token lifetimes and replay protection.
184
+
185
+ This setup explicitly configures credentials; it does not enable the Anthropic
186
+ client's automatic credential discovery or its invalidate-and-retry behavior on
187
+ HTTP 401. Keep the helper version pinned and verify the integration when upgrading.
188
+
117
189
  ## Language Models
118
190
 
119
191
  You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/anthropic",
3
- "version": "4.0.89",
3
+ "version": "4.0.91",
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.34",
39
- "@ai-sdk/provider-utils": "5.0.69"
39
+ "@ai-sdk/provider-utils": "5.0.70"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -9,8 +9,10 @@ import {
9
9
  createJsonResponseHandler,
10
10
  lazySchema,
11
11
  postFormDataToApi,
12
+ resolve,
12
13
  zodSchema,
13
14
  type FetchFunction,
15
+ type Resolvable,
14
16
  } from '@ai-sdk/provider-utils';
15
17
  import { z } from 'zod/v4';
16
18
  import { anthropicFailedResponseHandler } from './anthropic-error';
@@ -32,7 +34,7 @@ const anthropicUploadFileResponseSchema = lazySchema(() =>
32
34
  interface AnthropicFilesConfig {
33
35
  provider: string;
34
36
  baseURL: string;
35
- headers: () => Record<string, string | undefined>;
37
+ headers: Resolvable<Record<string, string | undefined>>;
36
38
  fetch?: FetchFunction;
37
39
  }
38
40
 
@@ -68,7 +70,7 @@ export class AnthropicFiles implements FilesV4 {
68
70
  const { value: response } = await postFormDataToApi({
69
71
  url: `${this.config.baseURL}/files`,
70
72
  headers: combineHeaders(
71
- this.config.headers(),
73
+ await resolve(this.config.headers),
72
74
  { 'anthropic-beta': 'files-api-2025-04-14' },
73
75
  headers,
74
76
  ),
@@ -1151,15 +1151,17 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1151
1151
 
1152
1152
  private async getHeaders({
1153
1153
  betas,
1154
+ configHeaders,
1154
1155
  headers,
1155
1156
  providerOptions,
1156
1157
  }: {
1157
1158
  betas: Set<string>;
1159
+ configHeaders: Record<string, string | undefined> | undefined;
1158
1160
  headers: Record<string, string | undefined> | undefined;
1159
1161
  providerOptions: LanguageModelV4CallOptions['providerOptions'];
1160
1162
  }) {
1161
1163
  const combinedHeaders = combineHeaders(
1162
- this.config.headers ? await resolve(this.config.headers) : undefined,
1164
+ configHeaders,
1163
1165
  headers,
1164
1166
  betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {},
1165
1167
  );
@@ -1169,13 +1171,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1169
1171
  : combinedHeaders;
1170
1172
  }
1171
1173
 
1172
- private async getBetasFromHeaders(
1174
+ private getBetasFromHeaders(
1175
+ configHeaders: Record<string, string | undefined> | undefined,
1173
1176
  requestHeaders: Record<string, string | undefined> | undefined,
1174
1177
  ) {
1175
- const configHeaders = this.config.headers
1176
- ? await resolve(this.config.headers)
1177
- : undefined;
1178
-
1179
1178
  const configBetaHeader = configHeaders?.['anthropic-beta'] ?? '';
1180
1179
  const requestBetaHeader = requestHeaders?.['anthropic-beta'] ?? '';
1181
1180
 
@@ -1249,6 +1248,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1249
1248
  async doGenerate(
1250
1249
  options: LanguageModelV4CallOptions,
1251
1250
  ): Promise<LanguageModelV4GenerateResult> {
1251
+ const configHeaders = this.config.headers
1252
+ ? await resolve(this.config.headers)
1253
+ : undefined;
1254
+
1252
1255
  const {
1253
1256
  args,
1254
1257
  warnings,
@@ -1260,7 +1263,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1260
1263
  } = await this.getArgs({
1261
1264
  ...options,
1262
1265
  stream: false,
1263
- userSuppliedBetas: await this.getBetasFromHeaders(options.headers),
1266
+ userSuppliedBetas: this.getBetasFromHeaders(
1267
+ configHeaders,
1268
+ options.headers,
1269
+ ),
1264
1270
  });
1265
1271
 
1266
1272
  // Extract citation documents for response processing
@@ -1279,6 +1285,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1279
1285
  url: this.buildRequestUrl(false),
1280
1286
  headers: await this.getHeaders({
1281
1287
  betas,
1288
+ configHeaders,
1282
1289
  headers: options.headers,
1283
1290
  providerOptions: options.providerOptions,
1284
1291
  }),
@@ -1896,6 +1903,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1896
1903
  ): Promise<LanguageModelV4StreamResult> {
1897
1904
  'use step';
1898
1905
 
1906
+ const configHeaders = this.config.headers
1907
+ ? await resolve(this.config.headers)
1908
+ : undefined;
1909
+
1899
1910
  const {
1900
1911
  args: body,
1901
1912
  warnings,
@@ -1907,7 +1918,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1907
1918
  } = await this.getArgs({
1908
1919
  ...options,
1909
1920
  stream: true,
1910
- userSuppliedBetas: await this.getBetasFromHeaders(options.headers),
1921
+ userSuppliedBetas: this.getBetasFromHeaders(
1922
+ configHeaders,
1923
+ options.headers,
1924
+ ),
1911
1925
  });
1912
1926
 
1913
1927
  // Extract citation documents for response processing
@@ -1923,6 +1937,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1923
1937
  url,
1924
1938
  headers: await this.getHeaders({
1925
1939
  betas,
1940
+ configHeaders,
1926
1941
  headers: options.headers,
1927
1942
  providerOptions: options.providerOptions,
1928
1943
  }),
@@ -12,10 +12,12 @@ import {
12
12
  generateId,
13
13
  loadApiKey,
14
14
  loadOptionalSetting,
15
+ resolve,
15
16
  validateBaseURL,
16
17
  withoutTrailingSlash,
17
18
  withUserAgentSuffix,
18
19
  type FetchFunction,
20
+ type Resolvable,
19
21
  } from '@ai-sdk/provider-utils';
20
22
  import { Experimental_DecisionLanguageModel as DecisionLanguageModel } from '@ai-sdk/provider-utils/experimental-decision';
21
23
  import { AnthropicFiles } from './anthropic-files';
@@ -103,10 +105,11 @@ export interface AnthropicProviderSettings {
103
105
 
104
106
  /**
105
107
  * Auth token that is being sent using the `Authorization: Bearer` header.
106
- * It defaults to the `ANTHROPIC_AUTH_TOKEN` environment variable.
108
+ * Can be a string, a promise, or a function returning either. Functions are
109
+ * called before each request to support short-lived tokens and token refresh.
107
110
  * Only one of `apiKey` or `authToken` is required.
108
111
  */
109
- authToken?: string;
112
+ authToken?: Resolvable<string>;
110
113
 
111
114
  /**
112
115
  * Custom headers to include in the requests.
@@ -157,18 +160,8 @@ export function createAnthropic(
157
160
  });
158
161
  }
159
162
 
160
- const getHeaders = () => {
161
- const authHeaders: Record<string, string> = options.authToken
162
- ? { Authorization: `Bearer ${options.authToken}` }
163
- : {
164
- 'x-api-key': loadApiKey({
165
- apiKey: options.apiKey,
166
- environmentVariableName: 'ANTHROPIC_API_KEY',
167
- description: 'Anthropic',
168
- }),
169
- };
170
-
171
- return withUserAgentSuffix(
163
+ const createHeaders = (authHeaders: Record<string, string>) =>
164
+ withUserAgentSuffix(
172
165
  {
173
166
  'anthropic-version': '2023-06-01',
174
167
  ...authHeaders,
@@ -176,6 +169,28 @@ export function createAnthropic(
176
169
  },
177
170
  `ai-sdk-anthropic/${VERSION}`,
178
171
  );
172
+
173
+ const getHeaders = () => {
174
+ const authToken = options.authToken;
175
+
176
+ if (authToken) {
177
+ const token = typeof authToken === 'function' ? authToken() : authToken;
178
+ const createBearerHeaders = (token: string) =>
179
+ createHeaders({ Authorization: `Bearer ${token}` });
180
+
181
+ // Keep synchronous credentials serializable across workflow boundaries.
182
+ return typeof token === 'string'
183
+ ? createBearerHeaders(token)
184
+ : resolve(token).then(createBearerHeaders);
185
+ }
186
+
187
+ return createHeaders({
188
+ 'x-api-key': loadApiKey({
189
+ apiKey: options.apiKey,
190
+ environmentVariableName: 'ANTHROPIC_API_KEY',
191
+ description: 'Anthropic',
192
+ }),
193
+ });
179
194
  };
180
195
 
181
196
  const languageModelConfig = {
@@ -41,16 +41,14 @@ export class AnthropicSkills implements SkillsV4 {
41
41
  private async fetchVersionMetadata({
42
42
  skillId,
43
43
  version,
44
- headers,
45
44
  }: {
46
45
  skillId: string;
47
46
  version: string;
48
- headers: Record<string, string | undefined>;
49
47
  }): Promise<{ name?: string; description?: string }> {
50
48
  const { value: versionResponse } = await getFromApi({
51
49
  url: `${this.config.baseURL}/skills/${encodePathSegment(skillId)}/versions/${encodePathSegment(version)}`,
52
50
  validateUrl: false,
53
- headers,
51
+ headers: await this.getHeaders(),
54
52
  failedResponseHandler: anthropicFailedResponseHandler,
55
53
  successfulResponseHandler: createJsonResponseHandler(
56
54
  anthropicSkillVersionResponseSchema,
@@ -86,11 +84,9 @@ export class AnthropicSkills implements SkillsV4 {
86
84
  );
87
85
  }
88
86
 
89
- const headers = await this.getHeaders();
90
-
91
87
  const { value: response } = await postFormDataToApi({
92
88
  url: `${this.config.baseURL}/skills`,
93
- headers,
89
+ headers: await this.getHeaders(),
94
90
  formData,
95
91
  failedResponseHandler: anthropicFailedResponseHandler,
96
92
  successfulResponseHandler: createJsonResponseHandler(
@@ -104,7 +100,6 @@ export class AnthropicSkills implements SkillsV4 {
104
100
  ? await this.fetchVersionMetadata({
105
101
  skillId: response.id,
106
102
  version: response.latest_version,
107
- headers,
108
103
  })
109
104
  : {};
110
105