UNPKG

@anthropic-ai/sdk

Version:

The official TypeScript library for the Anthropic API

550 lines 28 kB
"use strict"; // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. var _BaseAnthropic_instances, _a, _BaseAnthropic_encoder, _BaseAnthropic_baseURLOverridden; Object.defineProperty(exports, "__esModule", { value: true }); exports.Anthropic = exports.BaseAnthropic = exports.AI_PROMPT = exports.HUMAN_PROMPT = void 0; const tslib_1 = require("./internal/tslib.js"); const uuid_1 = require("./internal/utils/uuid.js"); const values_1 = require("./internal/utils/values.js"); const sleep_1 = require("./internal/utils/sleep.js"); const errors_1 = require("./internal/errors.js"); const detect_platform_1 = require("./internal/detect-platform.js"); const Shims = tslib_1.__importStar(require("./internal/shims.js")); const Opts = tslib_1.__importStar(require("./internal/request-options.js")); const version_1 = require("./version.js"); const Errors = tslib_1.__importStar(require("./core/error.js")); const Pagination = tslib_1.__importStar(require("./core/pagination.js")); const Uploads = tslib_1.__importStar(require("./core/uploads.js")); const API = tslib_1.__importStar(require("./resources/index.js")); const api_promise_1 = require("./core/api-promise.js"); const completions_1 = require("./resources/completions.js"); const models_1 = require("./resources/models.js"); const beta_1 = require("./resources/beta/beta.js"); const messages_1 = require("./resources/messages/messages.js"); const detect_platform_2 = require("./internal/detect-platform.js"); const headers_1 = require("./internal/headers.js"); const env_1 = require("./internal/utils/env.js"); const log_1 = require("./internal/utils/log.js"); const values_2 = require("./internal/utils/values.js"); exports.HUMAN_PROMPT = '\\n\\nHuman:'; exports.AI_PROMPT = '\\n\\nAssistant:'; /** * Base class for Anthropic API clients. */ class BaseAnthropic { /** * 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} [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 = (0, env_1.readEnv)('ANTHROPIC_BASE_URL'), apiKey = (0, env_1.readEnv)('ANTHROPIC_API_KEY') ?? null, authToken = (0, env_1.readEnv)('ANTHROPIC_AUTH_TOKEN') ?? null, ...opts } = {}) { _BaseAnthropic_instances.add(this); _BaseAnthropic_encoder.set(this, void 0); const options = { apiKey, authToken, ...opts, baseURL: baseURL || `https://api.anthropic.com`, }; if (!options.dangerouslyAllowBrowser && (0, detect_platform_2.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; this.timeout = options.timeout ?? _a.DEFAULT_TIMEOUT /* 10 minutes */; this.logger = options.logger ?? console; const defaultLogLevel = 'warn'; // Set default logLevel early so that we can log a warning in parseLogLevel. this.logLevel = defaultLogLevel; this.logLevel = (0, log_1.parseLogLevel)(options.logLevel, 'ClientOptions.logLevel', this) ?? (0, log_1.parseLogLevel)((0, env_1.readEnv)('ANTHROPIC_LOG'), "process.env['ANTHROPIC_LOG']", this) ?? defaultLogLevel; this.fetchOptions = options.fetchOptions; this.maxRetries = options.maxRetries ?? 2; this.fetch = options.fetch ?? Shims.getDefaultFetch(); tslib_1.__classPrivateFieldSet(this, _BaseAnthropic_encoder, Opts.FallbackEncoder, "f"); this._options = options; this.apiKey = typeof apiKey === 'string' ? apiKey : null; this.authToken = authToken; } /** * Create a new client instance re-using the same options given to the current client with optional overriding. */ withOptions(options) { const client = new this.constructor({ ...this._options, baseURL: this.baseURL, maxRetries: this.maxRetries, timeout: this.timeout, logger: this.logger, logLevel: this.logLevel, fetch: this.fetch, fetchOptions: this.fetchOptions, apiKey: this.apiKey, authToken: this.authToken, ...options, }); return client; } defaultQuery() { return this._options.defaultQuery; } validateHeaders({ values, nulls }) { if (values.get('x-api-key') || values.get('authorization')) { return; } 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 either apiKey or authToken to be set. Or for one of the "X-Api-Key" or "Authorization" headers to be explicitly omitted'); } async authHeaders(opts) { return (0, headers_1.buildHeaders)([await this.apiKeyAuth(opts), await this.bearerAuth(opts)]); } async apiKeyAuth(opts) { if (this.apiKey == null) { return undefined; } return (0, headers_1.buildHeaders)([{ 'X-Api-Key': this.apiKey }]); } async bearerAuth(opts) { if (this.authToken == null) { return undefined; } return (0, headers_1.buildHeaders)([{ Authorization: `Bearer ${this.authToken}` }]); } /** * Basic re-implementation of `qs.stringify` for primitive types. */ stringifyQuery(query) { return Object.entries(query) .filter(([_, value]) => typeof value !== 'undefined') .map(([key, value]) => { if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; } if (value === null) { return `${encodeURIComponent(key)}=`; } throw new Errors.AnthropicError(`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`); }) .join('&'); } getUserAgent() { return `${this.constructor.name}/JS ${version_1.VERSION}`; } defaultIdempotencyKey() { return `stainless-node-retry-${(0, uuid_1.uuid4)()}`; } makeStatusError(status, error, message, headers) { return Errors.APIError.generate(status, error, message, headers); } buildURL(path, query, defaultBaseURL) { const baseURL = (!tslib_1.__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL) || this.baseURL; const url = (0, values_1.isAbsoluteURL)(path) ? new URL(path) : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); if (!(0, values_2.isEmptyObj)(defaultQuery)) { query = { ...defaultQuery, ...query }; } if (typeof query === 'object' && query && !Array.isArray(query)) { url.search = this.stringifyQuery(query); } return url.toString(); } _calculateNonstreamingTimeout(maxTokens) { const defaultTimeout = 10 * 60; const expectedTimeout = (60 * 60 * maxTokens) / 128000; 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. */ async prepareOptions(options) { } /** * 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`. */ async prepareRequest(request, { url, options }) { } get(path, opts) { return this.methodRequest('get', path, opts); } post(path, opts) { return this.methodRequest('post', path, opts); } patch(path, opts) { return this.methodRequest('patch', path, opts); } put(path, opts) { return this.methodRequest('put', path, opts); } delete(path, opts) { return this.methodRequest('delete', path, opts); } methodRequest(method, path, opts) { return this.request(Promise.resolve(opts).then((opts) => { return { method, path, ...opts }; })); } request(options, remainingRetries = null) { return new api_promise_1.APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); } async makeRequest(optionsInput, retriesRemaining, retryOfRequestLogID) { const options = await optionsInput; const maxRetries = options.maxRetries ?? this.maxRetries; if (retriesRemaining == null) { retriesRemaining = maxRetries; } await this.prepareOptions(options); const { req, url, timeout } = await this.buildRequest(options, { retryCount: maxRetries - retriesRemaining, }); await this.prepareRequest(req, { url, options }); /** 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(); (0, log_1.loggerFor)(this).debug(`[${requestLogID}] sending request`, (0, log_1.formatRequestDetails)({ retryOfRequestLogID, method: options.method, url, options, headers: req.headers, })); if (options.signal?.aborted) { throw new Errors.APIUserAbortError(); } const controller = new AbortController(); const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(errors_1.castToError); const headersTime = Date.now(); if (response instanceof globalThis.Error) { 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 = (0, errors_1.isAbortError)(response) || /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); if (retriesRemaining) { (0, log_1.loggerFor)(this).info(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`); (0, log_1.loggerFor)(this).debug(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, (0, log_1.formatRequestDetails)({ retryOfRequestLogID, url, durationMs: headersTime - startTime, message: response.message, })); return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); } (0, log_1.loggerFor)(this).info(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`); (0, log_1.loggerFor)(this).debug(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, (0, log_1.formatRequestDetails)({ retryOfRequestLogID, url, durationMs: headersTime - startTime, message: response.message, })); if (isTimeout) { throw new Errors.APIConnectionTimeoutError(); } 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); if (retriesRemaining && shouldRetry) { const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; // We don't need the body of this response. await Shims.CancelReadableStream(response.body); (0, log_1.loggerFor)(this).info(`${responseInfo} - ${retryMessage}`); (0, log_1.loggerFor)(this).debug(`[${requestLogID}] response error (${retryMessage})`, (0, log_1.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`; (0, log_1.loggerFor)(this).info(`${responseInfo} - ${retryMessage}`); const errText = await response.text().catch((err) => (0, errors_1.castToError)(err).message); const errJSON = (0, values_1.safeJSON)(errText); const errMessage = errJSON ? undefined : errText; (0, log_1.loggerFor)(this).debug(`[${requestLogID}] response error (${retryMessage})`, (0, log_1.formatRequestDetails)({ retryOfRequestLogID, url: response.url, status: response.status, headers: response.headers, message: errMessage, durationMs: Date.now() - startTime, })); const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); throw err; } (0, log_1.loggerFor)(this).info(responseInfo); (0, log_1.loggerFor)(this).debug(`[${requestLogID}] response start`, (0, log_1.formatRequestDetails)({ retryOfRequestLogID, url: response.url, status: response.status, headers: response.headers, durationMs: headersTime - startTime, })); return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; } getAPIList(path, Page, opts) { return this.requestAPIList(Page, { method: 'get', path, ...opts }); } requestAPIList(Page, options) { const request = this.makeRequest(options, null, undefined); return new Pagination.PagePromise(this, request, Page); } async fetchWithTimeout(url, init, ms, controller) { const { signal, method, ...options } = init || {}; if (signal) signal.addEventListener('abort', () => controller.abort()); const timeout = setTimeout(() => controller.abort(), ms); const isReadableBody = (globalThis.ReadableStream && options.body instanceof globalThis.ReadableStream) || (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); const fetchOptions = { signal: controller.signal, ...(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(); } try { // use undefined this binding; fetch errors if bound to something else in browser/cloudflare return await this.fetch.call(undefined, url, fetchOptions); } finally { clearTimeout(timeout); } } async shouldRetry(response) { // Note this is not a standard header. const shouldRetryHeader = response.headers.get('x-should-retry'); // If the server explicitly says whether or not to retry, obey. if (shouldRetryHeader === 'true') return true; if (shouldRetryHeader === 'false') return false; // Retry on request timeouts. if (response.status === 408) return true; // Retry on lock timeouts. if (response.status === 409) return true; // Retry on rate limits. if (response.status === 429) return true; // Retry internal errors. if (response.status >= 500) return true; return false; } async retryRequest(options, retriesRemaining, requestLogID, responseHeaders) { let timeoutMillis; // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); if (retryAfterMillisHeader) { const timeoutMs = parseFloat(retryAfterMillisHeader); if (!Number.isNaN(timeoutMs)) { timeoutMillis = timeoutMs; } } // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After const retryAfterHeader = responseHeaders?.get('retry-after'); if (retryAfterHeader && !timeoutMillis) { const timeoutSeconds = parseFloat(retryAfterHeader); if (!Number.isNaN(timeoutSeconds)) { timeoutMillis = timeoutSeconds * 1000; } else { timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); } } // If the API asks us to wait a certain amount of time (and it's a reasonable amount), // just do what it says, but otherwise calculate a default if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } await (0, sleep_1.sleep)(timeoutMillis); return this.makeRequest(options, retriesRemaining - 1, requestLogID); } calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries) { const initialRetryDelay = 0.5; const maxRetryDelay = 8.0; const numRetries = maxRetries - retriesRemaining; // Apply exponential backoff, but not more than the max. const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); // Apply some jitter, take up to at most 25 percent of the retry time. const jitter = 1 - Math.random() * 0.25; return sleepSeconds * jitter * 1000; } calculateNonstreamingTimeout(maxTokens, maxNonstreamingTokens) { const maxTime = 60 * 60 * 1000; // 60 minutes const defaultTime = 60 * 10 * 1000; // 10 minutes const expectedTime = (maxTime * maxTokens) / 128000; if (expectedTime > defaultTime || (maxNonstreamingTokens != null && maxTokens > maxNonstreamingTokens)) { throw new Errors.AnthropicError('Streaming is required for operations that may take longer than 10 minutes. See https://github.com/anthropics/anthropic-sdk-typescript#long-requests for more details'); } return defaultTime; } async buildRequest(inputOptions, { retryCount = 0 } = {}) { const options = { ...inputOptions }; const { method, path, query, defaultBaseURL } = options; const url = this.buildURL(path, query, defaultBaseURL); if ('timeout' in options) (0, values_1.validatePositiveInteger)('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); const req = { method, headers: reqHeaders, ...(options.signal && { signal: options.signal }), ...(globalThis.ReadableStream && body instanceof globalThis.ReadableStream && { duplex: 'half' }), ...(body && { body }), ...(this.fetchOptions ?? {}), ...(options.fetchOptions ?? {}), }; return { req, url, timeout: options.timeout }; } async buildHeaders({ options, method, bodyHeaders, retryCount, }) { let idempotencyHeaders = {}; if (this.idempotencyHeader && method !== 'get') { if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; } const headers = (0, headers_1.buildHeaders)([ idempotencyHeaders, { Accept: 'application/json', 'User-Agent': this.getUserAgent(), 'X-Stainless-Retry-Count': String(retryCount), ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), ...(0, detect_platform_1.getPlatformHeaders)(), ...(this._options.dangerouslyAllowBrowser ? { 'anthropic-dangerous-direct-browser-access': 'true' } : undefined), 'anthropic-version': '2023-06-01', }, await this.authHeaders(options), this._options.defaultHeaders, bodyHeaders, options.headers, ]); this.validateHeaders(headers); return headers.values; } buildBody({ options: { body, headers: rawHeaders } }) { if (!body) { return { bodyHeaders: undefined, body: undefined }; } const headers = (0, headers_1.buildHeaders)([rawHeaders]); if ( // Pass raw type verbatim ArrayBuffer.isView(body) || body instanceof ArrayBuffer || body instanceof DataView || (typeof body === 'string' && // Preserve legacy string encoding behavior for now headers.values.has('content-type')) || // `Blob` is superset of `File` (globalThis.Blob && body instanceof globalThis.Blob) || // `FormData` -> `multipart/form-data` body instanceof FormData || // `URLSearchParams` -> `application/x-www-form-urlencoded` body instanceof URLSearchParams || // Send chunked stream (each chunk has own `length`) (globalThis.ReadableStream && body instanceof globalThis.ReadableStream)) { return { bodyHeaders: undefined, body: body }; } else if (typeof body === 'object' && (Symbol.asyncIterator in body || (Symbol.iterator in body && 'next' in body && typeof body.next === 'function'))) { return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body) }; } else { return tslib_1.__classPrivateFieldGet(this, _BaseAnthropic_encoder, "f").call(this, { body, headers }); } } } exports.BaseAnthropic = BaseAnthropic; _a = BaseAnthropic, _BaseAnthropic_encoder = new WeakMap(), _BaseAnthropic_instances = new WeakSet(), _BaseAnthropic_baseURLOverridden = function _BaseAnthropic_baseURLOverridden() { return this.baseURL !== 'https://api.anthropic.com'; }; BaseAnthropic.Anthropic = _a; BaseAnthropic.HUMAN_PROMPT = exports.HUMAN_PROMPT; BaseAnthropic.AI_PROMPT = exports.AI_PROMPT; BaseAnthropic.DEFAULT_TIMEOUT = 600000; // 10 minutes BaseAnthropic.AnthropicError = Errors.AnthropicError; BaseAnthropic.APIError = Errors.APIError; BaseAnthropic.APIConnectionError = Errors.APIConnectionError; BaseAnthropic.APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; BaseAnthropic.APIUserAbortError = Errors.APIUserAbortError; BaseAnthropic.NotFoundError = Errors.NotFoundError; BaseAnthropic.ConflictError = Errors.ConflictError; BaseAnthropic.RateLimitError = Errors.RateLimitError; BaseAnthropic.BadRequestError = Errors.BadRequestError; BaseAnthropic.AuthenticationError = Errors.AuthenticationError; BaseAnthropic.InternalServerError = Errors.InternalServerError; BaseAnthropic.PermissionDeniedError = Errors.PermissionDeniedError; BaseAnthropic.UnprocessableEntityError = Errors.UnprocessableEntityError; BaseAnthropic.toFile = Uploads.toFile; /** * API Client for interfacing with the Anthropic API. */ class Anthropic extends BaseAnthropic { constructor() { super(...arguments); this.completions = new API.Completions(this); this.messages = new API.Messages(this); this.models = new API.Models(this); this.beta = new API.Beta(this); } } exports.Anthropic = Anthropic; Anthropic.Completions = completions_1.Completions; Anthropic.Messages = messages_1.Messages; Anthropic.Models = models_1.Models; Anthropic.Beta = beta_1.Beta; //# sourceMappingURL=client.js.map