@anthropic-ai/sdk
Version:
The official TypeScript library for the Anthropic API
1,294 lines (1,198 loc) • 75.3 kB
text/typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types';
import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types';
import { uuid4 } from './internal/utils/uuid';
import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values';
import { sleep } from './internal/utils/sleep';
export type { Logger, LogLevel } from './internal/utils/log';
import { castToError, isAbortError } from './internal/errors';
import type { APIResponseProps } from './internal/parse';
import { getPlatformHeaders } from './internal/detect-platform';
import {
armAbandonmentBackstop,
registerRequestSignalCleanup,
releaseRequestSignal,
} from './internal/request-signal';
import * as Shims from './internal/shims';
import * as Opts from './internal/request-options';
import { stringifyQuery } from './internal/utils/query';
import { VERSION } from './version';
import * as Errors from './core/error';
import type { AccessTokenProvider } from './lib/credentials/types';
import { OAUTH_API_BETA_HEADER } from './lib/credentials/types';
import { TokenCache } from './lib/credentials/token-cache';
import { defaultCredentials, resolveCredentialsFromConfig } from './lib/credentials/credential-chain';
import type { AnthropicConfig } from './core/credentials';
import {
type Middleware,
isFetchOriginError,
isRetryableError,
wrapFetchWithMiddleware,
} from './core/middleware';
export type { Middleware, MiddlewareContext, MiddlewareNext } from './core/middleware';
export type { APIRequest } from './core/api';
import * as Pagination from './core/pagination';
import {
type BidirectionalPageCursorParams,
BidirectionalPageCursorResponse,
type PageCursorParams,
PageCursorResponse,
type PageParams,
PageResponse,
type TokenPageParams,
TokenPageResponse,
} from './core/pagination';
import * as Uploads from './core/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
import {
Completion,
CompletionCreateParams,
CompletionCreateParamsNonStreaming,
CompletionCreateParamsStreaming,
Completions,
} from './resources/completions';
import {
CapabilitySupport,
ContextManagementCapability,
EffortCapability,
ModelCapabilities,
ModelInfo,
ModelInfosPage,
ModelListParams,
ModelRetrieveParams,
Models,
ThinkingCapability,
ThinkingTypes,
} from './resources/models';
import {
AnthropicBeta,
Beta,
BetaAPIError,
BetaAuthenticationError,
BetaBillingError,
BetaError,
BetaErrorResponse,
BetaGatewayTimeoutError,
BetaInvalidRequestError,
BetaNotFoundError,
BetaOverloadedError,
BetaPermissionError,
BetaRateLimitError,
} from './resources/beta/beta';
import {
Base64ImageSource,
Base64PDFSource,
BashCodeExecutionOutputBlock,
BashCodeExecutionOutputBlockParam,
BashCodeExecutionResultBlock,
BashCodeExecutionResultBlockParam,
BashCodeExecutionToolResultBlock,
BashCodeExecutionToolResultBlockParam,
BashCodeExecutionToolResultError,
BashCodeExecutionToolResultErrorCode,
BashCodeExecutionToolResultErrorParam,
CacheControlEphemeral,
CacheCreation,
CitationCharLocation,
CitationCharLocationParam,
CitationContentBlockLocation,
CitationContentBlockLocationParam,
CitationPageLocation,
CitationPageLocationParam,
CitationSearchResultLocationParam,
CitationWebSearchResultLocationParam,
CitationsConfig,
CitationsConfigParam,
CitationsDelta,
CitationsSearchResultLocation,
CitationsWebSearchResultLocation,
CodeExecutionOutputBlock,
CodeExecutionOutputBlockParam,
CodeExecutionResultBlock,
CodeExecutionResultBlockParam,
CodeExecutionTool20250522,
CodeExecutionTool20250825,
CodeExecutionTool20260120,
CodeExecutionTool20260521,
CodeExecutionToolResultBlock,
CodeExecutionToolResultBlockContent,
CodeExecutionToolResultBlockParam,
CodeExecutionToolResultBlockParamContent,
CodeExecutionToolResultError,
CodeExecutionToolResultErrorCode,
CodeExecutionToolResultErrorParam,
Container,
ContainerUploadBlock,
ContainerUploadBlockParam,
ContentBlock,
ContentBlockDeltaEvent,
ContentBlockParam,
ContentBlockStartEvent,
ContentBlockStopEvent,
ContentBlockSource,
ContentBlockSourceContent,
DirectCaller,
DocumentBlock,
DocumentBlockParam,
EncryptedCodeExecutionResultBlock,
EncryptedCodeExecutionResultBlockParam,
ImageBlockParam,
InputJSONDelta,
JSONOutputFormat,
MemoryTool20250818,
Message,
MessageStreamParams,
MessageCountTokensParams,
MessageCountTokensTool,
MessageCreateParams,
MessageCreateParamsNonStreaming,
MessageCreateParamsStreaming,
MessageDeltaEvent,
MessageDeltaUsage,
MessageParam,
MessageStartEvent,
MessageStopEvent,
MessageStreamEvent,
MessageTokensCount,
Messages,
Metadata,
MidConversationSystemBlockParam,
Model,
OutputConfig,
OutputTokensDetails,
PlainTextSource,
RawContentBlockDelta,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
RawMessageDeltaEvent,
RawMessageStartEvent,
RawMessageStopEvent,
RawMessageStreamEvent,
RedactedThinkingBlock,
RedactedThinkingBlockParam,
RefusalStopDetails,
SearchResultBlockParam,
ServerToolCaller,
ServerToolCaller20260120,
ServerToolUsage,
ServerToolUseBlock,
ServerToolUseBlockParam,
SignatureDelta,
StopReason,
TextBlock,
TextBlockParam,
TextCitation,
TextCitationParam,
TextDelta,
TextEditorCodeExecutionCreateResultBlock,
TextEditorCodeExecutionCreateResultBlockParam,
TextEditorCodeExecutionStrReplaceResultBlock,
TextEditorCodeExecutionStrReplaceResultBlockParam,
TextEditorCodeExecutionToolResultBlock,
TextEditorCodeExecutionToolResultBlockParam,
TextEditorCodeExecutionToolResultError,
TextEditorCodeExecutionToolResultErrorCode,
TextEditorCodeExecutionToolResultErrorParam,
TextEditorCodeExecutionViewResultBlock,
TextEditorCodeExecutionViewResultBlockParam,
ThinkingBlock,
ThinkingBlockParam,
ThinkingConfigAdaptive,
ThinkingConfigDisabled,
ThinkingConfigEnabled,
ThinkingConfigParam,
ThinkingDelta,
Tool,
ToolBash20250124,
ToolChoice,
ToolChoiceAny,
ToolChoiceAuto,
ToolChoiceNone,
ToolChoiceTool,
ToolReferenceBlock,
ToolReferenceBlockParam,
ToolResultBlockParam,
ToolSearchToolBm25_20251119,
ToolSearchToolRegex20251119,
ToolSearchToolResultBlock,
ToolSearchToolResultBlockParam,
ToolSearchToolResultError,
ToolSearchToolResultErrorCode,
ToolSearchToolResultErrorParam,
ToolSearchToolSearchResultBlock,
ToolSearchToolSearchResultBlockParam,
ToolTextEditor20250124,
ToolTextEditor20250429,
ToolTextEditor20250728,
ToolUnion,
ToolUseBlock,
ToolUseBlockParam,
URLImageSource,
URLPDFSource,
Usage,
UserLocation,
WebFetchBlock,
WebFetchBlockParam,
WebFetchTool20250910,
WebFetchTool20260209,
WebFetchTool20260309,
WebFetchTool20260318,
WebFetchToolResultBlock,
WebFetchToolResultBlockParam,
WebFetchToolResultErrorBlock,
WebFetchToolResultErrorBlockParam,
WebFetchToolResultErrorCode,
WebSearchResultBlock,
WebSearchResultBlockParam,
WebSearchTool20250305,
WebSearchTool20260209,
WebSearchTool20260318,
WebSearchToolRequestError,
WebSearchToolResultBlock,
WebSearchToolResultBlockContent,
WebSearchToolResultBlockParam,
WebSearchToolResultBlockParamContent,
WebSearchToolResultError,
WebSearchToolResultErrorCode,
} from './resources/messages/messages';
import { type Fetch } from './internal/builtin-types';
import { isRunningInBrowser } from './internal/detect-platform';
import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers';
import { FinalRequestOptions, RequestOptions } from './internal/request-options';
import { readEnv } from './internal/utils/env';
import {
type LogLevel,
type Logger,
defaultLogLevel,
formatRequestDetails,
loggerFor,
parseLogLevel,
} from './internal/utils/log';
import { isEmptyObj } from './internal/utils/values';
/**
* Shared auth state. A `withOptions()` clone receives the parent's instance
* (unless the caller overrides auth options) so a clone created before lazy
* resolution settles observes the same provider/tokenCache/error/extraHeaders
* as the parent rather than starting an independent resolution.
*/
type AuthState = {
provider: AccessTokenProvider | null;
tokenCache: TokenCache | null;
resolution: Promise<void> | null;
error: unknown;
extraHeaders: Record<string, string>;
/**
* `base_url` from the resolved profile/config, normalized (no trailing
* slash). Stored on the shared auth state so `withOptions()` clones created
* before lazy resolution settles can still adopt it on their first request.
*/
baseURL?: string | undefined;
};
/**
* Per-request auth flags, keyed by the FinalRequestOptions object so
* caller-owned options aren't mutated.
*/
type RequestAuthFlags = {
usedTokenCache: boolean;
didRefreshFor401: boolean;
};
type InternalClientOptions = ClientOptions & {
__auth?: AuthState | undefined;
__baseURLIsExplicit?: boolean | undefined;
};
export type ApiKeySetter = () => Promise<string>;
export interface ClientOptions {
/**
* API key used for authentication.
*
* - Accepts either a static string or an async function that resolves to a string.
* - Defaults to process.env['ANTHROPIC_API_KEY'].
* - When a function is provided, it is invoked before each request so you can rotate
* or refresh credentials at runtime.
* - The function must return a non-empty string; otherwise an AnthropicError is thrown.
* - If the function throws, the error is wrapped in an AnthropicError with the original
* error available as `cause`.
*/
apiKey?: string | ApiKeySetter | null | undefined;
/**
* Defaults to process.env['ANTHROPIC_AUTH_TOKEN'].
*/
authToken?: string | null | undefined;
/**
* An {@link AccessTokenProvider} for OAuth/workload-identity authentication.
*
* When set, the provider is wrapped in a {@link TokenCache} and used for
* Bearer token auth on every request. Takes precedence over `authToken`
* but not `apiKey`.
*
* If omitted (and no `apiKey` or `authToken` is provided), the client
* automatically resolves credentials from config files or environment
* variables on the first request.
*/
credentials?: AccessTokenProvider | null | undefined;
/**
* An {@link AnthropicConfig} object to resolve credentials from directly,
* bypassing config-file and environment-variable lookup. This is the
* TypeScript equivalent of Go's `option.WithConfig(cfg)`.
*
* Ignored when `credentials` is set. For `oidc_federation`, the SDK
* performs the jwt-bearer exchange in-process; for `user_oauth`,
* `authentication.credentials_path` must point at the credentials file.
*/
config?: AnthropicConfig | null | undefined;
/**
* Name of a profile to load from `<config_dir>/configs/<profile>.json`.
*
* Equivalent to setting the `ANTHROPIC_PROFILE` environment variable, but
* scoped to this client instance. As an explicit constructor argument it
* takes precedence over `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` in the
* environment. Mutually exclusive with `credentials` and `config`.
*/
profile?: string | null | undefined;
/**
* Defaults to process.env['ANTHROPIC_WEBHOOK_SIGNING_KEY'].
*/
webhookKey?: string | null | undefined;
/**
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
*
* Defaults to process.env['ANTHROPIC_BASE_URL'].
*/
baseURL?: string | null | undefined;
/**
* The maximum amount of time (in milliseconds) that the client should wait for a response
* from the server before timing out a single request.
*
* Note that request timeouts are retried by default, so in a worst-case scenario you may wait
* much longer than this timeout before the promise succeeds or fails.
*
* @unit milliseconds
*/
timeout?: number | undefined;
/**
* Additional `RequestInit` options to be passed to `fetch` calls.
* Properties will be overridden by per-request `fetchOptions`.
*/
fetchOptions?: MergedRequestInit | undefined;
/**
* Specify a custom `fetch` function implementation.
*
* If not provided, we expect that `fetch` is defined globally.
*/
fetch?: Fetch | undefined;
/**
* {@link Middleware} functions that wrap every HTTP request made by the
* client.
*
* Middleware runs per HTTP attempt, including retries. It observes the
* canonical Anthropic-shaped request and response on every backend: on
* clients for third-party backends (Bedrock, Vertex, Foundry), the
* backend's URL/body rewriting, request signing, and response
* normalization happen inside `next`.
*/
middleware?: ReadonlyArray<Middleware> | undefined;
/**
* The maximum number of times that the client will retry a request in case of a
* temporary failure, like a network error or a 5XX error from the server.
*
* @default 2
*/
maxRetries?: number | undefined;
/**
* Default headers to include with every request to the API.
*
* These can be removed in individual requests by explicitly setting the
* header to `null` in request options.
*/
defaultHeaders?: HeadersLike | undefined;
/**
* Default query parameters to include with every request to the API.
*
* These can be removed in individual requests by explicitly setting the
* param to `undefined` in request options.
*/
defaultQuery?: Record<string, string | undefined> | undefined;
/**
* By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
* Only set this option to `true` if you understand the risks and have appropriate mitigations in place.
*/
dangerouslyAllowBrowser?: boolean | undefined;
/**
* Set the log level.
*
* Defaults to process.env['ANTHROPIC_LOG'] or 'warn' if it isn't set.
*/
logLevel?: LogLevel | undefined;
/**
* Set the logger.
*
* Defaults to globalThis.console.
*/
logger?: Logger | undefined;
}
export const HUMAN_PROMPT = '\\n\\nHuman:';
export const AI_PROMPT = '\\n\\nAssistant:';
/**
* Base class for Anthropic API clients.
*/
export class BaseAnthropic {
apiKey: string | null;
authToken: string | null;
webhookKey: string | null;
/**
* The active credential provider. Default credential resolution runs once
* at construction time. If it fails, the error is surfaced on every
* request and the client must be reconstructed — there is no retry path.
*
* Clones returned by {@link withOptions} share the parent's auth state
* (provider, token cache, pending resolution, and any resolution error)
* unless the caller passes an explicit `apiKey`, `authToken`,
* `credentials`, `config`, or `profile` override.
*/
get credentials(): AccessTokenProvider | null {
return this._authState.provider;
}
private _authState: AuthState;
/**
* Whether `baseURL` was chosen by the caller (constructor arg or env var)
* rather than derived. Non-explicit base URLs may be replaced — by a
* profile-supplied host, or by re-derivation in `withOptions()` clones.
* Subclasses that derive their own base URL should correct this after
* `super()`, since the base constructor can't tell a derived value apart.
*/
protected _baseURLIsExplicit: boolean;
private _requestAuthFlags = new WeakMap<FinalRequestOptions, RequestAuthFlags>();
baseURL: string;
maxRetries: number;
timeout: number;
logger: Logger;
logLevel: LogLevel | undefined;
fetchOptions: MergedRequestInit | undefined;
middleware: ReadonlyArray<Middleware>;
private fetch: Fetch;
#encoder: Opts.RequestEncoder;
protected idempotencyHeader?: string;
protected _options: ClientOptions;
/**
* API Client for interfacing with the Anthropic API.
*
* @param {string | null | undefined} [opts.apiKey=process.env['ANTHROPIC_API_KEY'] ?? null]
* @param {string | null | undefined} [opts.authToken=process.env['ANTHROPIC_AUTH_TOKEN'] ?? null]
* @param {string | null | undefined} [opts.webhookKey=process.env['ANTHROPIC_WEBHOOK_SIGNING_KEY'] ?? null]
* @param {string} [opts.baseURL=process.env['ANTHROPIC_BASE_URL'] ?? https://api.anthropic.com] - Override the default base URL for the API.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor({
baseURL = readEnv('ANTHROPIC_BASE_URL'),
apiKey,
authToken,
webhookKey = readEnv('ANTHROPIC_WEBHOOK_SIGNING_KEY') ?? null,
...opts
}: ClientOptions = {}) {
// An explicit `profile` is a constructor-level credential choice; when set,
// do not let env ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN shadow it.
if (apiKey === undefined) {
apiKey = opts.profile != null ? null : readEnv('ANTHROPIC_API_KEY') ?? null;
}
if (authToken === undefined) {
authToken = opts.profile != null ? null : readEnv('ANTHROPIC_AUTH_TOKEN') ?? null;
}
if (opts.profile != null && (opts.credentials != null || opts.config != null)) {
throw new TypeError('Pass at most one of `profile`, `credentials`, or `config`.');
}
const options: ClientOptions = {
apiKey,
authToken,
webhookKey,
...opts,
baseURL: baseURL || `https://api.anthropic.com`,
};
if (!options.dangerouslyAllowBrowser && isRunningInBrowser()) {
throw new Errors.AnthropicError(
"It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew Anthropic({ apiKey, dangerouslyAllowBrowser: true });\n",
);
}
this.baseURL = options.baseURL!;
// After destructuring, `baseURL` is the constructor arg or
// ANTHROPIC_BASE_URL — both count as an explicit choice that a profile
// base_url must not override. A falsy value means we fell through to the
// hardcoded default above and a profile may supply the host. withOptions()
// propagates the parent's flag via __baseURLIsExplicit so a non-overriding
// clone doesn't mistake the inherited baseURL for a caller-supplied one.
this._baseURLIsExplicit = (opts as InternalClientOptions).__baseURLIsExplicit ?? !!baseURL;
this.timeout = options.timeout ?? BaseAnthropic.DEFAULT_TIMEOUT /* 10 minutes */;
this.logger = options.logger ?? console;
// Set default logLevel early so that we can log a warning in parseLogLevel.
this.logLevel = defaultLogLevel;
this.logLevel =
parseLogLevel(options.logLevel, 'ClientOptions.logLevel', loggerFor(this)) ??
parseLogLevel(readEnv('ANTHROPIC_LOG'), "process.env['ANTHROPIC_LOG']", loggerFor(this)) ??
defaultLogLevel;
this.fetchOptions = options.fetchOptions;
this.maxRetries = options.maxRetries ?? 2;
this.fetch = options.fetch ?? Shims.getDefaultFetch();
this.#encoder = Opts.FallbackEncoder;
this.middleware = [...(options.middleware ?? [])];
const customHeadersEnv = readEnv('ANTHROPIC_CUSTOM_HEADERS');
if (customHeadersEnv) {
const parsed: Record<string, string> = {};
for (const line of customHeadersEnv.split('\n')) {
const colon = line.indexOf(':');
if (colon >= 0) {
parsed[line.substring(0, colon).trim()] = line.substring(colon + 1).trim();
}
}
options.defaultHeaders = { ...parsed, ...options.defaultHeaders };
}
const inherited = (opts as InternalClientOptions).__auth;
// Never persist the internal __auth handle on _options — it's a
// one-shot constructor signal, and leaking it through _options would
// cause withOptions() to spread a stale value into clones.
delete (options as InternalClientOptions).__auth;
delete (options as InternalClientOptions).__baseURLIsExplicit;
this._options = options;
this.apiKey = typeof apiKey === 'string' ? apiKey : null;
this.authToken = authToken;
this.webhookKey = webhookKey;
if (inherited) {
this._authState = inherited;
if (!this._baseURLIsExplicit && inherited.baseURL) {
this.baseURL = inherited.baseURL;
}
} else {
this._authState = { provider: null, tokenCache: null, resolution: null, error: null, extraHeaders: {} };
// apiKey/authToken win over credentials/config/profile; don't build a
// token cache or resolve a config that the request path will then ignore.
if (this.apiKey == null && this.authToken == null) {
const credentials = options.credentials ?? null;
if (credentials) {
this._authState.provider = credentials;
this._authState.tokenCache = this._makeTokenCache(credentials);
} else if (options.config != null) {
const result = resolveCredentialsFromConfig(options.config, this._credentialResolverOptions());
this._authState.provider = result.provider;
this._authState.tokenCache = this._makeTokenCache(result.provider);
this._authState.extraHeaders = result.extraHeaders;
this._applyCredentialBaseURL(result.baseURL);
} else if (options.profile != null) {
this._authState.resolution = this._resolveDefaultCredentials(options.profile);
} else if (this._shouldResolveDefaultCredentials()) {
// No explicit auth provided — lazily resolve from the credential
// chain on first request. Errors are captured into _auth.error and
// surfaced on first use rather than as an unhandled rejection.
this._authState.resolution = this._resolveDefaultCredentials();
}
}
}
}
/**
* Whether to lazily resolve auth from the default credential chain when no
* explicit auth is configured. Called once from the constructor, so
* overrides must not depend on subclass instance state. Subclasses that
* bring their own auth scheme return false so unrelated local credentials
* are never resolved or allowed to supply a base URL.
*/
protected _shouldResolveDefaultCredentials(): boolean {
return true;
}
/**
* Stores a profile/config-supplied base URL on the shared auth state and, if
* the caller did not pin `baseURL` via constructor option or env, adopts it
* as this client's outbound API host. Precedence: ctor opt > env > profile >
* hardcoded default.
*/
private _applyCredentialBaseURL(baseURL: string | undefined): void {
if (!baseURL) return;
const normalized = baseURL.replace(/\/+$/, '');
this._authState.baseURL = normalized;
if (!this._baseURLIsExplicit) {
this.baseURL = normalized;
}
}
/**
* Options bag passed into the credential chain. `baseURL` here is only the
* fallback host for the token-exchange POST when the config itself omits
* `base_url`; the chain returns the config's own `base_url` (if any) on
* {@link CredentialResult.baseURL}, which {@link _applyCredentialBaseURL}
* then adopts for outbound API requests. The two are deliberately decoupled
* so this fallback never round-trips into precedence.
*/
private _credentialResolverOptions() {
return {
baseURL: this.baseURL,
fetch: this._credentialsFetch(),
userAgent: this.getUserAgent(),
onCacheWriteError: (err: unknown) => {
loggerFor(this).debug('credential cache write failed (best-effort)', err);
},
onSafetyWarning: (msg: string) => {
loggerFor(this).warn(msg);
},
};
}
/**
* A `Fetch` for first-party credential token-exchange requests (OIDC
* federation jwt-bearer grants, user-OAuth refresh grants) that routes
* through this client's middleware chain, so middleware observes token
* traffic like any other request. Only client-level middleware applies:
* a minted token is shared across requests, so attributing the exchange
* to any one request's per-request middleware would be arbitrary. For the
* same reason, `ctx.options` is undefined for these requests.
*/
private _credentialsFetch(): Fetch {
return wrapFetchWithMiddleware(this.fetch, this.middleware, undefined, this);
}
private _makeTokenCache(provider: AccessTokenProvider): TokenCache {
return new TokenCache(provider, (err) => {
loggerFor(this).debug('advisory token refresh failed; serving cached token', err);
});
}
/**
* Create a new client instance re-using the same options given to the current client with optional overriding.
*/
withOptions(options: Partial<ClientOptions>): this {
// Share the auth state object unless the caller passes any auth-related
// key. The `in` check is intentional: even `apiKey: undefined` opts the
// clone out of sharing (it gets its own _auth and TokenCache, though it
// may still wrap the parent's provider via the credentials spread below).
const overridesStructuredAuth = 'credentials' in options || 'config' in options || 'profile' in options;
const overridesAuth = 'apiKey' in options || 'authToken' in options || overridesStructuredAuth;
const internal: InternalClientOptions = {
...this._options,
// Only forward baseURL when the caller (or env) explicitly chose it.
// For a non-explicit parent, this.baseURL may have been mutated to the
// profile-resolved host; pinning that as the clone's options.baseURL
// would make _options on the clone misreport caller intent and would
// leave the clone stuck on the parent's host across an auth override.
// The clone instead receives the construction-time value via
// ...this._options above and re-adopts the profile host through the
// shared _authState.baseURL + __baseURLIsExplicit=false path.
...(this._baseURLIsExplicit ? { baseURL: this.baseURL } : {}),
maxRetries: this.maxRetries,
timeout: this.timeout,
logger: this.logger,
logLevel: this.logLevel,
fetch: this.fetch,
fetchOptions: this.fetchOptions,
middleware: this.middleware,
apiKey: this.apiKey,
authToken: this.authToken,
webhookKey: this.webhookKey,
// credentials: this.credentials is a no-op when __auth is shared (the
// ctor takes the inherited path and ignores options.credentials); when
// overridesAuth is true via apiKey/authToken only, it lets the clone
// build a fresh TokenCache around the parent's provider.
credentials: this.credentials,
// When the caller passes a structured-credential override, drop inherited
// structured-credential options so only `...options` supplies them —
// otherwise an inherited `credentials`/`config`/`profile` would trip the
// mutual-exclusion check or precedence over the override.
...(overridesStructuredAuth ? { credentials: undefined, config: undefined, profile: undefined } : {}),
...options,
// Always set __auth so any stale value from ...this._options is
// overwritten. undefined means "build fresh auth from these options".
__auth: overridesAuth ? undefined : this._authState,
__baseURLIsExplicit: 'baseURL' in options ? true : this._baseURLIsExplicit,
};
return new (this.constructor as any as new (props: ClientOptions) => typeof this)(internal);
}
/**
* Lazily resolves credentials from config files or environment variables.
* Called once from the constructor when no explicit auth is provided, or
* when an explicit `profile` was passed (in which case a missing/unresolved
* profile is surfaced as an error instead of falling through to "no auth").
* The returned promise is stored and awaited on the first request.
*/
private async _resolveDefaultCredentials(profile?: string): Promise<void> {
try {
const result = await defaultCredentials(this._credentialResolverOptions(), profile);
if (result) {
this._authState.provider = result.provider;
this._authState.tokenCache = this._makeTokenCache(result.provider);
this._authState.extraHeaders = result.extraHeaders;
this._applyCredentialBaseURL(result.baseURL);
} else if (profile != null) {
throw new Errors.AnthropicError(
`Profile "${profile}" could not be resolved (no <config_dir>/configs/${profile}.json found).`,
);
}
} catch (err) {
this._authState.error = err;
} finally {
this._authState.resolution = null;
}
}
/**
* Check whether the base URL is set to its default.
*
* A profile-supplied `base_url` counts as an override here: a profile that
* pins a non-default host is declaring "this whole client targets deployment
* X", so per-endpoint {@link RequestOptions.defaultBaseURL} hints must not
* silently route individual calls back to production. No generated resource
* currently sets `defaultBaseURL`, so this is documenting intent for when
* one does.
*/
#baseURLOverridden(): boolean {
return this.baseURL !== 'https://api.anthropic.com';
}
protected defaultQuery(): Record<string, string | undefined> | undefined {
return this._options.defaultQuery;
}
protected validateHeaders({ values, nulls }: NullableHeaders) {
if (values.get('x-api-key') || values.get('authorization')) {
return;
}
if (this._authState.error) {
throw this._authState.error;
}
if (this._authState.tokenCache || this._authState.resolution) {
return; // auth will be injected per-request via authHeaders
}
if (this.apiKey && values.get('x-api-key')) {
return;
}
if (nulls.has('x-api-key')) {
return;
}
if (this.authToken && values.get('authorization')) {
return;
}
if (nulls.has('authorization')) {
return;
}
throw new Error(
'Could not resolve authentication method. Expected one of apiKey, authToken, credentials, config, or profile to be set. Or for one of the "X-Api-Key" or "Authorization" headers to be explicitly omitted',
);
}
private _authFlags(opts: FinalRequestOptions): RequestAuthFlags {
let flags = this._requestAuthFlags.get(opts);
if (!flags) {
flags = { usedTokenCache: false, didRefreshFor401: false };
this._requestAuthFlags.set(opts, flags);
}
return flags;
}
protected async authHeaders(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
// Wait for lazy credential resolution if it's in progress. If it failed,
// return no auth headers — validateHeaders surfaces the stored error
// after the explicit-header escape hatch has had a chance to apply.
if (this._authState.resolution) {
await this._authState.resolution;
}
if (this._authState.error) {
return undefined;
}
// If we have a token cache and no API key is set, use token auth
if (this._authState.tokenCache && this.apiKey == null) {
const token = await this._authState.tokenCache.getToken();
this._authFlags(opts).usedTokenCache = true;
return buildHeaders([{ Authorization: `Bearer ${token}` }]);
}
return buildHeaders([await this.apiKeyAuth(opts), await this.bearerAuth(opts)]);
}
protected async apiKeyAuth(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
if (this.apiKey == null) {
return undefined;
}
return buildHeaders([{ 'X-Api-Key': this.apiKey }]);
}
protected async bearerAuth(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
if (this.authToken == null) {
return undefined;
}
return buildHeaders([{ Authorization: `Bearer ${this.authToken}` }]);
}
protected stringifyQuery(query: object | Record<string, unknown>): string {
return stringifyQuery(query);
}
private getUserAgent(): string {
return `${this.constructor.name}/JS ${VERSION}`;
}
protected defaultIdempotencyKey(): string {
return `stainless-node-retry-${uuid4()}`;
}
protected makeStatusError(
status: number,
error: Object,
message: string | undefined,
headers: Headers,
): Errors.APIError {
return Errors.APIError.generate(status, error, message, headers);
}
buildURL(
path: string,
query: Record<string, unknown> | null | undefined,
defaultBaseURL?: string | undefined,
): string {
const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL;
const url =
isAbsoluteURL(path) ?
new URL(path)
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
const defaultQuery = this.defaultQuery();
const pathQuery = Object.fromEntries(url.searchParams);
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
query = { ...pathQuery, ...defaultQuery, ...query };
}
if (typeof query === 'object' && query && !Array.isArray(query)) {
url.search = this.stringifyQuery(query);
}
return url.toString();
}
_calculateNonstreamingTimeout(maxTokens: number): number {
const defaultTimeout = 10 * 60;
const expectedTimeout = (60 * 60 * maxTokens) / 128_000;
if (expectedTimeout > defaultTimeout) {
throw new Errors.AnthropicError(
'Streaming is required for operations that may take longer than 10 minutes. ' +
'See https://github.com/anthropics/anthropic-sdk-typescript#streaming-responses for more details',
);
}
return defaultTimeout * 1000;
}
/**
* Used as a callback for mutating the given `FinalRequestOptions` object.
*/
protected async prepareOptions(options: FinalRequestOptions): Promise<void> {}
/**
* Used as a callback for mutating the given `RequestInit` object.
*
* This is useful for cases where you want to add certain headers based off of
* the request properties, e.g. `method` or `url`.
*
* Runs after all middleware (including {@link backendMiddleware}),
* immediately before each underlying fetch call, so it sees exactly what
* goes over the wire. Middleware may replay a request by calling `next()`
* more than once, so this hook can run multiple times per attempt:
* overrides must be idempotent and overwrite headers from a previous
* invocation rather than append to them.
*/
protected async prepareRequest(
request: RequestInit,
{ url, options }: { url: string; options: FinalRequestOptions },
): Promise<void> {
// Append auth-derived headers when using token auth. Done here (after all
// header merging) rather than in authHeaders() so we append to any existing
// anthropic-beta values instead of being overwritten by later header sources.
if (this._authState.tokenCache && this.apiKey == null) {
// Normalize to a Headers instance — custom fetch impls or polyfills can
// hand back arrays / plain objects, and silently dropping the beta
// header in that case would surface as a confusing server-side 4xx.
const headers = request.headers instanceof Headers ? request.headers : new Headers(request.headers);
for (const [k, v] of Object.entries(this._authState.extraHeaders)) {
if (!headers.has(k)) headers.set(k, v);
}
const existing = headers
.get('anthropic-beta')
?.split(',')
.map((s) => s.trim());
if (!existing?.includes(OAUTH_API_BETA_HEADER)) {
headers.append('anthropic-beta', OAUTH_API_BETA_HEADER);
}
request.headers = headers;
}
}
/**
* Internal {@link Middleware} composed innermost in the chain — inside both
* client-level and per-request middleware, immediately around the underlying
* `fetch`. Subclasses for third-party backends override this to adapt the
* canonical Anthropic-shaped request to the backend's wire shape (URL/body
* rewriting, request signing) and to normalize the wire response back to the
* canonical shape (e.g. AWS EventStream to SSE).
*
* Running inside the user's middleware means user middleware always observes
* canonical Anthropic-shaped traffic, and the adaptation re-runs (e.g.
* re-signs) on every `next()` invocation, covering whatever the middleware
* mutated.
*
* Errors thrown here follow the middleware error policy: they propagate to
* the caller as-is — no retries, no `APIConnectionError` wrapping — unless
* retryable (see {@link Middleware}); throw a `RetryableError` to opt into
* the retry path.
*/
protected backendMiddleware(): ReadonlyArray<Middleware> {
return [];
}
get<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('get', path, opts);
}
post<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('post', path, opts);
}
patch<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('patch', path, opts);
}
put<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('put', path, opts);
}
delete<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('delete', path, opts);
}
private methodRequest<Rsp>(
method: HTTPMethod,
path: string,
opts?: PromiseOrValue<RequestOptions>,
): APIPromise<Rsp> {
return this.request(
Promise.resolve(opts).then((opts) => {
return { method, path, ...opts };
}),
);
}
request<Rsp>(
options: PromiseOrValue<FinalRequestOptions>,
remainingRetries: number | null = null,
): APIPromise<Rsp> {
return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined));
}
private async makeRequest(
optionsInput: PromiseOrValue<FinalRequestOptions>,
retriesRemaining: number | null,
retryOfRequestLogID: string | undefined,
): Promise<APIResponseProps> {
const options = await optionsInput;
const maxRetries = options.maxRetries ?? this.maxRetries;
if (retriesRemaining == null) {
retriesRemaining = maxRetries;
// Top-level call: reset per-request auth flags so a reused options object
// (via client.request(opts)) doesn't carry stale 401-refresh state.
this._requestAuthFlags.delete(options);
}
await this.prepareOptions(options);
const { req, url, timeout } = await this.buildRequest(options, {
retryCount: maxRetries - retriesRemaining,
});
/** Not an API request ID, just for correlating local log entries. */
const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0');
const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`;
const startTime = Date.now();
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
const controller = new AbortController();
const response = await this.fetchWithTimeout(url, req, timeout, controller, options, {
requestLogID,
retryOfRequestLogID,
}).catch(castToError);
const headersTime = Date.now();
if (response instanceof globalThis.Error) {
releaseRequestSignal(controller);
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
// detect native connection timeout errors
// deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)"
// undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)"
// others do not provide enough information to distinguish timeouts from other connection errors
const isTimeout =
isAbortError(response) ||
/timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : ''));
// Errors thrown by middleware (user middleware and the backend adaptation
// alike) propagate to the caller as-is — no retries, no APIConnectionError
// wrapping — except retryable errors (timeouts/aborts, APIConnectionErrors,
// and RetryableErrors, directly or in the `cause` chain), which stay on the
// retry path.
const hasMiddleware =
this.middleware.length > 0 || !!options.middleware?.length || this.backendMiddleware().length > 0;
if (hasMiddleware && !isTimeout && !isRetryableError(response)) {
loggerFor(this).info(`[${requestLogID}] middleware error (not retryable)`);
loggerFor(this).debug(
`[${requestLogID}] middleware error (not retryable)`,
formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}),
);
throw response;
}
if (retriesRemaining) {
loggerFor(this).info(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`,
);
loggerFor(this).debug(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}),
);
return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID);
}
loggerFor(this).info(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`,
);
loggerFor(this).debug(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`,
formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}),
);
if (isTimeout) {
throw new Errors.APIConnectionTimeoutError();
}
// a retryable middleware-origin error is still the caller's error: once retries are
// exhausted it propagates as-is rather than wrapped in APIConnectionError
if (hasMiddleware && !isFetchOriginError(response)) {
throw response;
}
throw new Errors.APIConnectionError({ cause: response });
}
const specialHeaders = [...response.headers.entries()]
.filter(([name]) => name === 'request-id')
.map(([name, value]) => ', ' + name + ': ' + JSON.stringify(value))
.join('');
const responseInfo = `[${requestLogID}${retryLogStr}${specialHeaders}] ${req.method} ${url} ${
response.ok ? 'succeeded' : 'failed'
} with status ${response.status} in ${headersTime - startTime}ms`;
if (!response.ok) {
const shouldRetry = await this.shouldRetry(response, options);
if (retriesRemaining && shouldRetry) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
// We don't need the body of this response.
await Shims.CancelReadableStream(response.body);
releaseRequestSignal(controller);
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
loggerFor(this).debug(
`[${requestLogID}] response error (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}),
);
return this.retryRequest(
options,
retriesRemaining,
retryOfRequestLogID ?? requestLogID,
response.headers,
);
}
const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`;
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
const errText = await response.text().catch((err: any) => castToError(err).message);
const errJSON = safeJSON(errText) as any;
const errMessage = errJSON ? undefined : errText;
loggerFor(this).debug(
`[${requestLogID}] response error (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
message: errMessage,
durationMs: Date.now() - startTime,
}),
);
releaseRequestSignal(controller);
const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers);
throw err;
}
loggerFor(this).info(responseInfo);
loggerFor(this).debug(
`[${requestLogID}] response start`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}),
);
armAbandonmentBackstop(response.body ?? response, controller);
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
}
getAPIList<Item, PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>>(
path: string,
Page: new (...args: any[]) => PageClass,
opts?: PromiseOrValue<RequestOptions>,
): Pagination.PagePromise<PageClass, Item> {
return this.requestAPIList(
Page,
opts && 'then' in opts ?
opts.then((opts) => ({ method: 'get', path, ...opts }))
: { method: 'get', path, ...opts },
);
}
requestAPIList<
Item = unknown,
PageClass extends Pagination.AbstractPage<Item> = Pagination.AbstractPage<Item>,
>(
Page: new (...args: ConstructorParameters<typeof Pagination.AbstractPage>) => PageClass,
options: PromiseOrValue<FinalRequestOptions>,
): Pagination.PagePromise<PageClass, Item> {
const request = this.makeRequest(options, null, undefined);
return new Pagination.PagePromise<PageClass, Item>(this as any as Anthropic, request, Page);
}
async fetchWithTimeout(
url: RequestInfo,
init: RequestInit | undefined,
ms: number,
controller: AbortController,
requestOptions?: FinalRequestOptions | undefined,
logCtx?: { requestLogID: string; retryOfRequestLogID?: string | undefined } | undefined,
): Promise<Response> {
const { signal, method, ...options } = init || {};
// Avoid creating a closure over `this`, `init`, or `options` to prevent memory leaks.
// An arrow function like `() => controller.abort()` captures the surrounding scope,
// which includes the request body and other large objects. When the user passes a
// long-lived AbortSignal, the listener prevents those objects from being GC'd for
// the lifetime of the signal. Using `.bind()` only retains a reference to the
// controller itself.
const abort = this._makeAbort(controller);
if (signal) {
signal.addEventListener('abort', abort, { once: true });
registerRequestSignalCleanup(controller, signal, abort);
}
const isReadableBody =
((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) ||
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);
const fetchOptions: RequestInit = {
signal: controller.signal as any,
...(isReadableBody ? { duplex: 'half' } : {}),
method: 'GET',
...options,
};
if (method) {
// Custom methods like 'patch' need to be uppercased
// See https://github.com/nodejs/undici/issues/2294
fetchOptions.method = method.toUpperCase();
}
// Arm the timeout around the underlying fetch only, not the middleware
// chain — middleware can take arbitrarily long (or call `next` more than
// once), and each inner-fetch invocation gets its own `ms` timer.
const baseFetch = this.fetch;
const timedFetch: Fetch = async (innerUrl, innerInit) => {
const timeout = setTimeout(abort, ms);
try {
return await baseFetch.call(undefined, innerUrl, innerInit);
} finally {
clearTimeout(timeout);
}
};
// Prepare the request (auth signing and other `prepareRequest` hooks) as
// the innermost step, after any middleware — including the backend
// middleware, so it sees exactly what goes over the wire. Runs per
// inner-fetch invocation, so a request middleware rewrote — or replayed
// via a second `next()` call — is prepared fresh each time. Preparation is
// outside the timeout timer, matching its pre-middleware behavior.
const innerFetch: Fetch =
requestOptions === undefined ? timedFetch : (
async (innerUrl, inner