UNPKG

@anthropic-ai/sdk

Version:
1,294 lines (1,198 loc) 75.3 kB
// 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