From 61aaaf631c72743a57b7ec25468a5d5f3e7c4bbd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:21:16 +0000 Subject: [PATCH 01/10] chore: bump testing data uri --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 19e9daeb..14c789bf 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 19 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-be055148d227480fcacc9086c37ac8009dcb487731069ada51af35044f65bee4.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-9563716c7b08b8936ba450ad05005d12cf5ca3b9a37fab8126ed372e422d6de6.yml From 1a5a31fed53ec5e72a327abac926d1334d531bd6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:07:55 +0000 Subject: [PATCH 02/10] fix(client): normalize method --- src/core.ts | 12 +++++++++++- tests/index.test.ts | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/core.ts b/src/core.ts index ea8d8dca..344bf6ac 100644 --- a/src/core.ts +++ b/src/core.ts @@ -556,9 +556,19 @@ export abstract class APIClient { const timeout = setTimeout(() => controller.abort(), ms); + const fetchOptions = { + signal: controller.signal as any, + ...options, + }; + if (fetchOptions.method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = fetchOptions.method.toUpperCase(); + } + return ( // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - this.fetch.call(undefined, url, { signal: controller.signal as any, ...options }).finally(() => { + this.fetch.call(undefined, url, fetchOptions).finally(() => { clearTimeout(timeout); }) ); diff --git a/tests/index.test.ts b/tests/index.test.ts index b6398085..9402a819 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -122,6 +122,23 @@ describe('instantiate client', () => { expect(spy).toHaveBeenCalledTimes(1); }); + test('normalized method', async () => { + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + capturedRequest = init; + return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Anthropic({ + baseURL: 'http://localhost:5000/', + apiKey: 'my-anthropic-api-key', + fetch: testFetch, + }); + + await client.patch('/foo'); + expect(capturedRequest?.method).toEqual('PATCH'); + }); + describe('baseUrl', () => { test('trailing slash', () => { const client = new Anthropic({ From 08cfa8be642078f55c8c1da7c11def948396c42d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:06:37 +0000 Subject: [PATCH 03/10] feat(api): add message batch delete endpoint --- .stats.yml | 4 +- api.md | 4 ++ src/resources/beta/messages/batches.ts | 52 +++++++++++++++++++ src/resources/beta/messages/index.ts | 2 + src/resources/beta/messages/messages.ts | 4 ++ src/resources/messages/batches.ts | 24 +++++++++ src/resources/messages/index.ts | 1 + src/resources/messages/messages.ts | 2 + .../beta/messages/batches.test.ts | 29 +++++++++++ tests/api-resources/messages/batches.test.ts | 18 +++++++ 10 files changed, 138 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 14c789bf..239e17b7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 19 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-9563716c7b08b8936ba450ad05005d12cf5ca3b9a37fab8126ed372e422d6de6.yml +configured_endpoints: 21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-fd67aea6883f1ee9e46f31a42d3940f0acb1749e787055bd9b9f278b20fa53ec.yml diff --git a/api.md b/api.md index 48d1c9a8..9a25fe32 100644 --- a/api.md +++ b/api.md @@ -70,6 +70,7 @@ Methods: Types: +- DeletedMessageBatch - MessageBatch - MessageBatchCanceledResult - MessageBatchErroredResult @@ -84,6 +85,7 @@ Methods: - client.messages.batches.create({ ...params }) -> MessageBatch - client.messages.batches.retrieve(messageBatchId) -> MessageBatch - client.messages.batches.list({ ...params }) -> MessageBatchesPage +- client.messages.batches.delete(messageBatchId) -> DeletedMessageBatch - client.messages.batches.cancel(messageBatchId) -> MessageBatch - client.messages.batches.results(messageBatchId) -> Response @@ -175,6 +177,7 @@ Methods: Types: +- BetaDeletedMessageBatch - BetaMessageBatch - BetaMessageBatchCanceledResult - BetaMessageBatchErroredResult @@ -189,5 +192,6 @@ Methods: - client.beta.messages.batches.create({ ...params }) -> BetaMessageBatch - client.beta.messages.batches.retrieve(messageBatchId, { ...params }) -> BetaMessageBatch - client.beta.messages.batches.list({ ...params }) -> BetaMessageBatchesPage +- client.beta.messages.batches.delete(messageBatchId, { ...params }) -> BetaDeletedMessageBatch - client.beta.messages.batches.cancel(messageBatchId, { ...params }) -> BetaMessageBatch - client.beta.messages.batches.results(messageBatchId, { ...params }) -> Response diff --git a/src/resources/beta/messages/batches.ts b/src/resources/beta/messages/batches.ts index 0a863e64..b564cb1c 100644 --- a/src/resources/beta/messages/batches.ts +++ b/src/resources/beta/messages/batches.ts @@ -85,6 +85,35 @@ export class Batches extends APIResource { }); } + /** + * This endpoint is idempotent and can be used to poll for Message Batch + * completion. To access the results of a Message Batch, make a request to the + * `results_url` field in the response. + */ + delete( + messageBatchId: string, + params?: BatchDeleteParams, + options?: Core.RequestOptions, + ): Core.APIPromise; + delete(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise; + delete( + messageBatchId: string, + params: BatchDeleteParams | Core.RequestOptions = {}, + options?: Core.RequestOptions, + ): Core.APIPromise { + if (isRequestOptions(params)) { + return this.delete(messageBatchId, {}, params); + } + const { betas } = params; + return this._client.delete(`/v1/messages/batches/${messageBatchId}?beta=true`, { + ...options, + headers: { + 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), + ...options?.headers, + }, + }); + } + /** * Batches may be canceled any time before processing ends. Once cancellation is * initiated, the batch enters a `canceling` state, at which time the system may @@ -168,6 +197,20 @@ export class Batches extends APIResource { export class BetaMessageBatchesPage extends Page {} +export interface BetaDeletedMessageBatch { + /** + * ID of the Message Batch. + */ + id: string; + + /** + * Deleted object type. + * + * For Message Batches, this is always `"message_batch_deleted"`. + */ + type: 'message_batch_deleted'; +} + export interface BetaMessageBatch { /** * Unique object identifier. @@ -374,6 +417,13 @@ export interface BatchListParams extends PageParams { betas?: Array; } +export interface BatchDeleteParams { + /** + * Optional header to specify the beta version(s) you want to use. + */ + betas?: Array; +} + export interface BatchCancelParams { /** * Optional header to specify the beta version(s) you want to use. @@ -392,6 +442,7 @@ Batches.BetaMessageBatchesPage = BetaMessageBatchesPage; export declare namespace Batches { export { + type BetaDeletedMessageBatch as BetaDeletedMessageBatch, type BetaMessageBatch as BetaMessageBatch, type BetaMessageBatchCanceledResult as BetaMessageBatchCanceledResult, type BetaMessageBatchErroredResult as BetaMessageBatchErroredResult, @@ -404,6 +455,7 @@ export declare namespace Batches { type BatchCreateParams as BatchCreateParams, type BatchRetrieveParams as BatchRetrieveParams, type BatchListParams as BatchListParams, + type BatchDeleteParams as BatchDeleteParams, type BatchCancelParams as BatchCancelParams, type BatchResultsParams as BatchResultsParams, }; diff --git a/src/resources/beta/messages/index.ts b/src/resources/beta/messages/index.ts index 2cf85964..e6b260ab 100644 --- a/src/resources/beta/messages/index.ts +++ b/src/resources/beta/messages/index.ts @@ -3,6 +3,7 @@ export { BetaMessageBatchesPage, Batches, + type BetaDeletedMessageBatch, type BetaMessageBatch, type BetaMessageBatchCanceledResult, type BetaMessageBatchErroredResult, @@ -14,6 +15,7 @@ export { type BatchCreateParams, type BatchRetrieveParams, type BatchListParams, + type BatchDeleteParams, type BatchCancelParams, type BatchResultsParams, } from './batches'; diff --git a/src/resources/beta/messages/messages.ts b/src/resources/beta/messages/messages.ts index 186a6c36..92239cd8 100644 --- a/src/resources/beta/messages/messages.ts +++ b/src/resources/beta/messages/messages.ts @@ -10,10 +10,12 @@ import * as BatchesAPI from './batches'; import { BatchCancelParams, BatchCreateParams, + BatchDeleteParams, BatchListParams, BatchResultsParams, BatchRetrieveParams, Batches, + BetaDeletedMessageBatch, BetaMessageBatch, BetaMessageBatchCanceledResult, BetaMessageBatchErroredResult, @@ -1115,6 +1117,7 @@ export declare namespace Messages { export { Batches as Batches, + type BetaDeletedMessageBatch as BetaDeletedMessageBatch, type BetaMessageBatch as BetaMessageBatch, type BetaMessageBatchCanceledResult as BetaMessageBatchCanceledResult, type BetaMessageBatchErroredResult as BetaMessageBatchErroredResult, @@ -1127,6 +1130,7 @@ export declare namespace Messages { type BatchCreateParams as BatchCreateParams, type BatchRetrieveParams as BatchRetrieveParams, type BatchListParams as BatchListParams, + type BatchDeleteParams as BatchDeleteParams, type BatchCancelParams as BatchCancelParams, type BatchResultsParams as BatchResultsParams, }; diff --git a/src/resources/messages/batches.ts b/src/resources/messages/batches.ts index b4fd45e8..3d9539cf 100644 --- a/src/resources/messages/batches.ts +++ b/src/resources/messages/batches.ts @@ -49,6 +49,15 @@ export class Batches extends APIResource { return this._client.getAPIList('/v1/messages/batches', MessageBatchesPage, { query, ...options }); } + /** + * This endpoint is idempotent and can be used to poll for Message Batch + * completion. To access the results of a Message Batch, make a request to the + * `results_url` field in the response. + */ + delete(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise { + return this._client.delete(`/v1/messages/batches/${messageBatchId}`, options); + } + /** * Batches may be canceled any time before processing ends. Once cancellation is * initiated, the batch enters a `canceling` state, at which time the system may @@ -90,6 +99,20 @@ export class Batches extends APIResource { export class MessageBatchesPage extends Page {} +export interface DeletedMessageBatch { + /** + * ID of the Message Batch. + */ + id: string; + + /** + * Deleted object type. + * + * For Message Batches, this is always `"message_batch_deleted"`. + */ + type: 'message_batch_deleted'; +} + export interface MessageBatch { /** * Unique object identifier. @@ -283,6 +306,7 @@ Batches.MessageBatchesPage = MessageBatchesPage; export declare namespace Batches { export { + type DeletedMessageBatch as DeletedMessageBatch, type MessageBatch as MessageBatch, type MessageBatchCanceledResult as MessageBatchCanceledResult, type MessageBatchErroredResult as MessageBatchErroredResult, diff --git a/src/resources/messages/index.ts b/src/resources/messages/index.ts index 10308d2a..1c9178ad 100644 --- a/src/resources/messages/index.ts +++ b/src/resources/messages/index.ts @@ -3,6 +3,7 @@ export { MessageBatchesPage, Batches, + type DeletedMessageBatch, type MessageBatch, type MessageBatchCanceledResult, type MessageBatchErroredResult, diff --git a/src/resources/messages/messages.ts b/src/resources/messages/messages.ts index a1affbf5..c75a62f5 100644 --- a/src/resources/messages/messages.ts +++ b/src/resources/messages/messages.ts @@ -9,6 +9,7 @@ import { BatchCreateParams, BatchListParams, Batches, + DeletedMessageBatch, MessageBatch, MessageBatchCanceledResult, MessageBatchErroredResult, @@ -1114,6 +1115,7 @@ export declare namespace Messages { export { Batches as Batches, + type DeletedMessageBatch as DeletedMessageBatch, type MessageBatch as MessageBatch, type MessageBatchCanceledResult as MessageBatchCanceledResult, type MessageBatchErroredResult as MessageBatchErroredResult, diff --git a/tests/api-resources/beta/messages/batches.test.ts b/tests/api-resources/beta/messages/batches.test.ts index e395910a..1ebd0cf4 100644 --- a/tests/api-resources/beta/messages/batches.test.ts +++ b/tests/api-resources/beta/messages/batches.test.ts @@ -132,6 +132,35 @@ describe('resource batches', () => { ).rejects.toThrow(Anthropic.NotFoundError); }); + test('delete', async () => { + const responsePromise = client.beta.messages.batches.delete('message_batch_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('delete: request options instead of params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.beta.messages.batches.delete('message_batch_id', { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Anthropic.NotFoundError); + }); + + test('delete: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.beta.messages.batches.delete( + 'message_batch_id', + { betas: ['string'] }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Anthropic.NotFoundError); + }); + test('cancel', async () => { const responsePromise = client.beta.messages.batches.cancel('message_batch_id'); const rawResponse = await responsePromise.asResponse(); diff --git a/tests/api-resources/messages/batches.test.ts b/tests/api-resources/messages/batches.test.ts index 26efdbc8..bc92b160 100644 --- a/tests/api-resources/messages/batches.test.ts +++ b/tests/api-resources/messages/batches.test.ts @@ -118,6 +118,24 @@ describe('resource batches', () => { ).rejects.toThrow(Anthropic.NotFoundError); }); + test('delete', async () => { + const responsePromise = client.messages.batches.delete('message_batch_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('delete: request options instead of params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.messages.batches.delete('message_batch_id', { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Anthropic.NotFoundError); + }); + test('cancel', async () => { const responsePromise = client.messages.batches.cancel('message_batch_id'); const rawResponse = await responsePromise.asResponse(); From 851ec7fa88f5f1fa0b13ff31e67370f1c8466027 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:54:30 +0000 Subject: [PATCH 04/10] docs: minor formatting changes --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a869b0f..7d94b319 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## Setting up the environment -This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable). +This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). Other package managers may work but are not officially supported for development. To set up the repository, run: @@ -29,10 +29,10 @@ All files in the `examples/` directory are not modified by the generator and can … ``` -``` -chmod +x examples/.ts +```sh +$ chmod +x examples/.ts # run the example against your api -yarn tsn -T examples/.ts +$ yarn tsn -T examples/.ts ``` ## Using the repository from source From 05b96ada10c04198d1e17421b7bc56f3f40d1dd2 Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 11:01:04 +0000 Subject: [PATCH 05/10] feat: initial alpha refactor --- .eslintrc.js | 19 + README.md | 26 +- api.md | 20 +- jest.config.ts | 2 +- package.json | 75 +- scripts/build | 8 +- scripts/lint | 11 +- scripts/utils/attw-report.cjs | 21 + scripts/utils/fix-index-exports.cjs | 7 +- scripts/utils/postprocess-files.cjs | 199 +-- src/_shims/MultipartBody.ts | 9 - src/_shims/README.md | 46 - src/_shims/auto/runtime-bun.ts | 4 - src/_shims/auto/runtime-deno.ts | 4 - src/_shims/auto/runtime-node.ts | 4 - src/_shims/auto/runtime.ts | 4 - src/_shims/auto/types-deno.ts | 4 - src/_shims/auto/types-node.ts | 4 - src/_shims/auto/types.d.ts | 101 -- src/_shims/auto/types.js | 3 - src/_shims/auto/types.mjs | 3 - src/_shims/bun-runtime.ts | 14 - src/_shims/index-deno.ts | 110 -- src/_shims/index.d.ts | 81 -- src/_shims/index.js | 13 - src/_shims/index.mjs | 7 - src/_shims/manual-types.d.ts | 12 - src/_shims/manual-types.js | 3 - src/_shims/manual-types.mjs | 3 - src/_shims/node-runtime.ts | 81 -- src/_shims/node-types.d.ts | 42 - src/_shims/node-types.js | 3 - src/_shims/node-types.mjs | 3 - src/_shims/registry.ts | 67 - src/_shims/web-runtime.ts | 103 -- src/_shims/web-types.d.ts | 83 -- src/_shims/web-types.js | 3 - src/_shims/web-types.mjs | 3 - src/api-promise.ts | 84 ++ src/client.ts | 861 ++++++++++++ src/core.ts | 1242 ----------------- src/error.ts | 6 +- src/index.ts | 416 +----- src/internal/builtin-types.ts | 79 ++ src/internal/detect-platform.ts | 192 +++ src/internal/errors.ts | 18 + src/internal/headers.ts | 96 ++ src/internal/parse.ts | 53 + src/internal/polyfill/crypto.node.d.ts | 10 + src/internal/polyfill/crypto.node.js | 11 + src/internal/polyfill/crypto.node.mjs | 2 + src/internal/polyfill/file.node.d.ts | 9 + src/internal/polyfill/file.node.js | 17 + src/internal/polyfill/file.node.mjs | 9 + src/internal/request-options.ts | 71 + src/internal/shim-types.d.ts | 28 + src/internal/shims.ts | 124 ++ src/internal/types.ts | 6 + src/internal/utils.ts | 8 + src/internal/utils/base64.ts | 40 + src/internal/utils/env.ts | 20 + src/internal/utils/log.ts | 9 + src/internal/utils/sleep.ts | 3 + src/internal/utils/uuid.ts | 13 + src/internal/utils/values.ts | 94 ++ src/pagination.ts | 134 +- src/resource.ts | 6 +- src/resources/beta/beta.ts | 3 +- src/resources/beta/index.ts | 2 +- src/resources/beta/messages/batches.ts | 121 +- src/resources/beta/messages/index.ts | 2 +- src/resources/beta/messages/messages.ts | 19 +- src/resources/beta/models.ts | 32 +- src/resources/completions.ts | 15 +- src/resources/index.ts | 2 +- src/resources/messages/batches.ts | 61 +- src/resources/messages/index.ts | 2 +- src/resources/messages/messages.ts | 20 +- src/resources/models.ts | 29 +- src/shims/node.ts | 50 - src/shims/web.ts | 50 - src/streaming.ts | 13 +- src/uploads.ts | 111 +- .../beta/messages/batches.test.ts | 36 - .../beta/messages/messages.test.ts | 1 - tests/api-resources/beta/models.test.ts | 15 - tests/api-resources/completions.test.ts | 1 - tests/api-resources/messages/batches.test.ts | 36 - tests/api-resources/messages/messages.test.ts | 1 - tests/api-resources/models.test.ts | 15 - tests/base64.test.ts | 80 ++ tests/buildHeaders.test.ts | 88 ++ tests/form.test.ts | 4 +- tests/index.test.ts | 132 +- tests/responses.test.ts | 154 -- tests/streaming.test.ts | 1 - tests/uploads.test.ts | 12 +- tsconfig.build.json | 2 +- tsconfig.dist-src.json | 2 +- tsconfig.json | 3 +- yarn.lock | 520 ++++--- 101 files changed, 2954 insertions(+), 3547 deletions(-) create mode 100644 scripts/utils/attw-report.cjs delete mode 100644 src/_shims/MultipartBody.ts delete mode 100644 src/_shims/README.md delete mode 100644 src/_shims/auto/runtime-bun.ts delete mode 100644 src/_shims/auto/runtime-deno.ts delete mode 100644 src/_shims/auto/runtime-node.ts delete mode 100644 src/_shims/auto/runtime.ts delete mode 100644 src/_shims/auto/types-deno.ts delete mode 100644 src/_shims/auto/types-node.ts delete mode 100644 src/_shims/auto/types.d.ts delete mode 100644 src/_shims/auto/types.js delete mode 100644 src/_shims/auto/types.mjs delete mode 100644 src/_shims/bun-runtime.ts delete mode 100644 src/_shims/index-deno.ts delete mode 100644 src/_shims/index.d.ts delete mode 100644 src/_shims/index.js delete mode 100644 src/_shims/index.mjs delete mode 100644 src/_shims/manual-types.d.ts delete mode 100644 src/_shims/manual-types.js delete mode 100644 src/_shims/manual-types.mjs delete mode 100644 src/_shims/node-runtime.ts delete mode 100644 src/_shims/node-types.d.ts delete mode 100644 src/_shims/node-types.js delete mode 100644 src/_shims/node-types.mjs delete mode 100644 src/_shims/registry.ts delete mode 100644 src/_shims/web-runtime.ts delete mode 100644 src/_shims/web-types.d.ts delete mode 100644 src/_shims/web-types.js delete mode 100644 src/_shims/web-types.mjs create mode 100644 src/api-promise.ts create mode 100644 src/client.ts create mode 100644 src/internal/builtin-types.ts create mode 100644 src/internal/detect-platform.ts create mode 100644 src/internal/errors.ts create mode 100644 src/internal/headers.ts create mode 100644 src/internal/parse.ts create mode 100644 src/internal/polyfill/crypto.node.d.ts create mode 100644 src/internal/polyfill/crypto.node.js create mode 100644 src/internal/polyfill/crypto.node.mjs create mode 100644 src/internal/polyfill/file.node.d.ts create mode 100644 src/internal/polyfill/file.node.js create mode 100644 src/internal/polyfill/file.node.mjs create mode 100644 src/internal/request-options.ts create mode 100644 src/internal/shim-types.d.ts create mode 100644 src/internal/shims.ts create mode 100644 src/internal/types.ts create mode 100644 src/internal/utils.ts create mode 100644 src/internal/utils/base64.ts create mode 100644 src/internal/utils/env.ts create mode 100644 src/internal/utils/log.ts create mode 100644 src/internal/utils/sleep.ts create mode 100644 src/internal/utils/uuid.ts create mode 100644 src/internal/utils/values.ts delete mode 100644 src/shims/node.ts delete mode 100644 src/shims/web.ts create mode 100644 tests/base64.test.ts create mode 100644 tests/buildHeaders.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index 60f0e7a3..67bf4a71 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,25 @@ module.exports = { 'no-unused-vars': 'off', 'prettier/prettier': 'error', 'unused-imports/no-unused-imports': 'error', + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@anthropic-ai/sdk', '@anthropic-ai/sdk/*'], + message: 'Use a relative import, not a package import.', + }, + ], + }, + ], }, + overrides: [ + { + files: ['tests/**', 'examples/**'], + rules: { + 'no-restricted-imports': 'off', + }, + }, + ], root: true, }; diff --git a/README.md b/README.md index da3db48e..a36f15f7 100644 --- a/README.md +++ b/README.md @@ -414,21 +414,23 @@ validate or strip extra properties from the response from the API. ### Customizing the fetch client -By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments. +By default, this library expects a global `fetch` function is defined. -If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment, -(for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`), -add the following import before your first import `from "Anthropic"`: +If you want to use a different `fetch` function, you can either polyfill the global: ```ts -// Tell TypeScript and the package to use the global web fetch instead of node-fetch. -// Note, despite the name, this does not add any polyfills, but expects them to be provided if needed. -import '@anthropic-ai/sdk/shims/web'; -import Anthropic from '@anthropic-ai/sdk'; +import fetch from 'my-fetch'; + +globalThis.fetch = fetch; ``` -To do the inverse, add `import "@anthropic-ai/sdk/shims/node"` (which does import polyfills). -This can also be useful if you are getting the wrong TypeScript types for `Response` ([more details](https://github.com/anthropics/anthropic-sdk-typescript/tree/main/src/_shims#readme)). +Or pass it to the client: + +```ts +import fetch from 'my-fetch'; + +const client = new Anthropic({ fetch }); +``` ### Logging and middleware @@ -481,6 +483,8 @@ await client.messages.create( ); ``` +## Frequently Asked Questions + ## Semantic versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: @@ -495,7 +499,7 @@ We are keen for your feedback; please open an [issue](https://www.github.com/ant ## Requirements -TypeScript >= 4.5 is supported. +TypeScript >= 4.9 is supported. The following runtimes are supported: diff --git a/api.md b/api.md index 9a25fe32..390a0b3f 100644 --- a/api.md +++ b/api.md @@ -83,11 +83,11 @@ Types: Methods: - client.messages.batches.create({ ...params }) -> MessageBatch -- client.messages.batches.retrieve(messageBatchId) -> MessageBatch +- client.messages.batches.retrieve(messageBatchID) -> MessageBatch - client.messages.batches.list({ ...params }) -> MessageBatchesPage -- client.messages.batches.delete(messageBatchId) -> DeletedMessageBatch -- client.messages.batches.cancel(messageBatchId) -> MessageBatch -- client.messages.batches.results(messageBatchId) -> Response +- client.messages.batches.delete(messageBatchID) -> DeletedMessageBatch +- client.messages.batches.cancel(messageBatchID) -> MessageBatch +- client.messages.batches.results(messageBatchID) -> Response # Models @@ -97,7 +97,7 @@ Types: Methods: -- client.models.retrieve(modelId) -> ModelInfo +- client.models.retrieve(modelID) -> ModelInfo - client.models.list({ ...params }) -> ModelInfosPage # Beta @@ -125,7 +125,7 @@ Types: Methods: -- client.beta.models.retrieve(modelId) -> BetaModelInfo +- client.beta.models.retrieve(modelID) -> BetaModelInfo - client.beta.models.list({ ...params }) -> BetaModelInfosPage ## Messages @@ -190,8 +190,8 @@ Types: Methods: - client.beta.messages.batches.create({ ...params }) -> BetaMessageBatch -- client.beta.messages.batches.retrieve(messageBatchId, { ...params }) -> BetaMessageBatch +- client.beta.messages.batches.retrieve(messageBatchID, { ...params }) -> BetaMessageBatch - client.beta.messages.batches.list({ ...params }) -> BetaMessageBatchesPage -- client.beta.messages.batches.delete(messageBatchId, { ...params }) -> BetaDeletedMessageBatch -- client.beta.messages.batches.cancel(messageBatchId, { ...params }) -> BetaMessageBatch -- client.beta.messages.batches.results(messageBatchId, { ...params }) -> Response +- client.beta.messages.batches.delete(messageBatchID, { ...params }) -> BetaDeletedMessageBatch +- client.beta.messages.batches.cancel(messageBatchID, { ...params }) -> BetaMessageBatch +- client.beta.messages.batches.results(messageBatchID, { ...params }) -> Response diff --git a/jest.config.ts b/jest.config.ts index 00231b16..98b9666d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -8,9 +8,9 @@ const config: JestConfigWithTsJest = { }, moduleNameMapper: { '^@anthropic-ai/sdk$': '/src/index.ts', - '^@anthropic-ai/sdk/_shims/auto/(.*)$': '/src/_shims/auto/$1-node', '^@anthropic-ai/sdk/(.*)$': '/src/$1', }, + setupFilesAfterEnv: ['/jest.setup.ts'], modulePathIgnorePatterns: [ '/ecosystem-tests/', '/dist/', diff --git a/package.json b/package.json index d8f88f57..b07a2f30 100644 --- a/package.json +++ b/package.json @@ -23,101 +23,46 @@ "lint": "./scripts/lint", "fix": "./scripts/format" }, - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, + "dependencies": {}, "devDependencies": { + "@arethetypeswrong/cli": "^0.17.0", "@swc/core": "^1.3.102", "@swc/jest": "^0.2.29", "@types/jest": "^29.4.0", + "@types/node": "^20.17.6", "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", + "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.49.0", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-unused-imports": "^3.0.0", "iconv-lite": "^0.6.3", "jest": "^29.4.0", "prettier": "^3.0.0", + "publint": "^0.2.12", "ts-jest": "^29.1.0", "ts-node": "^10.5.0", - "tsc-multi": "^1.1.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.3/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "^4.8.2" }, - "sideEffects": [ - "./_shims/index.js", - "./_shims/index.mjs", - "./shims/node.js", - "./shims/node.mjs", - "./shims/web.js", - "./shims/web.mjs" - ], "imports": { "@anthropic-ai/sdk": ".", "@anthropic-ai/sdk/*": "./src/*" }, "exports": { - "./_shims/auto/*": { - "deno": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "bun": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*-bun.js", - "default": "./dist/_shims/auto/*-bun.mjs" - }, - "browser": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "worker": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "workerd": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "node": { - "types": "./dist/_shims/auto/*-node.d.ts", - "require": "./dist/_shims/auto/*-node.js", - "default": "./dist/_shims/auto/*-node.mjs" - }, - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, ".": { - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + "import": "./dist/index.mjs", + "require": "./dist/index.js" }, "./*.mjs": { - "types": "./dist/*.d.ts", "default": "./dist/*.mjs" }, "./*.js": { - "types": "./dist/*.d.ts", "default": "./dist/*.js" }, "./*": { - "types": "./dist/*.d.ts", - "require": "./dist/*.js", - "default": "./dist/*.mjs" + "import": "./dist/*.mjs", + "require": "./dist/*.js" } } } diff --git a/scripts/build b/scripts/build index 0bee923e..5ffbb766 100755 --- a/scripts/build +++ b/scripts/build @@ -15,7 +15,6 @@ rm -rf dist; mkdir dist # Copy src to dist/src and build from dist/src into dist, so that # the source map for index.js.map will refer to ./src/index.ts etc cp -rp src README.md dist -rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts for file in LICENSE CHANGELOG.md; do if [ -e "${file}" ]; then cp "${file}" dist; fi done @@ -29,9 +28,6 @@ node scripts/utils/make-dist-package-json.cjs > dist/package.json # build to .js/.mjs/.d.ts files npm exec tsc-multi -# copy over handwritten .js/.mjs/.d.ts files -cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims -cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto # we need to add exports = module.exports = Anthropic to index.js; # No way to get that from index.ts because it would cause compile errors # when building .mjs @@ -42,6 +38,10 @@ node scripts/utils/fix-index-exports.cjs # the same export default statement) cp dist/index.d.ts dist/index.d.mts cp tsconfig.dist-src.json dist/src/tsconfig.json +cp src/internal/shim-types.d.ts dist/internal/shim-types.d.ts +cp src/internal/shim-types.d.ts dist/internal/shim-types.d.mts +mkdir -p dist/internal/polyfill +cp src/internal/polyfill/*.{mjs,js,d.ts} dist/internal/polyfill node scripts/utils/postprocess-files.cjs diff --git a/scripts/lint b/scripts/lint index 6ba75dfb..0096d1e5 100755 --- a/scripts/lint +++ b/scripts/lint @@ -7,5 +7,12 @@ cd "$(dirname "$0")/.." echo "==> Running eslint" ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . -echo "==> Running tsc" -./node_modules/.bin/tsc --noEmit +echo "==> Building" +./scripts/build # also checks types + +echo "==> Running Are The Types Wrong?" +./node_modules/.bin/attw --pack dist -f json >.attw.json || true +node scripts/utils/attw-report.cjs + +echo "==> Running publint" +./node_modules/.bin/publint dist diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs new file mode 100644 index 00000000..e45e7952 --- /dev/null +++ b/scripts/utils/attw-report.cjs @@ -0,0 +1,21 @@ +const fs = require('fs'); +const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) + .flat() + .filter( + (problem) => + !( + // This is intentional, if the user specifies .mjs they get ESM. + ( + (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || + // This is intentional for backwards compat reasons. + (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) + ) + ), + ); +fs.unlinkSync('.attw.json'); +if (problems.length) { + process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); + process.exitCode = 1; +} else { + process.stdout.write('Types ok!\n'); +} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs index 72b0b8fd..e5e10b3e 100644 --- a/scripts/utils/fix-index-exports.cjs +++ b/scripts/utils/fix-index-exports.cjs @@ -8,7 +8,10 @@ const indexJs = let before = fs.readFileSync(indexJs, 'utf8'); let after = before.replace( - /^\s*exports\.default\s*=\s*(\w+)/m, - 'exports = module.exports = $1;\nexports.default = $1', + /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, + `exports = module.exports = function (...args) { + return new exports.default(...args) + } + $1`.replace(/^ /gm, ''), ); fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs index 607032ec..deae575e 100644 --- a/scripts/utils/postprocess-files.cjs +++ b/scripts/utils/postprocess-files.cjs @@ -1,98 +1,11 @@ +// @ts-check const fs = require('fs'); const path = require('path'); -const { parse } = require('@typescript-eslint/parser'); - -const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? '@anthropic-ai/sdk/'; const distDir = process.env['DIST_PATH'] ? path.resolve(process.env['DIST_PATH']) : path.resolve(__dirname, '..', '..', 'dist'); -const distSrcDir = path.join(distDir, 'src'); - -/** - * Quick and dirty AST traversal - */ -function traverse(node, visitor) { - if (!node || typeof node.type !== 'string') return; - visitor.node?.(node); - visitor[node.type]?.(node); - for (const key in node) { - const value = node[key]; - if (Array.isArray(value)) { - for (const elem of value) traverse(elem, visitor); - } else if (value instanceof Object) { - traverse(value, visitor); - } - } -} - -/** - * Helper method for replacing arbitrary ranges of text in input code. - * - * The `replacer` is a function that will be called with a mini-api. For example: - * - * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar' - * - * The replaced ranges must not be overlapping. - */ -function replaceRanges(code, replacer) { - const replacements = []; - replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) }); - - if (!replacements.length) return code; - replacements.sort((a, b) => a.range[0] - b.range[0]); - const overlapIndex = replacements.findIndex( - (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0], - ); - if (overlapIndex >= 0) { - throw new Error( - `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify( - replacements[overlapIndex], - )}`, - ); - } - - const parts = []; - let end = 0; - for (const { - range: [from, to], - replacement, - } of replacements) { - if (from > end) parts.push(code.substring(end, from)); - parts.push(replacement); - end = to; - } - if (end < code.length) parts.push(code.substring(end)); - return parts.join(''); -} - -/** - * Like calling .map(), where the iteratee is called on the path in every import or export from statement. - * @returns the transformed code - */ -function mapModulePaths(code, iteratee) { - const ast = parse(code, { range: true }); - return replaceRanges(code, ({ replace }) => - traverse(ast, { - node(node) { - switch (node.type) { - case 'ImportDeclaration': - case 'ExportNamedDeclaration': - case 'ExportAllDeclaration': - case 'ImportExpression': - if (node.source) { - const { range, value } = node.source; - const transformed = iteratee(value); - if (transformed !== value) { - replace(range, JSON.stringify(transformed)); - } - } - } - }, - }), - ); -} async function* walk(dir) { for await (const d of await fs.promises.opendir(dir)) { @@ -103,63 +16,79 @@ async function* walk(dir) { } async function postprocess() { - for await (const file of walk(path.resolve(__dirname, '..', '..', 'dist'))) { - if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue; + for await (const file of walk(distDir)) { + if (!/(\.d)?[cm]?ts$/.test(file)) continue; const code = await fs.promises.readFile(file, 'utf8'); - let transformed = mapModulePaths(code, (importPath) => { - if (file.startsWith(distSrcDir)) { - if (importPath.startsWith(pkgImportPath)) { - // convert self-references in dist/src to relative paths - let relativePath = path.relative( - path.dirname(file), - path.join(distSrcDir, importPath.substring(pkgImportPath.length)), - ); - if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`; - return relativePath; - } - return importPath; - } - if (importPath.startsWith('.')) { - // add explicit file extensions to relative imports - const { dir, name } = path.parse(importPath); - const ext = /\.mjs$/.test(file) ? '.mjs' : '.js'; - return `${dir}/${name}${ext}`; - } - return importPath; - }); - - if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) { - // strip out `unknown extends Foo ? never :` shim guards in dist/src - // to prevent errors from appearing in Go To Source - transformed = transformed.replace( - new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'), - // replace with same number of characters to avoid breaking source maps - (match) => ' '.repeat(match.length), - ); - } - - if (file.endsWith('.d.ts')) { - // work around bad tsc behavior - // if we have `import { type Readable } from '@anthropic-ai/sdk/_shims/index'`, - // tsc sometimes replaces `Readable` with `import("stream").Readable` inline - // in the output .d.ts - transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable'); - } - - // strip out lib="dom" and types="node" references; these are needed at build time, - // but would pollute the user's TS environment - transformed = transformed.replace( - /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', ); if (transformed !== code) { - await fs.promises.writeFile(file, transformed, 'utf8'); console.error(`wrote ${path.relative(process.cwd(), file)}`); + await fs.promises.writeFile(file, transformed, 'utf8'); + } + } + + const newExports = { + '.': { + require: { + types: './index.d.ts', + default: './index.js', + }, + types: './index.d.mts', + default: './index.mjs', + }, + }; + + for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { + const subpath = './' + entry.name; + newExports[subpath + '/*.mjs'] = { + default: subpath + '/*.mjs', + }; + newExports[subpath + '/*.js'] = { + default: subpath + '/*.js', + }; + newExports[subpath + '/*'] = { + import: subpath + '/*.mjs', + require: subpath + '/*.js', + }; + } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { + const { name, ext } = path.parse(entry.name); + const subpathWithoutExt = './' + name; + const subpath = './' + entry.name; + newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; + const isModule = ext[1] === 'm'; + if (isModule) { + newExports[subpathWithoutExt].import = subpath; + } else { + newExports[subpathWithoutExt].require = subpath; + } + newExports[subpath] = { + default: subpath, + }; } } + await fs.promises.writeFile( + 'dist/package.json', + JSON.stringify( + Object.assign( + /** @type {Record} */ ( + JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) + ), + { + exports: newExports, + }, + ), + null, + 2, + ), + ); } postprocess(); diff --git a/src/_shims/MultipartBody.ts b/src/_shims/MultipartBody.ts deleted file mode 100644 index af3b1118..00000000 --- a/src/_shims/MultipartBody.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export class MultipartBody { - constructor(public body: any) {} - get [Symbol.toStringTag](): string { - return 'MultipartBody'; - } -} diff --git a/src/_shims/README.md b/src/_shims/README.md deleted file mode 100644 index 28efc3b5..00000000 --- a/src/_shims/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# 👋 Wondering what everything in here does? - -`@anthropic-ai/sdk` supports a wide variety of runtime environments like Node.js, Deno, Bun, browsers, and various -edge runtimes, as well as both CommonJS (CJS) and EcmaScript Modules (ESM). - -To do this, `@anthropic-ai/sdk` provides shims for either using `node-fetch` when in Node (because `fetch` is still experimental there) or the global `fetch` API built into the environment when not in Node. - -It uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to -automatically select the correct shims for each environment. However, conditional exports are a fairly new -feature and not supported everywhere. For instance, the TypeScript `"moduleResolution": "node"` - -setting doesn't consult the `exports` map, compared to `"moduleResolution": "nodeNext"`, which does. -Unfortunately that's still the default setting, and it can result in errors like -getting the wrong raw `Response` type from `.asResponse()`, for example. - -The user can work around these issues by manually importing one of: - -- `import '@anthropic-ai/sdk/shims/node'` -- `import '@anthropic-ai/sdk/shims/web'` - -All of the code here in `_shims` handles selecting the automatic default shims or manual overrides. - -### How it works - Runtime - -Runtime shims get installed by calling `setShims` exported by `@anthropic-ai/sdk/_shims/registry`. - -Manually importing `@anthropic-ai/sdk/shims/node` or `@anthropic-ai/sdk/shims/web`, calls `setShims` with the respective runtime shims. - -All client code imports shims from `@anthropic-ai/sdk/_shims/index`, which: - -- checks if shims have been set manually -- if not, calls `setShims` with the shims from `@anthropic-ai/sdk/_shims/auto/runtime` -- re-exports the installed shims from `@anthropic-ai/sdk/_shims/registry`. - -`@anthropic-ai/sdk/_shims/auto/runtime` exports web runtime shims. -If the `node` export condition is set, the export map replaces it with `@anthropic-ai/sdk/_shims/auto/runtime-node`. - -### How it works - Type time - -All client code imports shim types from `@anthropic-ai/sdk/_shims/index`, which selects the manual types from `@anthropic-ai/sdk/_shims/manual-types` if they have been declared, otherwise it exports the auto types from `@anthropic-ai/sdk/_shims/auto/types`. - -`@anthropic-ai/sdk/_shims/manual-types` exports an empty namespace. -Manually importing `@anthropic-ai/sdk/shims/node` or `@anthropic-ai/sdk/shims/web` merges declarations into this empty namespace, so they get picked up by `@anthropic-ai/sdk/_shims/index`. - -`@anthropic-ai/sdk/_shims/auto/types` exports web type definitions. -If the `node` export condition is set, the export map replaces it with `@anthropic-ai/sdk/_shims/auto/types-node`, though TS only picks this up if `"moduleResolution": "nodenext"` or `"moduleResolution": "bundler"`. diff --git a/src/_shims/auto/runtime-bun.ts b/src/_shims/auto/runtime-bun.ts deleted file mode 100644 index e053254b..00000000 --- a/src/_shims/auto/runtime-bun.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../bun-runtime'; diff --git a/src/_shims/auto/runtime-deno.ts b/src/_shims/auto/runtime-deno.ts deleted file mode 100644 index 62b7a39e..00000000 --- a/src/_shims/auto/runtime-deno.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-runtime'; diff --git a/src/_shims/auto/runtime-node.ts b/src/_shims/auto/runtime-node.ts deleted file mode 100644 index 0ae2216f..00000000 --- a/src/_shims/auto/runtime-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../node-runtime'; diff --git a/src/_shims/auto/runtime.ts b/src/_shims/auto/runtime.ts deleted file mode 100644 index 62b7a39e..00000000 --- a/src/_shims/auto/runtime.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-runtime'; diff --git a/src/_shims/auto/types-deno.ts b/src/_shims/auto/types-deno.ts deleted file mode 100644 index 226fb15a..00000000 --- a/src/_shims/auto/types-deno.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-types'; diff --git a/src/_shims/auto/types-node.ts b/src/_shims/auto/types-node.ts deleted file mode 100644 index 2625a8b7..00000000 --- a/src/_shims/auto/types-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../node-types'; diff --git a/src/_shims/auto/types.d.ts b/src/_shims/auto/types.d.ts deleted file mode 100644 index d7755070..00000000 --- a/src/_shims/auto/types.d.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export type Agent = any; - -// @ts-ignore -declare const _fetch: unknown extends typeof fetch ? never : typeof fetch; -export { _fetch as fetch }; - -// @ts-ignore -type _Request = unknown extends Request ? never : Request; -export { _Request as Request }; - -// @ts-ignore -type _RequestInfo = unknown extends RequestInfo ? never : RequestInfo; -export { type _RequestInfo as RequestInfo }; - -// @ts-ignore -type _RequestInit = unknown extends RequestInit ? never : RequestInit; -export { type _RequestInit as RequestInit }; - -// @ts-ignore -type _Response = unknown extends Response ? never : Response; -export { _Response as Response }; - -// @ts-ignore -type _ResponseInit = unknown extends ResponseInit ? never : ResponseInit; -export { type _ResponseInit as ResponseInit }; - -// @ts-ignore -type _ResponseType = unknown extends ResponseType ? never : ResponseType; -export { type _ResponseType as ResponseType }; - -// @ts-ignore -type _BodyInit = unknown extends BodyInit ? never : BodyInit; -export { type _BodyInit as BodyInit }; - -// @ts-ignore -type _Headers = unknown extends Headers ? never : Headers; -export { _Headers as Headers }; - -// @ts-ignore -type _HeadersInit = unknown extends HeadersInit ? never : HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -// @ts-ignore -type _FormData = unknown extends FormData ? never : FormData; -// @ts-ignore -declare const _FormData: unknown extends typeof FormData ? never : typeof FormData; -export { _FormData as FormData }; - -// @ts-ignore -type _File = unknown extends File ? never : File; -// @ts-ignore -declare const _File: unknown extends typeof File ? never : typeof File; -export { _File as File }; - -// @ts-ignore -type _Blob = unknown extends Blob ? never : Blob; -// @ts-ignore -declare const _Blob: unknown extends typeof Blob ? never : typeof Blob; -export { _Blob as Blob }; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -// @ts-ignore -type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; -// @ts-ignore -declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/auto/types.js b/src/_shims/auto/types.js deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/auto/types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/auto/types.mjs b/src/_shims/auto/types.mjs deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/auto/types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/bun-runtime.ts b/src/_shims/bun-runtime.ts deleted file mode 100644 index 8d5aaab0..00000000 --- a/src/_shims/bun-runtime.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { type Shims } from './registry'; -import { getRuntime as getWebRuntime } from './web-runtime'; -import { ReadStream as FsReadStream } from 'node:fs'; - -export function getRuntime(): Shims { - const runtime = getWebRuntime(); - function isFsReadStream(value: any): value is FsReadStream { - return value instanceof FsReadStream; - } - return { ...runtime, isFsReadStream }; -} diff --git a/src/_shims/index-deno.ts b/src/_shims/index-deno.ts deleted file mode 100644 index 0e54a183..00000000 --- a/src/_shims/index-deno.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { MultipartBody } from './MultipartBody'; -import { type RequestOptions } from '../core'; - -export const kind: string = 'web'; - -export type Agent = any; - -const _fetch = fetch; -type _fetch = typeof fetch; -export { _fetch as fetch }; - -const _Request = Request; -type _Request = Request; -export { _Request as Request }; - -type _RequestInfo = RequestInfo; -export { type _RequestInfo as RequestInfo }; - -type _RequestInit = RequestInit; -export { type _RequestInit as RequestInit }; - -const _Response = Response; -type _Response = Response; -export { _Response as Response }; - -type _ResponseInit = ResponseInit; -export { type _ResponseInit as ResponseInit }; - -type _ResponseType = ResponseType; -export { type _ResponseType as ResponseType }; - -type _BodyInit = BodyInit; -export { type _BodyInit as BodyInit }; - -const _Headers = Headers; -type _Headers = Headers; -export { _Headers as Headers }; - -type _HeadersInit = HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -const _FormData = FormData; -type _FormData = FormData; -export { _FormData as FormData }; - -const _File = File; -type _File = File; -export { _File as File }; - -const _Blob = Blob; -type _Blob = Blob; -export { _Blob as Blob }; - -export async function getMultipartRequestOptions>( - form: FormData, - opts: RequestOptions, -): Promise> { - return { - ...opts, - body: new MultipartBody(form) as any, - }; -} - -export function getDefaultAgent(url: string) { - return undefined; -} -export function fileFromPath() { - throw new Error( - 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/anthropics/anthropic-sdk-typescript#file-uploads', - ); -} - -export const isFsReadStream = (value: any) => false; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -const _ReadableStream = ReadableStream; -type _ReadableStream = ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/index.d.ts b/src/_shims/index.d.ts deleted file mode 100644 index ce8c10e5..00000000 --- a/src/_shims/index.d.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { manual } from './manual-types'; -import * as auto from '@anthropic-ai/sdk/_shims/auto/types'; -import { type RequestOptions } from '../core'; - -type SelectType = unknown extends Manual ? Auto : Manual; - -export const kind: string; - -// @ts-ignore -export type Agent = SelectType; - -// @ts-ignore -export const fetch: SelectType; - -// @ts-ignore -export type Request = SelectType; -// @ts-ignore -export type RequestInfo = SelectType; -// @ts-ignore -export type RequestInit = SelectType; - -// @ts-ignore -export type Response = SelectType; -// @ts-ignore -export type ResponseInit = SelectType; -// @ts-ignore -export type ResponseType = SelectType; -// @ts-ignore -export type BodyInit = SelectType; -// @ts-ignore -export type Headers = SelectType; -// @ts-ignore -export const Headers: SelectType; -// @ts-ignore -export type HeadersInit = SelectType; - -// @ts-ignore -export type BlobPropertyBag = SelectType; -// @ts-ignore -export type FilePropertyBag = SelectType; -// @ts-ignore -export type FileFromPathOptions = SelectType; -// @ts-ignore -export type FormData = SelectType; -// @ts-ignore -export const FormData: SelectType; -// @ts-ignore -export type File = SelectType; -// @ts-ignore -export const File: SelectType; -// @ts-ignore -export type Blob = SelectType; -// @ts-ignore -export const Blob: SelectType; - -// @ts-ignore -export type Readable = SelectType; -// @ts-ignore -export type FsReadStream = SelectType; -// @ts-ignore -export type ReadableStream = SelectType; -// @ts-ignore -export const ReadableStream: SelectType; - -export function getMultipartRequestOptions>( - form: FormData, - opts: RequestOptions, -): Promise>; - -export function getDefaultAgent(url: string): any; - -// @ts-ignore -export type FileFromPathOptions = SelectType; - -export function fileFromPath(path: string, options?: FileFromPathOptions): Promise; -export function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; - -export function isFsReadStream(value: any): value is FsReadStream; diff --git a/src/_shims/index.js b/src/_shims/index.js deleted file mode 100644 index f2db5798..00000000 --- a/src/_shims/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -const shims = require('./registry'); -const auto = require('@anthropic-ai/sdk/_shims/auto/runtime'); -if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); -for (const property of Object.keys(shims)) { - Object.defineProperty(exports, property, { - get() { - return shims[property]; - }, - }); -} diff --git a/src/_shims/index.mjs b/src/_shims/index.mjs deleted file mode 100644 index dfc2a414..00000000 --- a/src/_shims/index.mjs +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as shims from './registry.mjs'; -import * as auto from '@anthropic-ai/sdk/_shims/auto/runtime'; -if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); -export * from './registry.mjs'; diff --git a/src/_shims/manual-types.d.ts b/src/_shims/manual-types.d.ts deleted file mode 100644 index 506ec04f..00000000 --- a/src/_shims/manual-types.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -/** - * Types will get added to this namespace when you import one of the following: - * - * import '@anthropic-ai/sdk/shims/node' - * import '@anthropic-ai/sdk/shims/web' - * - * Importing more than one will cause type and runtime errors. - */ -export namespace manual {} diff --git a/src/_shims/manual-types.js b/src/_shims/manual-types.js deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/manual-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/manual-types.mjs b/src/_shims/manual-types.mjs deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/manual-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/node-runtime.ts b/src/_shims/node-runtime.ts deleted file mode 100644 index ab9f2ab5..00000000 --- a/src/_shims/node-runtime.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as nf from 'node-fetch'; -import * as fd from 'formdata-node'; -import { type File, type FilePropertyBag } from 'formdata-node'; -import KeepAliveAgent from 'agentkeepalive'; -import { AbortController as AbortControllerPolyfill } from 'abort-controller'; -import { ReadStream as FsReadStream } from 'node:fs'; -import { type Agent } from 'node:http'; -import { FormDataEncoder } from 'form-data-encoder'; -import { Readable } from 'node:stream'; -import { type RequestOptions } from '../core'; -import { MultipartBody } from './MultipartBody'; -import { type Shims } from './registry'; -import { ReadableStream } from 'node:stream/web'; - -type FileFromPathOptions = Omit; - -let fileFromPathWarned = false; - -/** - * @deprecated use fs.createReadStream('./my/file.txt') instead - */ -async function fileFromPath(path: string): Promise; -async function fileFromPath(path: string, filename?: string): Promise; -async function fileFromPath(path: string, options?: FileFromPathOptions): Promise; -async function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; -async function fileFromPath(path: string, ...args: any[]): Promise { - // this import fails in environments that don't handle export maps correctly, like old versions of Jest - const { fileFromPath: _fileFromPath } = await import('formdata-node/file-from-path'); - - if (!fileFromPathWarned) { - console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`); - fileFromPathWarned = true; - } - // @ts-ignore - return await _fileFromPath(path, ...args); -} - -const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); -const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); - -async function getMultipartRequestOptions>( - form: fd.FormData, - opts: RequestOptions, -): Promise> { - const encoder = new FormDataEncoder(form); - const readable = Readable.from(encoder); - const body = new MultipartBody(readable); - const headers = { - ...opts.headers, - ...encoder.headers, - 'Content-Length': encoder.contentLength, - }; - - return { ...opts, body: body as any, headers }; -} - -export function getRuntime(): Shims { - // Polyfill global object if needed. - if (typeof AbortController === 'undefined') { - // @ts-expect-error (the types are subtly different, but compatible in practice) - globalThis.AbortController = AbortControllerPolyfill; - } - return { - kind: 'node', - fetch: nf.default, - Request: nf.Request, - Response: nf.Response, - Headers: nf.Headers, - FormData: fd.FormData, - Blob: fd.Blob, - File: fd.File, - ReadableStream, - getMultipartRequestOptions, - getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), - fileFromPath, - isFsReadStream: (value: any): value is FsReadStream => value instanceof FsReadStream, - }; -} diff --git a/src/_shims/node-types.d.ts b/src/_shims/node-types.d.ts deleted file mode 100644 index c159e5fa..00000000 --- a/src/_shims/node-types.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as nf from 'node-fetch'; -import * as fd from 'formdata-node'; - -export { type Agent } from 'node:http'; -export { type Readable } from 'node:stream'; -export { type ReadStream as FsReadStream } from 'node:fs'; -export { ReadableStream } from 'node:stream/web'; - -export const fetch: typeof nf.default; - -export type Request = nf.Request; -export type RequestInfo = nf.RequestInfo; -export type RequestInit = nf.RequestInit; - -export type Response = nf.Response; -export type ResponseInit = nf.ResponseInit; -export type ResponseType = nf.ResponseType; -export type BodyInit = nf.BodyInit; -export type Headers = nf.Headers; -export type HeadersInit = nf.HeadersInit; - -type EndingType = 'native' | 'transparent'; -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -export type FormData = fd.FormData; -export const FormData: typeof fd.FormData; -export type File = fd.File; -export const File: typeof fd.File; -export type Blob = fd.Blob; -export const Blob: typeof fd.Blob; diff --git a/src/_shims/node-types.js b/src/_shims/node-types.js deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/node-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/node-types.mjs b/src/_shims/node-types.mjs deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/node-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/registry.ts b/src/_shims/registry.ts deleted file mode 100644 index 32ab1a48..00000000 --- a/src/_shims/registry.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { type RequestOptions } from '../core'; - -export interface Shims { - kind: string; - fetch: any; - Request: any; - Response: any; - Headers: any; - FormData: any; - Blob: any; - File: any; - ReadableStream: any; - getMultipartRequestOptions: >( - form: Shims['FormData'], - opts: RequestOptions, - ) => Promise>; - getDefaultAgent: (url: string) => any; - fileFromPath: - | ((path: string, filename?: string, options?: {}) => Promise) - | ((path: string, options?: {}) => Promise); - isFsReadStream: (value: any) => boolean; -} - -export let auto = false; -export let kind: Shims['kind'] | undefined = undefined; -export let fetch: Shims['fetch'] | undefined = undefined; -export let Request: Shims['Request'] | undefined = undefined; -export let Response: Shims['Response'] | undefined = undefined; -export let Headers: Shims['Headers'] | undefined = undefined; -export let FormData: Shims['FormData'] | undefined = undefined; -export let Blob: Shims['Blob'] | undefined = undefined; -export let File: Shims['File'] | undefined = undefined; -export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; -export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; -export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; -export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; -export let isFsReadStream: Shims['isFsReadStream'] | undefined = undefined; - -export function setShims(shims: Shims, options: { auto: boolean } = { auto: false }) { - if (auto) { - throw new Error( - `you must \`import '@anthropic-ai/sdk/shims/${shims.kind}'\` before importing anything else from @anthropic-ai/sdk`, - ); - } - if (kind) { - throw new Error( - `can't \`import '@anthropic-ai/sdk/shims/${shims.kind}'\` after \`import '@anthropic-ai/sdk/shims/${kind}'\``, - ); - } - auto = options.auto; - kind = shims.kind; - fetch = shims.fetch; - Request = shims.Request; - Response = shims.Response; - Headers = shims.Headers; - FormData = shims.FormData; - Blob = shims.Blob; - File = shims.File; - ReadableStream = shims.ReadableStream; - getMultipartRequestOptions = shims.getMultipartRequestOptions; - getDefaultAgent = shims.getDefaultAgent; - fileFromPath = shims.fileFromPath; - isFsReadStream = shims.isFsReadStream; -} diff --git a/src/_shims/web-runtime.ts b/src/_shims/web-runtime.ts deleted file mode 100644 index 1d640d5b..00000000 --- a/src/_shims/web-runtime.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { MultipartBody } from './MultipartBody'; -import { type RequestOptions } from '../core'; -import { type Shims } from './registry'; - -export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } = {}): Shims { - const recommendation = - manuallyImported ? - `You may need to use polyfills` - : `Add one of these imports before your first \`import … from '@anthropic-ai/sdk'\`: -- \`import '@anthropic-ai/sdk/shims/node'\` (if you're running on Node) -- \`import '@anthropic-ai/sdk/shims/web'\` (otherwise) -`; - - let _fetch, _Request, _Response, _Headers; - try { - // @ts-ignore - _fetch = fetch; - // @ts-ignore - _Request = Request; - // @ts-ignore - _Response = Response; - // @ts-ignore - _Headers = Headers; - } catch (error) { - throw new Error( - `this environment is missing the following Web Fetch API type: ${ - (error as any).message - }. ${recommendation}`, - ); - } - - return { - kind: 'web', - fetch: _fetch, - Request: _Request, - Response: _Response, - Headers: _Headers, - FormData: - // @ts-ignore - typeof FormData !== 'undefined' ? FormData : ( - class FormData { - // @ts-ignore - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'FormData' is undefined. ${recommendation}`, - ); - } - } - ), - Blob: - typeof Blob !== 'undefined' ? Blob : ( - class Blob { - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'Blob' is undefined. ${recommendation}`, - ); - } - } - ), - File: - // @ts-ignore - typeof File !== 'undefined' ? File : ( - class File { - // @ts-ignore - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'File' is undefined. ${recommendation}`, - ); - } - } - ), - ReadableStream: - // @ts-ignore - typeof ReadableStream !== 'undefined' ? ReadableStream : ( - class ReadableStream { - // @ts-ignore - constructor() { - throw new Error( - `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, - ); - } - } - ), - getMultipartRequestOptions: async >( - // @ts-ignore - form: FormData, - opts: RequestOptions, - ): Promise> => ({ - ...opts, - body: new MultipartBody(form) as any, - }), - getDefaultAgent: (url: string) => undefined, - fileFromPath: () => { - throw new Error( - 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/anthropics/anthropic-sdk-typescript#file-uploads', - ); - }, - isFsReadStream: (value: any) => false, - }; -} diff --git a/src/_shims/web-types.d.ts b/src/_shims/web-types.d.ts deleted file mode 100644 index 4ff35138..00000000 --- a/src/_shims/web-types.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export type Agent = any; - -declare const _fetch: typeof fetch; -export { _fetch as fetch }; - -type _Request = Request; -export { _Request as Request }; - -type _RequestInfo = RequestInfo; -export { type _RequestInfo as RequestInfo }; - -type _RequestInit = RequestInit; -export { type _RequestInit as RequestInit }; - -type _Response = Response; -export { _Response as Response }; - -type _ResponseInit = ResponseInit; -export { type _ResponseInit as ResponseInit }; - -type _ResponseType = ResponseType; -export { type _ResponseType as ResponseType }; - -type _BodyInit = BodyInit; -export { type _BodyInit as BodyInit }; - -type _Headers = Headers; -export { _Headers as Headers }; - -type _HeadersInit = HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -type _FormData = FormData; -declare const _FormData: typeof FormData; -export { _FormData as FormData }; - -type _File = File; -declare const _File: typeof File; -export { _File as File }; - -type _Blob = Blob; -declare const _Blob: typeof Blob; -export { _Blob as Blob }; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -type _ReadableStream = ReadableStream; -declare const _ReadableStream: typeof ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/web-types.js b/src/_shims/web-types.js deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/web-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/web-types.mjs b/src/_shims/web-types.mjs deleted file mode 100644 index ddbdb799..00000000 --- a/src/_shims/web-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/api-promise.ts b/src/api-promise.ts new file mode 100644 index 00000000..31b52325 --- /dev/null +++ b/src/api-promise.ts @@ -0,0 +1,84 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { type PromiseOrValue } from './internal/types'; +import { APIResponseProps, defaultParseResponse } from './internal/parse'; + +/** + * A subclass of `Promise` providing additional helper methods + * for interacting with the SDK. + */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + + constructor( + private responsePromise: Promise, + private parseResponse: (props: APIResponseProps) => PromiseOrValue = defaultParseResponse, + ) { + super((resolve) => { + // this is maybe a bit weird but this has to be a no-op to not implicitly + // parse the response body; instead .then, .catch, .finally are overridden + // to parse the response + resolve(null as any); + }); + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.responsePromise, async (props) => + transform(await this.parseResponse(props), props), + ); + } + + /** + * Gets the raw `Response` instance instead of parsing the response + * data. + * + * If you want to parse the response body but still get the `Response` + * instance, you can use {@link withResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + asResponse(): Promise { + return this.responsePromise.then((p) => p.response); + } + + /** + * Gets the parsed response data and the raw `Response` instance. + * + * If you just want to get the raw `Response` instance without parsing it, + * you can use {@link asResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then(this.parseResponse); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 00000000..3528c04a --- /dev/null +++ b/src/client.ts @@ -0,0 +1,861 @@ +// 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 } from './internal/types'; +import { debug } from './internal/utils/log'; +import { uuid4 } from './internal/utils/uuid'; +import { validatePositiveInteger, isAbsoluteURL } from './internal/utils/values'; +import { sleep } from './internal/utils/sleep'; +import { castToError } from './internal/errors'; +import type { APIResponseProps } from './internal/parse'; +import { getPlatformHeaders } from './internal/detect-platform'; +import * as Shims from './internal/shims'; +import * as Opts from './internal/request-options'; +import { VERSION } from './version'; +import { isBlobLike } from './uploads'; +import { buildHeaders } from './internal/headers'; +import * as Errors from './error'; +import * as Pagination from './pagination'; +import { AbstractPage, type PageParams, PageResponse } from './pagination'; +import * as Uploads from './uploads'; +import * as API from './resources/index'; +import { APIPromise } from './api-promise'; +import { type Fetch } from './internal/builtin-types'; +import { isRunningInBrowser } from './internal/detect-platform'; +import { HeadersLike, NullableHeaders, isEmptyHeaders } from './internal/headers'; +import { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import { + Completion, + CompletionCreateParams, + CompletionCreateParamsNonStreaming, + CompletionCreateParamsStreaming, + Completions, +} from './resources/completions'; +import { ModelInfo, ModelInfosPage, ModelListParams, Models } from './resources/models'; +import { readEnv } from './internal/utils/env'; +import { isEmptyObj } from './internal/utils/values'; +import { + AnthropicBeta, + Beta, + BetaAPIError, + BetaAuthenticationError, + BetaBillingError, + BetaError, + BetaErrorResponse, + BetaGatewayTimeoutError, + BetaInvalidRequestError, + BetaNotFoundError, + BetaOverloadedError, + BetaPermissionError, + BetaRateLimitError, +} from './resources/beta/beta'; +import { + Base64PDFSource, + CacheControlEphemeral, + ContentBlock, + ContentBlockDeltaEvent, + ContentBlockParam, + ContentBlockStartEvent, + ContentBlockStopEvent, + DocumentBlockParam, + ImageBlockParam, + InputJSONDelta, + Message, + MessageCountTokensParams, + MessageCreateParams, + MessageCreateParamsNonStreaming, + MessageCreateParamsStreaming, + MessageDeltaEvent, + MessageDeltaUsage, + MessageParam, + MessageStartEvent, + MessageStopEvent, + MessageStreamEvent, + MessageTokensCount, + Messages, + Metadata, + Model, + RawContentBlockDeltaEvent, + RawContentBlockStartEvent, + RawContentBlockStopEvent, + RawMessageDeltaEvent, + RawMessageStartEvent, + RawMessageStopEvent, + RawMessageStreamEvent, + TextBlock, + TextBlockParam, + TextDelta, + Tool, + ToolChoice, + ToolChoiceAny, + ToolChoiceAuto, + ToolChoiceTool, + ToolResultBlockParam, + ToolUseBlock, + ToolUseBlockParam, + Usage, +} from './resources/messages/messages'; + +const safeJSON = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return undefined; + } +}; + +export interface ClientOptions { + /** + * Defaults to process.env['ANTHROPIC_API_KEY']. + */ + apiKey?: string | null | undefined; + + /** + * Defaults to process.env['ANTHROPIC_AUTH_TOKEN']. + */ + authToken?: 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. + */ + timeout?: number; + + /** + * An HTTP agent used to manage HTTP(S) connections. + * + * If not provided, an agent will be constructed by default in the Node.js environment, + * otherwise no agent is used. + */ + httpAgent?: Shims.Agent; + + /** + * Specify a custom `fetch` function implementation. + * + * If not provided, we expect that `fetch` is defined globally. + */ + fetch?: Fetch | 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; +} + +type FinalizedRequestInit = RequestInit & { headers: Headers }; + +export class BaseAnthropic { + apiKey: string | null; + authToken: string | null; + + baseURL: string; + maxRetries: number; + timeout: number; + httpAgent: Shims.Agent | undefined; + + private fetch: Fetch; + #encoder: Opts.RequestEncoder; + protected idempotencyHeader?: string; + private _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} [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 {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. + * @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} 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 = readEnv('ANTHROPIC_API_KEY') ?? null, + authToken = readEnv('ANTHROPIC_AUTH_TOKEN') ?? null, + ...opts + }: ClientOptions = {}) { + const options: ClientOptions = { + apiKey, + authToken, + ...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!; + this.timeout = options.timeout ?? Anthropic.DEFAULT_TIMEOUT /* 10 minutes */; + this.httpAgent = options.httpAgent; + this.maxRetries = options.maxRetries ?? 2; + this.fetch = options.fetch ?? Shims.getDefaultFetch(); + this.#encoder = Opts.FallbackEncoder; + + this._options = options; + + this.apiKey = apiKey; + this.authToken = authToken; + } + + protected defaultQuery(): Record | undefined { + return this._options.defaultQuery; + } + + protected validateHeaders({ values, nulls }: NullableHeaders) { + 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', + ); + } + + protected authHeaders(opts: FinalRequestOptions): Headers | undefined { + const apiKeyAuth = this.apiKeyAuth(opts); + const bearerAuth = this.bearerAuth(opts); + + if (apiKeyAuth != null && !isEmptyHeaders(apiKeyAuth)) { + return apiKeyAuth; + } + + if (bearerAuth != null && !isEmptyHeaders(bearerAuth)) { + return bearerAuth; + } + return undefined; + } + + protected apiKeyAuth(opts: FinalRequestOptions): Headers | undefined { + if (this.apiKey == null) { + return undefined; + } + return new Headers({ 'X-Api-Key': this.apiKey }); + } + + protected bearerAuth(opts: FinalRequestOptions): Headers | undefined { + if (this.authToken == null) { + return undefined; + } + return new Headers({ Authorization: `Bearer ${this.authToken}` }); + } + + /** + * Basic re-implementation of `qs.stringify` for primitive types. + */ + protected stringifyQuery(query: Record): string { + 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('&'); + } + + 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 | null | undefined): string { + const url = + isAbsoluteURL(path) ? + new URL(path) + : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + + const defaultQuery = this.defaultQuery(); + if (!isEmptyObj(defaultQuery)) { + query = { ...defaultQuery, ...query }; + } + + if (typeof query === 'object' && query && !Array.isArray(query)) { + url.search = this.stringifyQuery(query as Record); + } + + return url.toString(); + } + + private calculateContentLength(body: unknown): string | null { + if (typeof body === 'string') { + if (typeof Buffer !== 'undefined') { + return Buffer.byteLength(body, 'utf8').toString(); + } + + if (typeof TextEncoder !== 'undefined') { + const encoder = new TextEncoder(); + const encoded = encoder.encode(body); + return encoded.length.toString(); + } + } else if (ArrayBuffer.isView(body)) { + return body.byteLength.toString(); + } + + return null; + } + + /** + * Used as a callback for mutating the given `FinalRequestOptions` object. + */ + protected async prepareOptions(options: FinalRequestOptions): Promise {} + + /** + * 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`. + */ + protected async prepareRequest( + request: RequestInit, + { url, options }: { url: string; options: FinalRequestOptions }, + ): Promise {} + + get(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('get', path, opts); + } + + post(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('post', path, opts); + } + + patch(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('patch', path, opts); + } + + put(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('put', path, opts); + } + + delete(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('delete', path, opts); + } + + private methodRequest( + method: HTTPMethod, + path: string, + opts?: PromiseOrValue, + ): APIPromise { + return this.request( + Promise.resolve(opts).then(async (opts) => { + const body = + opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer()) + : opts?.body instanceof DataView ? opts.body + : opts?.body instanceof ArrayBuffer ? new DataView(opts.body) + : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer) + : opts?.body; + return { method, path, ...opts, body }; + }), + ); + } + + request( + options: PromiseOrValue, + remainingRetries: number | null = null, + ): APIPromise { + return new APIPromise(this.makeRequest(options, remainingRetries)); + } + + private async makeRequest( + optionsInput: PromiseOrValue, + retriesRemaining: number | null, + ): Promise { + const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; + if (retriesRemaining == null) { + retriesRemaining = maxRetries; + } + + await this.prepareOptions(options); + + const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); + + await this.prepareRequest(req, { url, options }); + + debug('request', url, options, req.headers); + + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + + const controller = new AbortController(); + const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); + + if (response instanceof Error) { + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + if (retriesRemaining) { + return this.retryRequest(options, retriesRemaining); + } + if (response.name === 'AbortError') { + throw new Errors.APIConnectionTimeoutError(); + } + throw new Errors.APIConnectionError({ cause: response }); + } + + if (!response.ok) { + if (retriesRemaining && this.shouldRetry(response)) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + debug(`response (error; ${retryMessage})`, response.status, url, response.headers); + return this.retryRequest(options, retriesRemaining, response.headers); + } + + const errText = await response.text().catch((err: any) => castToError(err).message); + const errJSON = safeJSON(errText); + const errMessage = errJSON ? undefined : errText; + const retryMessage = retriesRemaining ? `(error; no more retries left)` : `(error; not retryable)`; + + debug(`response (error; ${retryMessage})`, response.status, url, response.headers, errMessage); + + const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); + throw err; + } + + return { response, options, controller }; + } + + getAPIList = Pagination.AbstractPage>( + path: string, + Page: new (...args: any[]) => PageClass, + opts?: RequestOptions, + ): Pagination.PagePromise { + return this.requestAPIList(Page, { method: 'get', path, ...opts }); + } + + requestAPIList< + Item = unknown, + PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, + >( + Page: new (...args: ConstructorParameters) => PageClass, + options: FinalRequestOptions, + ): Pagination.PagePromise { + const request = this.makeRequest(options, null); + return new Pagination.PagePromise(this as any as Anthropic, request, Page); + } + + async fetchWithTimeout( + url: RequestInfo, + init: RequestInit | undefined, + ms: number, + controller: AbortController, + ): Promise { + const { signal, method, ...options } = init || {}; + if (signal) signal.addEventListener('abort', () => controller.abort()); + + const timeout = setTimeout(() => controller.abort(), ms); + + const isReadableBody = Shims.isReadableLike(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(); + } + + return ( + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + this.fetch.call(undefined, url, fetchOptions).finally(() => { + clearTimeout(timeout); + }) + ); + } + + private shouldRetry(response: Response): boolean { + // 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; + } + + private async retryRequest( + options: FinalRequestOptions, + retriesRemaining: number, + responseHeaders?: Headers | undefined, + ): Promise { + let timeoutMillis: number | undefined; + + // 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 sleep(timeoutMillis); + + return this.makeRequest(options, retriesRemaining - 1); + } + + private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { + 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; + } + + buildRequest( + options: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): { req: FinalizedRequestInit; url: string; timeout: number } { + const { method, path, query } = options; + + const url = this.buildURL(path!, query as Record); + if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); + const timeout = options.timeout ?? this.timeout; + const httpAgent = options.httpAgent ?? this.httpAgent; + const minAgentTimeout = timeout + 1000; + if ( + typeof (httpAgent as any)?.options?.timeout === 'number' && + minAgentTimeout > ((httpAgent as any).options.timeout ?? 0) + ) { + // Allow any given request to bump our agent active socket timeout. + // This may seem strange, but leaking active sockets should be rare and not particularly problematic, + // and without mutating agent we would need to create more of them. + // This tradeoff optimizes for performance. + (httpAgent as any).options.timeout = minAgentTimeout; + } + + const { bodyHeaders, body } = this.buildBody({ options }); + const reqHeaders = this.buildHeaders({ options, method, bodyHeaders, retryCount }); + + const req: FinalizedRequestInit = { + method, + headers: reqHeaders, + ...(httpAgent && { agent: httpAgent }), + ...(options.signal && { signal: options.signal }), + ...((globalThis as any).ReadableStream && + body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), + ...(body && { body }), + }; + + return { req, url, timeout }; + } + + private buildHeaders({ + options, + method, + bodyHeaders, + retryCount, + }: { + options: FinalRequestOptions; + method: HTTPMethod; + bodyHeaders: HeadersLike; + retryCount: number; + }): Headers { + let idempotencyHeaders: HeadersLike = {}; + if (this.idempotencyHeader && method !== 'get') { + if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); + idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; + } + + const headers = buildHeaders([ + idempotencyHeaders, + { + Accept: 'application/json', + 'User-Agent': this.getUserAgent(), + 'X-Stainless-Retry-Count': String(retryCount), + ...getPlatformHeaders(), + ...(this._options.dangerouslyAllowBrowser ? + { 'anthropic-dangerous-direct-browser-access': 'true' } + : undefined), + 'anthropic-version': '2023-06-01', + }, + this.authHeaders(options), + this._options.defaultHeaders, + bodyHeaders, + options.headers, + ]); + + this.validateHeaders(headers); + + return headers.values; + } + + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + bodyHeaders: HeadersLike; + body: BodyInit | undefined; + } { + if (!body) { + return { bodyHeaders: undefined, body: undefined }; + } + const headers = 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` + body instanceof 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 as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) + ) { + return { bodyHeaders: undefined, body: body as BodyInit }; + } 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 as AsyncIterable) }; + } else { + return this.#encoder({ body, headers }); + } + } + + static Anthropic = this; + static HUMAN_PROMPT = '\n\nHuman:'; + static AI_PROMPT = '\n\nAssistant:'; + static DEFAULT_TIMEOUT = 600000; // 10 minutes + + static AnthropicError = Errors.AnthropicError; + static APIError = Errors.APIError; + static APIConnectionError = Errors.APIConnectionError; + static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; + static APIUserAbortError = Errors.APIUserAbortError; + static NotFoundError = Errors.NotFoundError; + static ConflictError = Errors.ConflictError; + static RateLimitError = Errors.RateLimitError; + static BadRequestError = Errors.BadRequestError; + static AuthenticationError = Errors.AuthenticationError; + static InternalServerError = Errors.InternalServerError; + static PermissionDeniedError = Errors.PermissionDeniedError; + static UnprocessableEntityError = Errors.UnprocessableEntityError; + + static toFile = Uploads.toFile; +} + +/** + * API Client for interfacing with the Anthropic API. + */ +export class Anthropic extends BaseAnthropic { + completions: API.Completions = new API.Completions(this); + messages: API.Messages = new API.Messages(this); + models: API.Models = new API.Models(this); + beta: API.Beta = new API.Beta(this); +} +Anthropic.Completions = Completions; +Anthropic.Messages = Messages; +Anthropic.Models = Models; +Anthropic.Beta = Beta; +export declare namespace Anthropic { + export type RequestOptions = Opts.RequestOptions; + + export import Page = Pagination.Page; + export { type PageParams as PageParams, type PageResponse as PageResponse }; + + export { + Completions as Completions, + type Completion as Completion, + type CompletionCreateParams as CompletionCreateParams, + type CompletionCreateParamsNonStreaming as CompletionCreateParamsNonStreaming, + type CompletionCreateParamsStreaming as CompletionCreateParamsStreaming, + }; + + export { + Messages as Messages, + type Base64PDFSource as Base64PDFSource, + type CacheControlEphemeral as CacheControlEphemeral, + type ContentBlock as ContentBlock, + type ContentBlockDeltaEvent as ContentBlockDeltaEvent, + type ContentBlockParam as ContentBlockParam, + type ContentBlockStartEvent as ContentBlockStartEvent, + type ContentBlockStopEvent as ContentBlockStopEvent, + type DocumentBlockParam as DocumentBlockParam, + type ImageBlockParam as ImageBlockParam, + type InputJSONDelta as InputJSONDelta, + type Message as Message, + type MessageDeltaEvent as MessageDeltaEvent, + type MessageDeltaUsage as MessageDeltaUsage, + type MessageParam as MessageParam, + type MessageStartEvent as MessageStartEvent, + type MessageStopEvent as MessageStopEvent, + type MessageStreamEvent as MessageStreamEvent, + type MessageTokensCount as MessageTokensCount, + type Metadata as Metadata, + type Model as Model, + type RawContentBlockDeltaEvent as RawContentBlockDeltaEvent, + type RawContentBlockStartEvent as RawContentBlockStartEvent, + type RawContentBlockStopEvent as RawContentBlockStopEvent, + type RawMessageDeltaEvent as RawMessageDeltaEvent, + type RawMessageStartEvent as RawMessageStartEvent, + type RawMessageStopEvent as RawMessageStopEvent, + type RawMessageStreamEvent as RawMessageStreamEvent, + type TextBlock as TextBlock, + type TextBlockParam as TextBlockParam, + type TextDelta as TextDelta, + type Tool as Tool, + type ToolChoice as ToolChoice, + type ToolChoiceAny as ToolChoiceAny, + type ToolChoiceAuto as ToolChoiceAuto, + type ToolChoiceTool as ToolChoiceTool, + type ToolResultBlockParam as ToolResultBlockParam, + type ToolUseBlock as ToolUseBlock, + type ToolUseBlockParam as ToolUseBlockParam, + type Usage as Usage, + type MessageCreateParams as MessageCreateParams, + type MessageCreateParamsNonStreaming as MessageCreateParamsNonStreaming, + type MessageCreateParamsStreaming as MessageCreateParamsStreaming, + type MessageCountTokensParams as MessageCountTokensParams, + }; + + export { + Models as Models, + type ModelInfo as ModelInfo, + type ModelInfosPage as ModelInfosPage, + type ModelListParams as ModelListParams, + }; + + export { + Beta as Beta, + type AnthropicBeta as AnthropicBeta, + type BetaAPIError as BetaAPIError, + type BetaAuthenticationError as BetaAuthenticationError, + type BetaBillingError as BetaBillingError, + type BetaError as BetaError, + type BetaErrorResponse as BetaErrorResponse, + type BetaGatewayTimeoutError as BetaGatewayTimeoutError, + type BetaInvalidRequestError as BetaInvalidRequestError, + type BetaNotFoundError as BetaNotFoundError, + type BetaOverloadedError as BetaOverloadedError, + type BetaPermissionError as BetaPermissionError, + type BetaRateLimitError as BetaRateLimitError, + }; + + export type APIErrorObject = API.APIErrorObject; + export type AuthenticationError = API.AuthenticationError; + export type BillingError = API.BillingError; + export type ErrorObject = API.ErrorObject; + export type ErrorResponse = API.ErrorResponse; + export type GatewayTimeoutError = API.GatewayTimeoutError; + export type InvalidRequestError = API.InvalidRequestError; + export type NotFoundError = API.NotFoundError; + export type OverloadedError = API.OverloadedError; + export type PermissionError = API.PermissionError; + export type RateLimitError = API.RateLimitError; +} +export const { HUMAN_PROMPT, AI_PROMPT } = Anthropic; diff --git a/src/core.ts b/src/core.ts index 344bf6ac..e69de29b 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,1242 +0,0 @@ -import { VERSION } from './version'; -import { Stream } from './streaming'; -import { - AnthropicError, - APIError, - APIConnectionError, - APIConnectionTimeoutError, - APIUserAbortError, -} from './error'; -import { - kind as shimsKind, - type Readable, - getDefaultAgent, - type Agent, - fetch, - type RequestInfo, - type RequestInit, - type Response, - type HeadersInit, -} from './_shims/index'; -export { type Response }; -import { BlobLike, isBlobLike, isMultipartBody } from './uploads'; -export { - maybeMultipartFormRequestOptions, - multipartFormRequestOptions, - createForm, - type Uploadable, -} from './uploads'; - -export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise; - -type PromiseOrValue = T | Promise; - -type APIResponseProps = { - response: Response; - options: FinalRequestOptions; - controller: AbortController; -}; - -async function defaultParseResponse(props: APIResponseProps): Promise> { - const { response } = props; - if (props.options.stream) { - debug('response', response.status, response.url, response.headers, response.body); - - // Note: there is an invariant here that isn't represented in the type system - // that if you set `stream: true` the response type must also be `Stream` - - if (props.options.__streamClass) { - return props.options.__streamClass.fromSSEResponse(response, props.controller) as any; - } - - return Stream.fromSSEResponse(response, props.controller) as any; - } - - // fetch refuses to read the body when the status code is 204. - if (response.status === 204) { - return null as WithRequestID; - } - - if (props.options.__binaryResponse) { - return response as unknown as WithRequestID; - } - - const contentType = response.headers.get('content-type'); - const isJSON = - contentType?.includes('application/json') || contentType?.includes('application/vnd.api+json'); - if (isJSON) { - const json = await response.json(); - - debug('response', response.status, response.url, response.headers, json); - - return _addRequestID(json as T, response); - } - - const text = await response.text(); - debug('response', response.status, response.url, response.headers, text); - - // TODO handle blob, arraybuffer, other content types, etc. - return text as unknown as WithRequestID; -} - -type WithRequestID = - T extends Array | Response | AbstractPage ? T - : T extends Record ? T & { _request_id?: string | null } - : T; - -function _addRequestID(value: T, response: Response): WithRequestID { - if (!value || typeof value !== 'object' || Array.isArray(value)) { - return value as WithRequestID; - } - - return Object.defineProperty(value, '_request_id', { - value: response.headers.get('request-id'), - enumerable: false, - }) as WithRequestID; -} - -/** - * A subclass of `Promise` providing additional helper methods - * for interacting with the SDK. - */ -export class APIPromise extends Promise> { - private parsedPromise: Promise> | undefined; - - constructor( - private responsePromise: Promise, - private parseResponse: ( - props: APIResponseProps, - ) => PromiseOrValue> = defaultParseResponse, - ) { - super((resolve) => { - // this is maybe a bit weird but this has to be a no-op to not implicitly - // parse the response body; instead .then, .catch, .finally are overridden - // to parse the response - resolve(null as any); - }); - } - - _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { - return new APIPromise(this.responsePromise, async (props) => - _addRequestID(transform(await this.parseResponse(props), props), props.response), - ); - } - - /** - * Gets the raw `Response` instance instead of parsing the response - * data. - * - * If you want to parse the response body but still get the `Response` - * instance, you can use {@link withResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` if you can, - * or add one of these imports before your first `import … from '@anthropic-ai/sdk'`: - * - `import '@anthropic-ai/sdk/shims/node'` (if you're running on Node) - * - `import '@anthropic-ai/sdk/shims/web'` (otherwise) - */ - asResponse(): Promise { - return this.responsePromise.then((p) => p.response); - } - - /** - * Gets the parsed response data, the raw `Response` instance and the ID of the request, - * returned vie the `request-id` header which is useful for debugging requests and resporting - * issues to Anthropic. - * - * If you just want to get the raw `Response` instance without parsing it, - * you can use {@link asResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` if you can, - * or add one of these imports before your first `import … from '@anthropic-ai/sdk'`: - * - `import '@anthropic-ai/sdk/shims/node'` (if you're running on Node) - * - `import '@anthropic-ai/sdk/shims/web'` (otherwise) - */ - async withResponse(): Promise<{ data: T; response: Response; request_id: string | null | undefined }> { - const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response, request_id: response.headers.get('request-id') }; - } - - private parse(): Promise> { - if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then(this.parseResponse) as any as Promise>; - } - return this.parsedPromise; - } - - override then, TResult2 = never>( - onfulfilled?: ((value: WithRequestID) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - ): Promise { - return this.parse().then(onfulfilled, onrejected); - } - - override catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise | TResult> { - return this.parse().catch(onrejected); - } - - override finally(onfinally?: (() => void) | undefined | null): Promise> { - return this.parse().finally(onfinally); - } -} - -export abstract class APIClient { - baseURL: string; - maxRetries: number; - timeout: number; - httpAgent: Agent | undefined; - - private fetch: Fetch; - protected idempotencyHeader?: string; - - constructor({ - baseURL, - maxRetries = 2, - timeout = 600000, // 10 minutes - httpAgent, - fetch: overriddenFetch, - }: { - baseURL: string; - maxRetries?: number | undefined; - timeout: number | undefined; - httpAgent: Agent | undefined; - fetch: Fetch | undefined; - }) { - this.baseURL = baseURL; - this.maxRetries = validatePositiveInteger('maxRetries', maxRetries); - this.timeout = validatePositiveInteger('timeout', timeout); - this.httpAgent = httpAgent; - - this.fetch = overriddenFetch ?? fetch; - } - - protected authHeaders(opts: FinalRequestOptions): Headers { - return {}; - } - - /** - * Override this to add your own default headers, for example: - * - * { - * ...super.defaultHeaders(), - * Authorization: 'Bearer 123', - * } - */ - protected defaultHeaders(opts: FinalRequestOptions): Headers { - return { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': this.getUserAgent(), - ...getPlatformHeaders(), - ...this.authHeaders(opts), - }; - } - - protected abstract defaultQuery(): DefaultQuery | undefined; - - /** - * Override this to add your own headers validation: - */ - protected validateHeaders(headers: Headers, customHeaders: Headers) {} - - protected defaultIdempotencyKey(): string { - return `stainless-node-retry-${uuid4()}`; - } - - get(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('get', path, opts); - } - - post(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('post', path, opts); - } - - patch(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('patch', path, opts); - } - - put(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('put', path, opts); - } - - delete(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('delete', path, opts); - } - - private methodRequest( - method: HTTPMethod, - path: string, - opts?: PromiseOrValue>, - ): APIPromise { - return this.request( - Promise.resolve(opts).then(async (opts) => { - const body = - opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer()) - : opts?.body instanceof DataView ? opts.body - : opts?.body instanceof ArrayBuffer ? new DataView(opts.body) - : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer) - : opts?.body; - return { method, path, ...opts, body }; - }), - ); - } - - getAPIList = AbstractPage>( - path: string, - Page: new (...args: any[]) => PageClass, - opts?: RequestOptions, - ): PagePromise { - return this.requestAPIList(Page, { method: 'get', path, ...opts }); - } - - private calculateContentLength(body: unknown): string | null { - if (typeof body === 'string') { - if (typeof Buffer !== 'undefined') { - return Buffer.byteLength(body, 'utf8').toString(); - } - - if (typeof TextEncoder !== 'undefined') { - const encoder = new TextEncoder(); - const encoded = encoder.encode(body); - return encoded.length.toString(); - } - } else if (ArrayBuffer.isView(body)) { - return body.byteLength.toString(); - } - - return null; - } - - buildRequest( - options: FinalRequestOptions, - { retryCount = 0 }: { retryCount?: number } = {}, - ): { req: RequestInit; url: string; timeout: number } { - const { method, path, query, headers: headers = {} } = options; - - const body = - ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ? - options.body - : isMultipartBody(options.body) ? options.body.body - : options.body ? JSON.stringify(options.body, null, 2) - : null; - const contentLength = this.calculateContentLength(body); - - const url = this.buildURL(path!, query); - if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); - const timeout = options.timeout ?? this.timeout; - const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url); - const minAgentTimeout = timeout + 1000; - if ( - typeof (httpAgent as any)?.options?.timeout === 'number' && - minAgentTimeout > ((httpAgent as any).options.timeout ?? 0) - ) { - // Allow any given request to bump our agent active socket timeout. - // This may seem strange, but leaking active sockets should be rare and not particularly problematic, - // and without mutating agent we would need to create more of them. - // This tradeoff optimizes for performance. - (httpAgent as any).options.timeout = minAgentTimeout; - } - - if (this.idempotencyHeader && method !== 'get') { - if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); - headers[this.idempotencyHeader] = options.idempotencyKey; - } - - const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount }); - - const req: RequestInit = { - method, - ...(body && { body: body as any }), - headers: reqHeaders, - ...(httpAgent && { agent: httpAgent }), - // @ts-ignore node-fetch uses a custom AbortSignal type that is - // not compatible with standard web types - signal: options.signal ?? null, - }; - - return { req, url, timeout }; - } - - private buildHeaders({ - options, - headers, - contentLength, - retryCount, - }: { - options: FinalRequestOptions; - headers: Record; - contentLength: string | null | undefined; - retryCount: number; - }): Record { - const reqHeaders: Record = {}; - if (contentLength) { - reqHeaders['content-length'] = contentLength; - } - - const defaultHeaders = this.defaultHeaders(options); - applyHeadersMut(reqHeaders, defaultHeaders); - applyHeadersMut(reqHeaders, headers); - - // let builtin fetch set the Content-Type for multipart bodies - if (isMultipartBody(options.body) && shimsKind !== 'node') { - delete reqHeaders['content-type']; - } - - // Don't set the retry count header if it was already set or removed through default headers or by the - // caller. We check `defaultHeaders` and `headers`, which can contain nulls, instead of `reqHeaders` to - // account for the removal case. - if ( - getHeader(defaultHeaders, 'x-stainless-retry-count') === undefined && - getHeader(headers, 'x-stainless-retry-count') === undefined - ) { - reqHeaders['x-stainless-retry-count'] = String(retryCount); - } - - this.validateHeaders(reqHeaders, headers); - - return reqHeaders; - } - - /** - * Used as a callback for mutating the given `FinalRequestOptions` object. - */ - protected async prepareOptions(options: FinalRequestOptions): Promise {} - - /** - * 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`. - */ - protected async prepareRequest( - request: RequestInit, - { url, options }: { url: string; options: FinalRequestOptions }, - ): Promise {} - - protected parseHeaders(headers: HeadersInit | null | undefined): Record { - return ( - !headers ? {} - : Symbol.iterator in headers ? - Object.fromEntries(Array.from(headers as Iterable).map((header) => [...header])) - : { ...headers } - ); - } - - protected makeStatusError( - status: number | undefined, - error: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ): APIError { - return APIError.generate(status, error, message, headers); - } - - request( - options: PromiseOrValue>, - remainingRetries: number | null = null, - ): APIPromise { - return new APIPromise(this.makeRequest(options, remainingRetries)); - } - - private async makeRequest( - optionsInput: PromiseOrValue>, - retriesRemaining: number | null, - ): Promise { - const options = await optionsInput; - const maxRetries = options.maxRetries ?? this.maxRetries; - if (retriesRemaining == null) { - retriesRemaining = maxRetries; - } - - await this.prepareOptions(options); - - const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); - - await this.prepareRequest(req, { url, options }); - - debug('request', url, options, req.headers); - - if (options.signal?.aborted) { - throw new APIUserAbortError(); - } - - const controller = new AbortController(); - const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); - - if (response instanceof Error) { - if (options.signal?.aborted) { - throw new APIUserAbortError(); - } - if (retriesRemaining) { - return this.retryRequest(options, retriesRemaining); - } - if (response.name === 'AbortError') { - throw new APIConnectionTimeoutError(); - } - throw new APIConnectionError({ cause: response }); - } - - const responseHeaders = createResponseHeaders(response.headers); - - if (!response.ok) { - if (retriesRemaining && this.shouldRetry(response)) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders); - return this.retryRequest(options, retriesRemaining, responseHeaders); - } - - const errText = await response.text().catch((e) => castToError(e).message); - const errJSON = safeJSON(errText); - const errMessage = errJSON ? undefined : errText; - const retryMessage = retriesRemaining ? `(error; no more retries left)` : `(error; not retryable)`; - - debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders, errMessage); - - const err = this.makeStatusError(response.status, errJSON, errMessage, responseHeaders); - throw err; - } - - return { response, options, controller }; - } - - requestAPIList = AbstractPage>( - Page: new (...args: ConstructorParameters) => PageClass, - options: FinalRequestOptions, - ): PagePromise { - const request = this.makeRequest(options, null); - return new PagePromise(this, request, Page); - } - - buildURL(path: string, query: Req | null | undefined): string { - const url = - isAbsoluteURL(path) ? - new URL(path) - : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); - - const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query } as Req; - } - - if (typeof query === 'object' && query && !Array.isArray(query)) { - url.search = this.stringifyQuery(query as Record); - } - - return url.toString(); - } - - protected stringifyQuery(query: Record): string { - 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 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('&'); - } - - async fetchWithTimeout( - url: RequestInfo, - init: RequestInit | undefined, - ms: number, - controller: AbortController, - ): Promise { - const { signal, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); - - const timeout = setTimeout(() => controller.abort(), ms); - - const fetchOptions = { - signal: controller.signal as any, - ...options, - }; - if (fetchOptions.method) { - // Custom methods like 'patch' need to be uppercased - // See https://github.com/nodejs/undici/issues/2294 - fetchOptions.method = fetchOptions.method.toUpperCase(); - } - - return ( - // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - this.fetch.call(undefined, url, fetchOptions).finally(() => { - clearTimeout(timeout); - }) - ); - } - - private shouldRetry(response: Response): boolean { - // 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; - } - - private async retryRequest( - options: FinalRequestOptions, - retriesRemaining: number, - responseHeaders?: Headers | undefined, - ): Promise { - let timeoutMillis: number | undefined; - - // 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?.['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?.['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 sleep(timeoutMillis); - - return this.makeRequest(options, retriesRemaining - 1); - } - - private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - 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; - } - - private getUserAgent(): string { - return `${this.constructor.name}/JS ${VERSION}`; - } -} - -export type PageInfo = { url: URL } | { params: Record | null }; - -export abstract class AbstractPage implements AsyncIterable { - #client: APIClient; - protected options: FinalRequestOptions; - - protected response: Response; - protected body: unknown; - - constructor(client: APIClient, response: Response, body: unknown, options: FinalRequestOptions) { - this.#client = client; - this.options = options; - this.response = response; - this.body = body; - } - - /** - * @deprecated Use nextPageInfo instead - */ - abstract nextPageParams(): Partial> | null; - abstract nextPageInfo(): PageInfo | null; - - abstract getPaginatedItems(): Item[]; - - hasNextPage(): boolean { - const items = this.getPaginatedItems(); - if (!items.length) return false; - return this.nextPageInfo() != null; - } - - async getNextPage(): Promise { - const nextInfo = this.nextPageInfo(); - if (!nextInfo) { - throw new AnthropicError( - 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', - ); - } - const nextOptions = { ...this.options }; - if ('params' in nextInfo && typeof nextOptions.query === 'object') { - nextOptions.query = { ...nextOptions.query, ...nextInfo.params }; - } else if ('url' in nextInfo) { - const params = [...Object.entries(nextOptions.query || {}), ...nextInfo.url.searchParams.entries()]; - for (const [key, value] of params) { - nextInfo.url.searchParams.set(key, value as any); - } - nextOptions.query = undefined; - nextOptions.path = nextInfo.url.toString(); - } - return await this.#client.requestAPIList(this.constructor as any, nextOptions); - } - - async *iterPages(): AsyncGenerator { - // eslint-disable-next-line @typescript-eslint/no-this-alias - let page: this = this; - yield page; - while (page.hasNextPage()) { - page = await page.getNextPage(); - yield page; - } - } - - async *[Symbol.asyncIterator](): AsyncGenerator { - for await (const page of this.iterPages()) { - for (const item of page.getPaginatedItems()) { - yield item; - } - } - } -} - -/** - * This subclass of Promise will resolve to an instantiated Page once the request completes. - * - * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: - * - * for await (const item of client.items.list()) { - * console.log(item) - * } - */ -export class PagePromise< - PageClass extends AbstractPage, - Item = ReturnType[number], - > - extends APIPromise - implements AsyncIterable -{ - constructor( - client: APIClient, - request: Promise, - Page: new (...args: ConstructorParameters) => PageClass, - ) { - super( - request, - async (props) => - new Page( - client, - props.response, - await defaultParseResponse(props), - props.options, - ) as WithRequestID, - ); - } - - /** - * Allow auto-paginating iteration on an unawaited list call, eg: - * - * for await (const item of client.items.list()) { - * console.log(item) - * } - */ - async *[Symbol.asyncIterator](): AsyncGenerator { - const page = await this; - for await (const item of page) { - yield item; - } - } -} - -export const createResponseHeaders = ( - headers: Awaited>['headers'], -): Record => { - return new Proxy( - Object.fromEntries( - // @ts-ignore - headers.entries(), - ), - { - get(target, name) { - const key = name.toString(); - return target[key.toLowerCase()] || target[key]; - }, - }, - ); -}; - -type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; - -export type RequestClient = { fetch: Fetch }; -export type Headers = Record; -export type DefaultQuery = Record; -export type KeysEnum = { [P in keyof Required]: true }; - -export type RequestOptions< - Req = unknown | Record | Readable | BlobLike | ArrayBufferView | ArrayBuffer, -> = { - method?: HTTPMethod; - path?: string; - query?: Req | undefined; - body?: Req | null | undefined; - headers?: Headers | undefined; - - maxRetries?: number; - stream?: boolean | undefined; - timeout?: number; - httpAgent?: Agent; - signal?: AbortSignal | undefined | null; - idempotencyKey?: string; - - __binaryRequest?: boolean | undefined; - __binaryResponse?: boolean | undefined; - __streamClass?: typeof Stream; -}; - -// This is required so that we can determine if a given object matches the RequestOptions -// type at runtime. While this requires duplication, it is enforced by the TypeScript -// compiler such that any missing / extraneous keys will cause an error. -const requestOptionsKeys: KeysEnum = { - method: true, - path: true, - query: true, - body: true, - headers: true, - - maxRetries: true, - stream: true, - timeout: true, - httpAgent: true, - signal: true, - idempotencyKey: true, - - __binaryRequest: true, - __binaryResponse: true, - __streamClass: true, -}; - -export const isRequestOptions = (obj: unknown): obj is RequestOptions => { - return ( - typeof obj === 'object' && - obj !== null && - !isEmptyObj(obj) && - Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k)) - ); -}; - -export type FinalRequestOptions | Readable | DataView> = - RequestOptions & { - method: HTTPMethod; - path: string; - }; - -declare const Deno: any; -declare const EdgeRuntime: any; -type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; -type PlatformName = - | 'MacOS' - | 'Linux' - | 'Windows' - | 'FreeBSD' - | 'OpenBSD' - | 'iOS' - | 'Android' - | `Other:${string}` - | 'Unknown'; -type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; -type PlatformProperties = { - 'X-Stainless-Lang': 'js'; - 'X-Stainless-Package-Version': string; - 'X-Stainless-OS': PlatformName; - 'X-Stainless-Arch': Arch; - 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; - 'X-Stainless-Runtime-Version': string; -}; -const getPlatformProperties = (): PlatformProperties => { - if (typeof Deno !== 'undefined' && Deno.build != null) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(Deno.build.os), - 'X-Stainless-Arch': normalizeArch(Deno.build.arch), - 'X-Stainless-Runtime': 'deno', - 'X-Stainless-Runtime-Version': - typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', - }; - } - if (typeof EdgeRuntime !== 'undefined') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': `other:${EdgeRuntime}`, - 'X-Stainless-Runtime': 'edge', - 'X-Stainless-Runtime-Version': process.version, - }; - } - // Check if Node.js - if (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(process.platform), - 'X-Stainless-Arch': normalizeArch(process.arch), - 'X-Stainless-Runtime': 'node', - 'X-Stainless-Runtime-Version': process.version, - }; - } - - const browserInfo = getBrowserInfo(); - if (browserInfo) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, - 'X-Stainless-Runtime-Version': browserInfo.version, - }; - } - - // TODO add support for Cloudflare workers, etc. - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': 'unknown', - 'X-Stainless-Runtime-Version': 'unknown', - }; -}; - -type BrowserInfo = { - browser: Browser; - version: string; -}; - -declare const navigator: { userAgent: string } | undefined; - -// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts -function getBrowserInfo(): BrowserInfo | null { - if (typeof navigator === 'undefined' || !navigator) { - return null; - } - - // NOTE: The order matters here! - const browserPatterns = [ - { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, - ]; - - // Find the FIRST matching browser - for (const { key, pattern } of browserPatterns) { - const match = pattern.exec(navigator.userAgent); - if (match) { - const major = match[1] || 0; - const minor = match[2] || 0; - const patch = match[3] || 0; - - return { browser: key, version: `${major}.${minor}.${patch}` }; - } - } - - return null; -} - -const normalizeArch = (arch: string): Arch => { - // Node docs: - // - https://nodejs.org/api/process.html#processarch - // Deno docs: - // - https://doc.deno.land/deno/stable/~/Deno.build - if (arch === 'x32') return 'x32'; - if (arch === 'x86_64' || arch === 'x64') return 'x64'; - if (arch === 'arm') return 'arm'; - if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; - if (arch) return `other:${arch}`; - return 'unknown'; -}; - -const normalizePlatform = (platform: string): PlatformName => { - // Node platforms: - // - https://nodejs.org/api/process.html#processplatform - // Deno platforms: - // - https://doc.deno.land/deno/stable/~/Deno.build - // - https://github.com/denoland/deno/issues/14799 - - platform = platform.toLowerCase(); - - // NOTE: this iOS check is untested and may not work - // Node does not work natively on IOS, there is a fork at - // https://github.com/nodejs-mobile/nodejs-mobile - // however it is unknown at the time of writing how to detect if it is running - if (platform.includes('ios')) return 'iOS'; - if (platform === 'android') return 'Android'; - if (platform === 'darwin') return 'MacOS'; - if (platform === 'win32') return 'Windows'; - if (platform === 'freebsd') return 'FreeBSD'; - if (platform === 'openbsd') return 'OpenBSD'; - if (platform === 'linux') return 'Linux'; - if (platform) return `Other:${platform}`; - return 'Unknown'; -}; - -let _platformHeaders: PlatformProperties; -const getPlatformHeaders = () => { - return (_platformHeaders ??= getPlatformProperties()); -}; - -export const safeJSON = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return undefined; - } -}; - -// https://url.spec.whatwg.org/#url-scheme-string -const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; -const isAbsoluteURL = (url: string): boolean => { - return startsWithSchemeRegexp.test(url); -}; - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -const validatePositiveInteger = (name: string, n: unknown): number => { - if (typeof n !== 'number' || !Number.isInteger(n)) { - throw new AnthropicError(`${name} must be an integer`); - } - if (n < 0) { - throw new AnthropicError(`${name} must be a positive integer`); - } - return n; -}; - -export const castToError = (err: any): Error => { - if (err instanceof Error) return err; - if (typeof err === 'object' && err !== null) { - try { - return new Error(JSON.stringify(err)); - } catch {} - } - return new Error(String(err)); -}; - -export const ensurePresent = (value: T | null | undefined): T => { - if (value == null) throw new AnthropicError(`Expected a value to be given but received ${value} instead.`); - return value; -}; - -/** - * Read an environment variable. - * - * Trims beginning and trailing whitespace. - * - * Will return undefined if the environment variable doesn't exist or cannot be accessed. - */ -export const readEnv = (env: string): string | undefined => { - if (typeof process !== 'undefined') { - return process.env?.[env]?.trim() ?? undefined; - } - if (typeof Deno !== 'undefined') { - return Deno.env?.get?.(env)?.trim(); - } - return undefined; -}; - -export const coerceInteger = (value: unknown): number => { - if (typeof value === 'number') return Math.round(value); - if (typeof value === 'string') return parseInt(value, 10); - - throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceFloat = (value: unknown): number => { - if (typeof value === 'number') return value; - if (typeof value === 'string') return parseFloat(value); - - throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceBoolean = (value: unknown): boolean => { - if (typeof value === 'boolean') return value; - if (typeof value === 'string') return value === 'true'; - return Boolean(value); -}; - -export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceInteger(value); -}; - -export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceFloat(value); -}; - -export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value === undefined) { - return undefined; - } - return coerceBoolean(value); -}; - -// https://stackoverflow.com/a/34491287 -export function isEmptyObj(obj: Object | null | undefined): boolean { - if (!obj) return true; - for (const _k in obj) return false; - return true; -} - -// https://eslint.org/docs/latest/rules/no-prototype-builtins -export function hasOwn(obj: Object, key: string): boolean { - return Object.prototype.hasOwnProperty.call(obj, key); -} - -/** - * Copies headers from "newHeaders" onto "targetHeaders", - * using lower-case for all properties, - * ignoring any keys with undefined values, - * and deleting any keys with null values. - */ -function applyHeadersMut(targetHeaders: Headers, newHeaders: Headers): void { - for (const k in newHeaders) { - if (!hasOwn(newHeaders, k)) continue; - const lowerKey = k.toLowerCase(); - if (!lowerKey) continue; - - const val = newHeaders[k]; - - if (val === null) { - delete targetHeaders[lowerKey]; - } else if (val !== undefined) { - targetHeaders[lowerKey] = val; - } - } -} - -export function debug(action: string, ...args: any[]) { - if (typeof process !== 'undefined' && process?.env?.['DEBUG'] === 'true') { - console.log(`Anthropic:DEBUG:${action}`, ...args); - } -} - -/** - * https://stackoverflow.com/a/2117523 - */ -const uuid4 = () => { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - const v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); -}; - -export const isRunningInBrowser = () => { - return ( - // @ts-ignore - typeof window !== 'undefined' && - // @ts-ignore - typeof window.document !== 'undefined' && - // @ts-ignore - typeof navigator !== 'undefined' - ); -}; - -export interface HeadersProtocol { - get: (header: string) => string | null | undefined; -} -export type HeadersLike = Record | HeadersProtocol; - -export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { - return typeof headers?.get === 'function'; -}; - -export const getRequiredHeader = (headers: HeadersLike | Headers, header: string): string => { - const foundHeader = getHeader(headers, header); - if (foundHeader === undefined) { - throw new Error(`Could not find ${header} header`); - } - return foundHeader; -}; - -export const getHeader = (headers: HeadersLike | Headers, header: string): string | undefined => { - const lowerCasedHeader = header.toLowerCase(); - if (isHeadersProtocol(headers)) { - // to deal with the case where the header looks like Stainless-Event-Id - const intercapsHeader = - header[0]?.toUpperCase() + - header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase()); - for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) { - const value = headers.get(key); - if (value) { - return value; - } - } - } - - for (const [key, value] of Object.entries(headers)) { - if (key.toLowerCase() === lowerCasedHeader) { - if (Array.isArray(value)) { - if (value.length <= 1) return value[0]; - console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`); - return value[0]; - } - return value; - } - } - - return undefined; -}; - -/** - * Encodes a string to Base64 format. - */ -export const toBase64 = (str: string | null | undefined): string => { - if (!str) return ''; - if (typeof Buffer !== 'undefined') { - return Buffer.from(str).toString('base64'); - } - - if (typeof btoa !== 'undefined') { - return btoa(str); - } - - throw new AnthropicError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined'); -}; - -export function isObj(obj: unknown): obj is Record { - return obj != null && typeof obj === 'object' && !Array.isArray(obj); -} diff --git a/src/error.ts b/src/error.ts index 64525004..1c1d9a58 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { castToError, Headers } from './core'; +import { castToError } from './internal/errors'; export class AnthropicError extends Error {} @@ -16,13 +16,13 @@ export class APIError< /** JSON body of the response that caused the error */ readonly error: TError; - readonly request_id: string | null | undefined; + readonly requestID: string | null | undefined; constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { super(`${APIError.makeMessage(status, error, message)}`); this.status = status; this.headers = headers; - this.request_id = headers?.['request-id']; + this.requestID = headers?.get('request-id'); this.error = error; } diff --git a/src/index.ts b/src/index.ts index bfca4fc8..b1ded2ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,409 +1,17 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { type Agent } from './_shims/index'; -import * as Core from './core'; -import * as Errors from './error'; -import * as Pagination from './pagination'; -import { type PageParams, PageResponse } from './pagination'; -import * as Uploads from './uploads'; -import * as API from './resources/index'; -import { - Completion, - CompletionCreateParams, - CompletionCreateParamsNonStreaming, - CompletionCreateParamsStreaming, - Completions, -} from './resources/completions'; -import { ModelInfo, ModelInfosPage, ModelListParams, Models } from './resources/models'; -import { - AnthropicBeta, - Beta, - BetaAPIError, - BetaAuthenticationError, - BetaBillingError, - BetaError, - BetaErrorResponse, - BetaGatewayTimeoutError, - BetaInvalidRequestError, - BetaNotFoundError, - BetaOverloadedError, - BetaPermissionError, - BetaRateLimitError, -} from './resources/beta/beta'; -import { - Base64PDFSource, - CacheControlEphemeral, - ContentBlock, - ContentBlockDeltaEvent, - ContentBlockParam, - ContentBlockStartEvent, - ContentBlockStopEvent, - DocumentBlockParam, - ImageBlockParam, - InputJSONDelta, - Message, - MessageCountTokensParams, - MessageCreateParams, - MessageCreateParamsNonStreaming, - MessageCreateParamsStreaming, - MessageDeltaEvent, - MessageDeltaUsage, - MessageParam, - MessageStartEvent, - MessageStopEvent, - MessageStreamEvent, - MessageStreamParams, - MessageTokensCount, - Messages, - Metadata, - Model, - RawContentBlockDeltaEvent, - RawContentBlockStartEvent, - RawContentBlockStopEvent, - RawMessageDeltaEvent, - RawMessageStartEvent, - RawMessageStopEvent, - RawMessageStreamEvent, - TextBlock, - TextBlockParam, - TextDelta, - Tool, - ToolChoice, - ToolChoiceAny, - ToolChoiceAuto, - ToolChoiceTool, - ToolResultBlockParam, - ToolUseBlock, - ToolUseBlockParam, - Usage, -} from './resources/messages/messages'; +export { Anthropic as default } from './client'; -export interface ClientOptions { - /** - * Defaults to process.env['ANTHROPIC_API_KEY']. - */ - apiKey?: string | null | undefined; - - /** - * Defaults to process.env['ANTHROPIC_AUTH_TOKEN']. - */ - authToken?: 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. - */ - timeout?: number; - - /** - * An HTTP agent used to manage HTTP(S) connections. - * - * If not provided, an agent will be constructed by default in the Node.js environment, - * otherwise no agent is used. - */ - httpAgent?: Agent; - - /** - * Specify a custom `fetch` function implementation. - * - * If not provided, we use `node-fetch` on Node.js and otherwise expect that `fetch` is - * defined globally. - */ - fetch?: Core.Fetch | 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; - - /** - * Default headers to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * header to `undefined` or `null` in request options. - */ - defaultHeaders?: Core.Headers; - - /** - * 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?: Core.DefaultQuery; - - /** - * 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; -} - -/** - * API Client for interfacing with the Anthropic API. - */ -export class Anthropic extends Core.APIClient { - apiKey: string | null; - authToken: string | null; - - private _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} [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 {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. - * @param {Core.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 {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. - * @param {Core.DefaultQuery} 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 = Core.readEnv('ANTHROPIC_BASE_URL'), - apiKey = Core.readEnv('ANTHROPIC_API_KEY') ?? null, - authToken = Core.readEnv('ANTHROPIC_AUTH_TOKEN') ?? null, - ...opts - }: ClientOptions = {}) { - const options: ClientOptions = { - apiKey, - authToken, - ...opts, - baseURL: baseURL || `https://api.anthropic.com`, - }; - - if (!options.dangerouslyAllowBrowser && Core.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", - ); - } - - super({ - baseURL: options.baseURL!, - timeout: options.timeout ?? 600000 /* 10 minutes */, - httpAgent: options.httpAgent, - maxRetries: options.maxRetries, - fetch: options.fetch, - }); - - this._options = options; - - this.apiKey = apiKey; - this.authToken = authToken; - } - - completions: API.Completions = new API.Completions(this); - messages: API.Messages = new API.Messages(this); - models: API.Models = new API.Models(this); - beta: API.Beta = new API.Beta(this); - - protected override defaultQuery(): Core.DefaultQuery | undefined { - return this._options.defaultQuery; - } - - protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers { - return { - ...super.defaultHeaders(opts), - ...(this._options.dangerouslyAllowBrowser ? - { 'anthropic-dangerous-direct-browser-access': 'true' } - : undefined), - 'anthropic-version': '2023-06-01', - ...this._options.defaultHeaders, - }; - } - - protected override validateHeaders(headers: Core.Headers, customHeaders: Core.Headers) { - if (this.apiKey && headers['x-api-key']) { - return; - } - if (customHeaders['x-api-key'] === null) { - return; - } - - if (this.authToken && headers['authorization']) { - return; - } - if (customHeaders['authorization'] === null) { - 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', - ); - } - - protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers { - const apiKeyAuth = this.apiKeyAuth(opts); - const bearerAuth = this.bearerAuth(opts); - - if (apiKeyAuth != null && !Core.isEmptyObj(apiKeyAuth)) { - return apiKeyAuth; - } - - if (bearerAuth != null && !Core.isEmptyObj(bearerAuth)) { - return bearerAuth; - } - return {}; - } - - protected apiKeyAuth(opts: Core.FinalRequestOptions): Core.Headers { - if (this.apiKey == null) { - return {}; - } - return { 'X-Api-Key': this.apiKey }; - } - - protected bearerAuth(opts: Core.FinalRequestOptions): Core.Headers { - if (this.authToken == null) { - return {}; - } - return { Authorization: `Bearer ${this.authToken}` }; - } - - static Anthropic = this; - static HUMAN_PROMPT = '\n\nHuman:'; - static AI_PROMPT = '\n\nAssistant:'; - static DEFAULT_TIMEOUT = 600000; // 10 minutes - - static AnthropicError = Errors.AnthropicError; - static APIError = Errors.APIError; - static APIConnectionError = Errors.APIConnectionError; - static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; - static APIUserAbortError = Errors.APIUserAbortError; - static NotFoundError = Errors.NotFoundError; - static ConflictError = Errors.ConflictError; - static RateLimitError = Errors.RateLimitError; - static BadRequestError = Errors.BadRequestError; - static AuthenticationError = Errors.AuthenticationError; - static InternalServerError = Errors.InternalServerError; - static PermissionDeniedError = Errors.PermissionDeniedError; - static UnprocessableEntityError = Errors.UnprocessableEntityError; - - static toFile = Uploads.toFile; - static fileFromPath = Uploads.fileFromPath; -} - -Anthropic.Completions = Completions; -Anthropic.Messages = Messages; -Anthropic.Models = Models; -Anthropic.ModelInfosPage = ModelInfosPage; -Anthropic.Beta = Beta; -export declare namespace Anthropic { - export type RequestOptions = Core.RequestOptions; - - export import Page = Pagination.Page; - export { type PageParams as PageParams, type PageResponse as PageResponse }; - - export { - Completions as Completions, - type Completion as Completion, - type CompletionCreateParams as CompletionCreateParams, - type CompletionCreateParamsNonStreaming as CompletionCreateParamsNonStreaming, - type CompletionCreateParamsStreaming as CompletionCreateParamsStreaming, - }; - - export { - Messages as Messages, - type Base64PDFSource as Base64PDFSource, - type CacheControlEphemeral as CacheControlEphemeral, - type ContentBlock as ContentBlock, - type ContentBlockDeltaEvent as ContentBlockDeltaEvent, - type ContentBlockParam as ContentBlockParam, - type ContentBlockStartEvent as ContentBlockStartEvent, - type ContentBlockStopEvent as ContentBlockStopEvent, - type DocumentBlockParam as DocumentBlockParam, - type ImageBlockParam as ImageBlockParam, - type InputJSONDelta as InputJSONDelta, - type Message as Message, - type MessageDeltaEvent as MessageDeltaEvent, - type MessageDeltaUsage as MessageDeltaUsage, - type MessageParam as MessageParam, - type MessageStartEvent as MessageStartEvent, - type MessageStopEvent as MessageStopEvent, - type MessageStreamEvent as MessageStreamEvent, - type MessageTokensCount as MessageTokensCount, - type Metadata as Metadata, - type Model as Model, - type RawContentBlockDeltaEvent as RawContentBlockDeltaEvent, - type RawContentBlockStartEvent as RawContentBlockStartEvent, - type RawContentBlockStopEvent as RawContentBlockStopEvent, - type RawMessageDeltaEvent as RawMessageDeltaEvent, - type RawMessageStartEvent as RawMessageStartEvent, - type RawMessageStopEvent as RawMessageStopEvent, - type RawMessageStreamEvent as RawMessageStreamEvent, - type TextBlock as TextBlock, - type TextBlockParam as TextBlockParam, - type TextDelta as TextDelta, - type Tool as Tool, - type ToolChoice as ToolChoice, - type ToolChoiceAny as ToolChoiceAny, - type ToolChoiceAuto as ToolChoiceAuto, - type ToolChoiceTool as ToolChoiceTool, - type ToolResultBlockParam as ToolResultBlockParam, - type ToolUseBlock as ToolUseBlock, - type ToolUseBlockParam as ToolUseBlockParam, - type Usage as Usage, - type MessageCreateParams as MessageCreateParams, - type MessageCreateParamsNonStreaming as MessageCreateParamsNonStreaming, - type MessageCreateParamsStreaming as MessageCreateParamsStreaming, - type MessageStreamParams as MessageStreamParams, - type MessageCountTokensParams as MessageCountTokensParams, - }; - - export { - Models as Models, - type ModelInfo as ModelInfo, - ModelInfosPage as ModelInfosPage, - type ModelListParams as ModelListParams, - }; - - export { - Beta as Beta, - type AnthropicBeta as AnthropicBeta, - type BetaAPIError as BetaAPIError, - type BetaAuthenticationError as BetaAuthenticationError, - type BetaBillingError as BetaBillingError, - type BetaError as BetaError, - type BetaErrorResponse as BetaErrorResponse, - type BetaGatewayTimeoutError as BetaGatewayTimeoutError, - type BetaInvalidRequestError as BetaInvalidRequestError, - type BetaNotFoundError as BetaNotFoundError, - type BetaOverloadedError as BetaOverloadedError, - type BetaPermissionError as BetaPermissionError, - type BetaRateLimitError as BetaRateLimitError, - }; - - export type APIErrorObject = API.APIErrorObject; - export type AuthenticationError = API.AuthenticationError; - export type BillingError = API.BillingError; - export type ErrorObject = API.ErrorObject; - export type ErrorResponse = API.ErrorResponse; - export type GatewayTimeoutError = API.GatewayTimeoutError; - export type InvalidRequestError = API.InvalidRequestError; - export type NotFoundError = API.NotFoundError; - export type OverloadedError = API.OverloadedError; - export type PermissionError = API.PermissionError; - export type RateLimitError = API.RateLimitError; -} -export const { HUMAN_PROMPT, AI_PROMPT } = Anthropic; - -export { toFile, fileFromPath } from './uploads'; +export { + multipartFormRequestOptions, + maybeMultipartFormRequestOptions, + Uploadable, + createForm, + toFile, +} from './uploads'; +export { APIPromise } from './api-promise'; +export { BaseAnthropic, Anthropic, ClientOptions, HUMAN_PROMPT, AI_PROMPT } from './client'; +export { PagePromise } from './pagination'; export { AnthropicError, APIError, @@ -419,5 +27,3 @@ export { PermissionDeniedError, UnprocessableEntityError, } from './error'; - -export default Anthropic; diff --git a/src/internal/builtin-types.ts b/src/internal/builtin-types.ts new file mode 100644 index 00000000..ca1be792 --- /dev/null +++ b/src/internal/builtin-types.ts @@ -0,0 +1,79 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type Fetch = typeof fetch; + +/** + * An alias to the builtin `RequestInit` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit + */ +type _RequestInit = RequestInit; + +/** + * An alias to the builtin `Response` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/Response + */ +type _Response = Response; + +/** + * The type for the first argument to `fetch`. + * + * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource + */ +type _RequestInfo = Request | URL | string; + +/** + * The type for constructing `RequestInit` Headers. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers + */ +type _HeadersInit = RequestInit['headers']; + +/** + * The type for constructing `RequestInit` body. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#body + */ +type _BodyInit = RequestInit['body']; + +export type { + _BodyInit as BodyInit, + _HeadersInit as HeadersInit, + _RequestInfo as RequestInfo, + _RequestInit as RequestInit, + _Response as Response, +}; + +/** + * A copy of the builtin `EndingType` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 + */ +type EndingType = 'native' | 'transparent'; + +/** + * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 + * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options + */ +export interface BlobPropertyBag { + endings?: EndingType; + type?: string; +} + +/** + * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 + * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options + */ +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} diff --git a/src/internal/detect-platform.ts b/src/internal/detect-platform.ts new file mode 100644 index 00000000..24eec519 --- /dev/null +++ b/src/internal/detect-platform.ts @@ -0,0 +1,192 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { VERSION } from '../version'; + +export const isRunningInBrowser = () => { + return ( + // @ts-ignore + typeof window !== 'undefined' && + // @ts-ignore + typeof window.document !== 'undefined' && + // @ts-ignore + typeof navigator !== 'undefined' + ); +}; + +type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; + +/** + * Note this does not detect 'browser'; for that, use getBrowserInfo(). + */ +function getDetectedPlatform(): DetectedPlatform { + if (typeof Deno !== 'undefined' && Deno.build != null) { + return 'deno'; + } + if (typeof EdgeRuntime !== 'undefined') { + return 'edge'; + } + if (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') { + return 'node'; + } + return 'unknown'; +} + +declare const Deno: any; +declare const EdgeRuntime: any; +type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; +type PlatformName = + | 'MacOS' + | 'Linux' + | 'Windows' + | 'FreeBSD' + | 'OpenBSD' + | 'iOS' + | 'Android' + | `Other:${string}` + | 'Unknown'; +type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; +type PlatformProperties = { + 'X-Stainless-Lang': 'js'; + 'X-Stainless-Package-Version': string; + 'X-Stainless-OS': PlatformName; + 'X-Stainless-Arch': Arch; + 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; + 'X-Stainless-Runtime-Version': string; +}; +const getPlatformProperties = (): PlatformProperties => { + const detectedPlatform = getDetectedPlatform(); + if (detectedPlatform === 'deno') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform(Deno.build.os), + 'X-Stainless-Arch': normalizeArch(Deno.build.arch), + 'X-Stainless-Runtime': 'deno', + 'X-Stainless-Runtime-Version': + typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', + }; + } + if (typeof EdgeRuntime !== 'undefined') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': `other:${EdgeRuntime}`, + 'X-Stainless-Runtime': 'edge', + 'X-Stainless-Runtime-Version': process.version, + }; + } + // Check if Node.js + if (detectedPlatform === 'node') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform(process.platform), + 'X-Stainless-Arch': normalizeArch(process.arch), + 'X-Stainless-Runtime': 'node', + 'X-Stainless-Runtime-Version': process.version, + }; + } + + const browserInfo = getBrowserInfo(); + if (browserInfo) { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, + 'X-Stainless-Runtime-Version': browserInfo.version, + }; + } + + // TODO add support for Cloudflare workers, etc. + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': 'unknown', + 'X-Stainless-Runtime-Version': 'unknown', + }; +}; + +type BrowserInfo = { + browser: Browser; + version: string; +}; + +declare const navigator: { userAgent: string } | undefined; + +// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts +function getBrowserInfo(): BrowserInfo | null { + if (typeof navigator === 'undefined' || !navigator) { + return null; + } + + // NOTE: The order matters here! + const browserPatterns = [ + { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, + ]; + + // Find the FIRST matching browser + for (const { key, pattern } of browserPatterns) { + const match = pattern.exec(navigator.userAgent); + if (match) { + const major = match[1] || 0; + const minor = match[2] || 0; + const patch = match[3] || 0; + + return { browser: key, version: `${major}.${minor}.${patch}` }; + } + } + + return null; +} + +const normalizeArch = (arch: string): Arch => { + // Node docs: + // - https://nodejs.org/api/process.html#processarch + // Deno docs: + // - https://doc.deno.land/deno/stable/~/Deno.build + if (arch === 'x32') return 'x32'; + if (arch === 'x86_64' || arch === 'x64') return 'x64'; + if (arch === 'arm') return 'arm'; + if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; + if (arch) return `other:${arch}`; + return 'unknown'; +}; + +const normalizePlatform = (platform: string): PlatformName => { + // Node platforms: + // - https://nodejs.org/api/process.html#processplatform + // Deno platforms: + // - https://doc.deno.land/deno/stable/~/Deno.build + // - https://github.com/denoland/deno/issues/14799 + + platform = platform.toLowerCase(); + + // NOTE: this iOS check is untested and may not work + // Node does not work natively on IOS, there is a fork at + // https://github.com/nodejs-mobile/nodejs-mobile + // however it is unknown at the time of writing how to detect if it is running + if (platform.includes('ios')) return 'iOS'; + if (platform === 'android') return 'Android'; + if (platform === 'darwin') return 'MacOS'; + if (platform === 'win32') return 'Windows'; + if (platform === 'freebsd') return 'FreeBSD'; + if (platform === 'openbsd') return 'OpenBSD'; + if (platform === 'linux') return 'Linux'; + if (platform) return `Other:${platform}`; + return 'Unknown'; +}; + +let _platformHeaders: PlatformProperties; +export const getPlatformHeaders = () => { + return (_platformHeaders ??= getPlatformProperties()); +}; diff --git a/src/internal/errors.ts b/src/internal/errors.ts new file mode 100644 index 00000000..08321f31 --- /dev/null +++ b/src/internal/errors.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export function isAbortError(err: unknown) { + return ( + (err instanceof Error && err.name === 'AbortError') || + (typeof err === 'object' && err && 'name' in err && (err as any).name === 'AbortError') + ); +} + +export const castToError = (err: any): Error => { + if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + return new Error(JSON.stringify(err)); + } catch {} + } + return new Error(err); +}; diff --git a/src/internal/headers.ts b/src/internal/headers.ts new file mode 100644 index 00000000..a110a120 --- /dev/null +++ b/src/internal/headers.ts @@ -0,0 +1,96 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +type HeaderValue = string | undefined | null; +export type HeadersLike = + | Headers + | readonly [string, HeaderValue][] + | Record + | undefined + | null + | NullableHeaders; + +const brand_privateNullableHeaders = Symbol('brand.privateNullableHeaders'); + +/** + * @internal + * Users can pass explicit nulls to unset default headers. When we parse them + * into a standard headers type we need to preserve that information. + */ +export type NullableHeaders = { + /** Brand check, prevent users from creating a NullableHeaders. */ + [brand_privateNullableHeaders]: true; + /** Parsed headers. */ + values: Headers; + /** Set of lowercase header names explicitly set to null. */ + nulls: Set; +}; + +const isArray = Array.isArray as (val: unknown) => val is readonly unknown[]; + +function* iterateHeaders(headers: HeadersLike): IterableIterator { + if (!headers) return; + + if (brand_privateNullableHeaders in headers) { + const { values, nulls } = headers; + yield* values.entries(); + for (const name of nulls) { + yield [name, null]; + } + return; + } + + let shouldClear = false; + let iter: Iterable; + if (headers instanceof Headers) { + iter = headers.entries(); + } else if (isArray(headers)) { + iter = headers; + } else { + shouldClear = true; + iter = Object.entries(headers ?? {}); + } + for (let row of iter) { + const name = row[0]; + const values = isArray(row[1]) ? row[1] : [row[1]]; + let didClear = false; + for (const value of values) { + if (value === undefined) continue; + + // Objects keys always overwrite older headers, they never append. + // Yield a null to clear the header before adding the new values. + if (shouldClear && !didClear) { + didClear = true; + yield [name, null]; + } + yield [name, value]; + } + } +} + +export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { + const targetHeaders = new Headers(); + const nullHeaders = new Set(); + const seenHeaders = new Set(); + for (const headers of newHeaders) { + for (const [name, value] of iterateHeaders(headers)) { + const lowerName = name.toLowerCase(); + if (!seenHeaders.has(lowerName)) { + targetHeaders.delete(name); + seenHeaders.add(lowerName); + } + if (value === null) { + targetHeaders.delete(name); + nullHeaders.add(lowerName); + } else { + targetHeaders.append(name, value); + nullHeaders.delete(lowerName); + } + } + } + return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; +}; + +export const isEmptyHeaders = (headers: HeadersLike) => { + for (const _ of iterateHeaders(headers)) return false; + return true; +}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts new file mode 100644 index 00000000..df65a386 --- /dev/null +++ b/src/internal/parse.ts @@ -0,0 +1,53 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { debug } from './utils/log'; +import { FinalRequestOptions } from './request-options'; +import { Stream } from '../streaming'; + +export type APIResponseProps = { + response: Response; + options: FinalRequestOptions; + controller: AbortController; +}; + +export async function defaultParseResponse(props: APIResponseProps): Promise { + const { response } = props; + if (props.options.stream) { + debug('response', response.status, response.url, response.headers, response.body); + + // Note: there is an invariant here that isn't represented in the type system + // that if you set `stream: true` the response type must also be `Stream` + + if (props.options.__streamClass) { + return props.options.__streamClass.fromSSEResponse(response, props.controller) as any; + } + + return Stream.fromSSEResponse(response, props.controller) as any; + } + + // fetch refuses to read the body when the status code is 204. + if (response.status === 204) { + return null as T; + } + + if (props.options.__binaryResponse) { + return response as unknown as T; + } + + const contentType = response.headers.get('content-type'); + const isJSON = + contentType?.includes('application/json') || contentType?.includes('application/vnd.api+json'); + if (isJSON) { + const json = await response.json(); + + debug('response', response.status, response.url, response.headers, json); + + return json as T; + } + + const text = await response.text(); + debug('response', response.status, response.url, response.headers, text); + + // TODO handle blob, arraybuffer, other content types, etc. + return text as unknown as T; +} diff --git a/src/internal/polyfill/crypto.node.d.ts b/src/internal/polyfill/crypto.node.d.ts new file mode 100644 index 00000000..dc7caac8 --- /dev/null +++ b/src/internal/polyfill/crypto.node.d.ts @@ -0,0 +1,10 @@ +export declare const crypto: { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) */ + getRandomValues(array: T): T; + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) + */ + randomUUID?: () => string; +}; diff --git a/src/internal/polyfill/crypto.node.js b/src/internal/polyfill/crypto.node.js new file mode 100644 index 00000000..83062a3d --- /dev/null +++ b/src/internal/polyfill/crypto.node.js @@ -0,0 +1,11 @@ +if (typeof require !== 'undefined') { + if (globalThis.crypto) { + exports.crypto = globalThis.crypto; + } else { + try { + // Use [require][0](...) and not require(...) so bundlers don't try to bundle the + // crypto module. + exports.crypto = [require][0]('node:crypto').webcrypto; + } catch (e) {} + } +} diff --git a/src/internal/polyfill/crypto.node.mjs b/src/internal/polyfill/crypto.node.mjs new file mode 100644 index 00000000..24c6f3b9 --- /dev/null +++ b/src/internal/polyfill/crypto.node.mjs @@ -0,0 +1,2 @@ +import * as mod from './crypto.node.js'; +export const crypto = globalThis.crypto || mod.crypto; diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts new file mode 100644 index 00000000..b2a59bfd --- /dev/null +++ b/src/internal/polyfill/file.node.d.ts @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +export {}; diff --git a/src/internal/polyfill/file.node.js b/src/internal/polyfill/file.node.js new file mode 100644 index 00000000..eba997e1 --- /dev/null +++ b/src/internal/polyfill/file.node.js @@ -0,0 +1,17 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +if (typeof require !== 'undefined') { + if (!globalThis.File) { + try { + // Use [require][0](...) and not require(...) so bundlers don't try to bundle the + // buffer module. + globalThis.File = [require][0]('node:buffer').File; + } catch (e) {} + } +} diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs new file mode 100644 index 00000000..520dcb84 --- /dev/null +++ b/src/internal/polyfill/file.node.mjs @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +import './file.node.js'; diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts new file mode 100644 index 00000000..d4a5c66d --- /dev/null +++ b/src/internal/request-options.ts @@ -0,0 +1,71 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { NullableHeaders } from './headers'; + +import type { Agent } from './shims'; +import type { BodyInit } from './builtin-types'; +import { isEmptyObj, hasOwn } from './utils/values'; +import { Stream } from '../streaming'; +import type { HTTPMethod, KeysEnum } from './types'; +import { type HeadersLike } from './headers'; + +export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; + +export type RequestOptions = { + method?: HTTPMethod; + path?: string; + query?: object | undefined | null; + body?: unknown; + headers?: HeadersLike; + maxRetries?: number; + stream?: boolean | undefined; + timeout?: number; + httpAgent?: Agent; + signal?: AbortSignal | undefined | null; + idempotencyKey?: string; + + __binaryResponse?: boolean | undefined; + __streamClass?: typeof Stream; +}; + +// This is required so that we can determine if a given object matches the RequestOptions +// type at runtime. While this requires duplication, it is enforced by the TypeScript +// compiler such that any missing / extraneous keys will cause an error. +const requestOptionsKeys: KeysEnum = { + method: true, + path: true, + query: true, + body: true, + headers: true, + + maxRetries: true, + stream: true, + timeout: true, + httpAgent: true, + signal: true, + idempotencyKey: true, + + __binaryResponse: true, + __streamClass: true, +}; + +export const isRequestOptions = (obj: unknown): obj is RequestOptions => { + return ( + typeof obj === 'object' && + obj !== null && + !isEmptyObj(obj) && + Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k)) + ); +}; + +export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; +export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; + +export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { + return { + bodyHeaders: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; diff --git a/src/internal/shim-types.d.ts b/src/internal/shim-types.d.ts new file mode 100644 index 00000000..fe48144f --- /dev/null +++ b/src/internal/shim-types.d.ts @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +/** + * In order to properly access the global `NodeJS` type, if it's available, we + * need to make use of declaration shadowing. Without this, any checks for the + * presence of `NodeJS.ReadableStream` will fail. + */ +declare namespace NodeJS { + interface ReadableStream {} +} + +type HasProperties = keyof T extends never ? false : true; + +// @ts-ignore +type _ReadableStream = + // @ts-ignore + HasProperties extends true ? NodeJS.ReadableStream : ReadableStream; + +// @ts-ignore +declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; +export { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts new file mode 100644 index 00000000..bb5a2037 --- /dev/null +++ b/src/internal/shims.ts @@ -0,0 +1,124 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. + * + * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error + * messages in cases where an environment isn't fully supported. + */ + +import { type Fetch } from './builtin-types'; +import { type ReadableStream } from './shim-types'; + +/** + * A minimal copy of the `Agent` type from `undici-types` so we can + * use it in the `ClientOptions` type. + * + * https://nodejs.org/api/http.html#class-httpagent + */ +export interface Agent { + dispatch(options: any, handler: any): boolean; + closed: boolean; + destroyed: boolean; +} + +export function getDefaultFetch(): Fetch { + if (typeof fetch !== 'undefined') { + return fetch; + } + + throw new Error( + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new Anthropic({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + ); +} + +/** + * A minimal copy of the NodeJS `stream.Readable` class so that we can + * accept the NodeJS types in certain places, e.g. file uploads + * + * https://nodejs.org/api/stream.html#class-streamreadable + */ +export interface ReadableLike { + readable: boolean; + readonly readableEnded: boolean; + readonly readableFlowing: boolean | null; + readonly readableHighWaterMark: number; + readonly readableLength: number; + readonly readableObjectMode: boolean; + destroyed: boolean; + read(size?: number): any; + pause(): this; + resume(): this; + isPaused(): boolean; + destroy(error?: Error): this; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +/** + * Determines if the given value looks like a NodeJS `stream.Readable` + * object and that it is readable, i.e. has not been consumed. + * + * https://nodejs.org/api/stream.html#class-streamreadable + */ +export function isReadableLike(value: any) { + // We declare our own class of Readable here, so it's not feasible to + // do an 'instanceof' check. Instead, check for Readable-like properties. + return !!value && value.readable === true && typeof value.read === 'function'; +} + +/** + * A minimal copy of the NodeJS `fs.ReadStream` class for usage within file uploads. + * + * https://nodejs.org/api/fs.html#class-fsreadstream + */ +export interface FsReadStreamLike extends ReadableLike { + path: {}; // real type is string | Buffer but we can't reference `Buffer` here +} + +/** + * Determines if the given value looks like a NodeJS `fs.ReadStream` + * object. + * + * This just checks if the object matches our `Readable` interface + * and defines a `path` property, there may be false positives. + * + * https://nodejs.org/api/fs.html#class-fsreadstream + */ +export function isFsReadStreamLike(value: any): value is FsReadStreamLike { + return isReadableLike(value) && 'path' in value; +} + +type ReadableStreamArgs = ConstructorParameters; + +export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { + const ReadableStream = (globalThis as any).ReadableStream; + if (typeof ReadableStream === 'undefined') { + // Note: All of the platforms / runtimes we officially support already define + // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. + throw new Error( + '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', + ); + } + + return new ReadableStream(...args); +} + +export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { + let iter: AsyncIterator | Iterator = + Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); + + return makeReadableStream({ + start() {}, + async pull(controller) { + const { done, value } = await iter.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel() { + await iter.return?.(); + }, + }); +} diff --git a/src/internal/types.ts b/src/internal/types.ts new file mode 100644 index 00000000..99740c34 --- /dev/null +++ b/src/internal/types.ts @@ -0,0 +1,6 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type PromiseOrValue = T | Promise; +export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +export type KeysEnum = { [P in keyof Required]: true }; diff --git a/src/internal/utils.ts b/src/internal/utils.ts new file mode 100644 index 00000000..3cbfacce --- /dev/null +++ b/src/internal/utils.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './utils/values'; +export * from './utils/base64'; +export * from './utils/env'; +export * from './utils/log'; +export * from './utils/uuid'; +export * from './utils/sleep'; diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts new file mode 100644 index 00000000..12d70740 --- /dev/null +++ b/src/internal/utils/base64.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { AnthropicError } from '../../error'; + +// @ts-ignore +declare const Buffer: typeof import('node:buffer').Buffer; + +export const toBase64 = (data: string | Uint8Array | null | undefined): string => { + if (!data) return ''; + + if (typeof data === 'string') { + data = new TextEncoder().encode(data); + } + + if (typeof Buffer !== 'undefined') { + return Buffer.from(data).toString('base64'); + } + + if (typeof btoa !== 'undefined') { + return btoa(String.fromCharCode.apply(null, data as any)); + } + + throw new AnthropicError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); +}; + +export const fromBase64 = (str: string): Uint8Array => { + if (typeof Buffer !== 'undefined') { + return new Uint8Array(Buffer.from(str, 'base64')); + } + + if (typeof atob !== 'undefined') { + return new Uint8Array( + atob(str) + .split('') + .map((c) => c.charCodeAt(0)), + ); + } + + throw new AnthropicError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); +}; diff --git a/src/internal/utils/env.ts b/src/internal/utils/env.ts new file mode 100644 index 00000000..d9b1dc4a --- /dev/null +++ b/src/internal/utils/env.ts @@ -0,0 +1,20 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +declare const Deno: any; + +/** + * Read an environment variable. + * + * Trims beginning and trailing whitespace. + * + * Will return undefined if the environment variable doesn't exist or cannot be accessed. + */ +export const readEnv = (env: string): string | undefined => { + if (typeof process !== 'undefined') { + return process.env?.[env]?.trim() ?? undefined; + } + if (typeof Deno !== 'undefined') { + return Deno.env?.get?.(env)?.trim(); + } + return undefined; +}; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts new file mode 100644 index 00000000..7fe7ccba --- /dev/null +++ b/src/internal/utils/log.ts @@ -0,0 +1,9 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { readEnv } from './env'; + +export function debug(action: string, ...args: any[]) { + if (readEnv('DEBUG') === 'true') { + console.log(`Anthropic:DEBUG:${action}`, ...args); + } +} diff --git a/src/internal/utils/sleep.ts b/src/internal/utils/sleep.ts new file mode 100644 index 00000000..fcf4e083 --- /dev/null +++ b/src/internal/utils/sleep.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/internal/utils/uuid.ts b/src/internal/utils/uuid.ts new file mode 100644 index 00000000..6c43f81d --- /dev/null +++ b/src/internal/utils/uuid.ts @@ -0,0 +1,13 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { crypto } from '../polyfill/crypto.node'; + +/** + * https://stackoverflow.com/a/2117523 + */ +export function uuid4() { + if (crypto.randomUUID) return crypto.randomUUID(); + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => + (+c ^ (crypto.getRandomValues(new Uint8Array(1))[0]! & (15 >> (+c / 4)))).toString(16), + ); +} diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts new file mode 100644 index 00000000..68b55f9c --- /dev/null +++ b/src/internal/utils/values.ts @@ -0,0 +1,94 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { AnthropicError } from '../../error'; + +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; + +export const isAbsoluteURL = (url: string): boolean => { + return startsWithSchemeRegexp.test(url); +}; + +/** Returns an object if the given value isn't an object, otherwise returns as-is */ +export function maybeObj(x: unknown): object { + if (typeof x !== 'object') { + return {}; + } + + return x ?? {}; +} + +// https://stackoverflow.com/a/34491287 +export function isEmptyObj(obj: Object | null | undefined): boolean { + if (!obj) return true; + for (const _k in obj) return false; + return true; +} + +// https://eslint.org/docs/latest/rules/no-prototype-builtins +export function hasOwn(obj: Object, key: string): boolean { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +export function isObj(obj: unknown): obj is Record { + return obj != null && typeof obj === 'object' && !Array.isArray(obj); +} + +export const ensurePresent = (value: T | null | undefined): T => { + if (value == null) { + throw new AnthropicError(`Expected a value to be given but received ${value} instead.`); + } + + return value; +}; + +export const validatePositiveInteger = (name: string, n: unknown): number => { + if (typeof n !== 'number' || !Number.isInteger(n)) { + throw new AnthropicError(`${name} must be an integer`); + } + if (n < 0) { + throw new AnthropicError(`${name} must be a positive integer`); + } + return n; +}; + +export const coerceInteger = (value: unknown): number => { + if (typeof value === 'number') return Math.round(value); + if (typeof value === 'string') return parseInt(value, 10); + + throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceFloat = (value: unknown): number => { + if (typeof value === 'number') return value; + if (typeof value === 'string') return parseFloat(value); + + throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') return value === 'true'; + return Boolean(value); +}; + +export const maybeCoerceInteger = (value: unknown): number | undefined => { + if (value === undefined) { + return undefined; + } + return coerceInteger(value); +}; + +export const maybeCoerceFloat = (value: unknown): number | undefined => { + if (value === undefined) { + return undefined; + } + return coerceFloat(value); +}; + +export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { + if (value === undefined) { + return undefined; + } + return coerceBoolean(value); +}; diff --git a/src/pagination.ts b/src/pagination.ts index ac0d581b..a3d3881f 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -1,6 +1,110 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core'; +import type { Anthropic } from './client'; +import { AnthropicError } from './error'; +import { FinalRequestOptions } from './internal/request-options'; +import { defaultParseResponse } from './internal/parse'; +import { APIPromise } from './api-promise'; +import { type APIResponseProps } from './internal/parse'; +import { maybeObj } from './internal/utils/values'; + +export type PageRequestOptions = Pick; + +export abstract class AbstractPage implements AsyncIterable { + #client: Anthropic; + protected options: FinalRequestOptions; + + protected response: Response; + protected body: unknown; + + constructor(client: Anthropic, response: Response, body: unknown, options: FinalRequestOptions) { + this.#client = client; + this.options = options; + this.response = response; + this.body = body; + } + + abstract nextPageRequestOptions(): PageRequestOptions | null; + + abstract getPaginatedItems(): Item[]; + + hasNextPage(): boolean { + const items = this.getPaginatedItems(); + if (!items.length) return false; + return this.nextPageRequestOptions() != null; + } + + async getNextPage(): Promise { + const nextOptions = this.nextPageRequestOptions(); + if (!nextOptions) { + throw new AnthropicError( + 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', + ); + } + + return await this.#client.requestAPIList(this.constructor as any, nextOptions); + } + + async *iterPages(): AsyncGenerator { + // eslint-disable-next-line @typescript-eslint/no-this-alias + let page: this = this; + yield page; + while (page.hasNextPage()) { + page = await page.getNextPage(); + yield page; + } + } + + async *[Symbol.asyncIterator](): AsyncGenerator { + for await (const page of this.iterPages()) { + for (const item of page.getPaginatedItems()) { + yield item; + } + } + } +} + +/** + * This subclass of Promise will resolve to an instantiated Page once the request completes. + * + * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ +export class PagePromise< + PageClass extends AbstractPage, + Item = ReturnType[number], + > + extends APIPromise + implements AsyncIterable +{ + constructor( + client: Anthropic, + request: Promise, + Page: new (...args: ConstructorParameters) => PageClass, + ) { + super( + request, + async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options), + ); + } + + /** + * Allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ + async *[Symbol.asyncIterator]() { + const page = await this; + for await (const item of page) { + yield item; + } + } +} export interface PageResponse { data: Array; @@ -32,7 +136,7 @@ export class Page extends AbstractPage implements PageResponse last_id: string | null; - constructor(client: APIClient, response: Response, body: PageResponse, options: FinalRequestOptions) { + constructor(client: Anthropic, response: Response, body: PageResponse, options: FinalRequestOptions) { super(client, response, body, options); this.data = body.data || []; @@ -45,27 +149,19 @@ export class Page extends AbstractPage implements PageResponse return this.data ?? []; } - // @deprecated Please use `nextPageInfo()` instead - nextPageParams(): Partial | null { - const info = this.nextPageInfo(); - if (!info) return null; - if ('params' in info) return info.params; - const params = Object.fromEntries(info.url.searchParams); - if (!Object.keys(params).length) return null; - return params; - } - - nextPageInfo(): PageInfo | null { + nextPageRequestOptions(): PageRequestOptions | null { if ((this.options.query as Record)?.['before_id']) { // in reverse - const firstId = this.first_id; - if (!firstId) { + const first_id = this.first_id; + if (!first_id) { return null; } return { - params: { - before_id: firstId, + ...this.options, + query: { + ...maybeObj(this.options.query), + before_id: first_id, }, }; } @@ -76,7 +172,9 @@ export class Page extends AbstractPage implements PageResponse } return { - params: { + ...this.options, + query: { + ...maybeObj(this.options.query), after_id: cursor, }, }; diff --git a/src/resource.ts b/src/resource.ts index 4d0a2661..19d27e77 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import * as Core from './core'; +import { BaseAnthropic } from './client'; export class APIResource { - protected _client: Core.APIClient; + protected _client: BaseAnthropic; - constructor(client: Core.APIClient) { + constructor(client: BaseAnthropic) { this._client = client; } } diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts index e29a187c..29ddfbc4 100644 --- a/src/resources/beta/beta.ts +++ b/src/resources/beta/beta.ts @@ -132,7 +132,6 @@ export interface BetaRateLimitError { } Beta.Models = Models; -Beta.BetaModelInfosPage = BetaModelInfosPage; Beta.Messages = Messages; export declare namespace Beta { @@ -154,7 +153,7 @@ export declare namespace Beta { export { Models as Models, type BetaModelInfo as BetaModelInfo, - BetaModelInfosPage as BetaModelInfosPage, + type BetaModelInfosPage as BetaModelInfosPage, type ModelListParams as ModelListParams, }; diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts index a68f2327..5db36de4 100644 --- a/src/resources/beta/index.ts +++ b/src/resources/beta/index.ts @@ -15,7 +15,6 @@ export { type BetaPermissionError, type BetaRateLimitError, } from './beta'; -export { BetaModelInfosPage, Models, type BetaModelInfo, type ModelListParams } from './models'; export { Messages, type BetaBase64PDFBlock, @@ -58,3 +57,4 @@ export { type MessageCreateParamsStreaming, type MessageCountTokensParams, } from './messages/index'; +export { Models, type BetaModelInfo, type ModelListParams, type BetaModelInfosPage } from './models'; diff --git a/src/resources/beta/messages/batches.ts b/src/resources/beta/messages/batches.ts index b564cb1c..cfb4eb5b 100644 --- a/src/resources/beta/messages/batches.ts +++ b/src/resources/beta/messages/batches.ts @@ -1,11 +1,12 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../../resource'; -import { isRequestOptions } from '../../../core'; -import * as Core from '../../../core'; import * as BetaAPI from '../beta'; -import * as BetaMessagesAPI from './messages'; -import { Page, type PageParams } from '../../../pagination'; +import * as MessagesAPI from '../../messages/messages'; +import * as MessagesMessagesAPI from './messages'; +import { APIPromise } from '../../../api-promise'; +import { Page, type PageParams, PagePromise } from '../../../pagination'; +import { RequestOptions } from '../../../internal/request-options'; import { JSONLDecoder } from '../../../internal/decoders/jsonl'; import { AnthropicError } from '../../../error'; @@ -17,7 +18,7 @@ export class Batches extends APIResource { * once. Once a Message Batch is created, it begins processing immediately. Batches * can take up to 24 hours to complete. */ - create(params: BatchCreateParams, options?: Core.RequestOptions): Core.APIPromise { + create(params: BatchCreateParams, options?: RequestOptions): APIPromise { const { betas, ...body } = params; return this._client.post('/v1/messages/batches?beta=true', { body, @@ -35,21 +36,12 @@ export class Batches extends APIResource { * `results_url` field in the response. */ retrieve( - messageBatchId: string, - params?: BatchRetrieveParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - retrieve(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise; - retrieve( - messageBatchId: string, - params: BatchRetrieveParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.retrieve(messageBatchId, {}, params); - } - const { betas } = params; - return this._client.get(`/v1/messages/batches/${messageBatchId}?beta=true`, { + messageBatchID: string, + params: BatchRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { betas } = params ?? {}; + return this._client.get(`/v1/messages/batches/${messageBatchID}?beta=true`, { ...options, headers: { 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), @@ -63,19 +55,11 @@ export class Batches extends APIResource { * returned first. */ list( - params?: BatchListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - params: BatchListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(params)) { - return this.list({}, params); - } - const { betas, ...query } = params; - return this._client.getAPIList('/v1/messages/batches?beta=true', BetaMessageBatchesPage, { + params: BatchListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + const { betas, ...query } = params ?? {}; + return this._client.getAPIList('/v1/messages/batches?beta=true', Page, { query, ...options, headers: { @@ -91,21 +75,12 @@ export class Batches extends APIResource { * `results_url` field in the response. */ delete( - messageBatchId: string, - params?: BatchDeleteParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - delete(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise; - delete( - messageBatchId: string, - params: BatchDeleteParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.delete(messageBatchId, {}, params); - } - const { betas } = params; - return this._client.delete(`/v1/messages/batches/${messageBatchId}?beta=true`, { + messageBatchID: string, + params: BatchDeleteParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { betas } = params ?? {}; + return this._client.delete(`/v1/messages/batches/${messageBatchID}?beta=true`, { ...options, headers: { 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), @@ -126,21 +101,12 @@ export class Batches extends APIResource { * non-interruptible. */ cancel( - messageBatchId: string, - params?: BatchCancelParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - cancel(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise; - cancel( - messageBatchId: string, - params: BatchCancelParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.cancel(messageBatchId, {}, params); - } - const { betas } = params; - return this._client.post(`/v1/messages/batches/${messageBatchId}/cancel?beta=true`, { + messageBatchID: string, + params: BatchCancelParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { betas } = params ?? {}; + return this._client.post(`/v1/messages/batches/${messageBatchID}/cancel?beta=true`, { ...options, headers: { 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), @@ -156,24 +122,11 @@ export class Batches extends APIResource { * in the Message Batch. Results are not guaranteed to be in the same order as * requests. Use the `custom_id` field to match results to requests. */ - async results( - messageBatchId: string, - params?: BatchResultsParams, - options?: Core.RequestOptions, - ): Promise>; - async results( - messageBatchId: string, - options?: Core.RequestOptions, - ): Promise>; - async results( - messageBatchId: string, - params: BatchResultsParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Promise> { - if (isRequestOptions(params)) { - return this.results(messageBatchId, {}, params); - } - + results( + messageBatchID: string, + params: BatchResultsParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { const batch = await this.retrieve(messageBatchId); if (!batch.results_url) { throw new AnthropicError( @@ -195,7 +148,7 @@ export class Batches extends APIResource { } } -export class BetaMessageBatchesPage extends Page {} +export type BetaMessageBatchesPage = Page; export interface BetaDeletedMessageBatch { /** @@ -438,8 +391,6 @@ export interface BatchResultsParams { betas?: Array; } -Batches.BetaMessageBatchesPage = BetaMessageBatchesPage; - export declare namespace Batches { export { type BetaDeletedMessageBatch as BetaDeletedMessageBatch, @@ -451,7 +402,7 @@ export declare namespace Batches { type BetaMessageBatchRequestCounts as BetaMessageBatchRequestCounts, type BetaMessageBatchResult as BetaMessageBatchResult, type BetaMessageBatchSucceededResult as BetaMessageBatchSucceededResult, - BetaMessageBatchesPage as BetaMessageBatchesPage, + type BetaMessageBatchesPage as BetaMessageBatchesPage, type BatchCreateParams as BatchCreateParams, type BatchRetrieveParams as BatchRetrieveParams, type BatchListParams as BatchListParams, diff --git a/src/resources/beta/messages/index.ts b/src/resources/beta/messages/index.ts index e6b260ab..faf65c59 100644 --- a/src/resources/beta/messages/index.ts +++ b/src/resources/beta/messages/index.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - BetaMessageBatchesPage, Batches, type BetaDeletedMessageBatch, type BetaMessageBatch, @@ -18,6 +17,7 @@ export { type BatchDeleteParams, type BatchCancelParams, type BatchResultsParams, + type BetaMessageBatchesPage, } from './batches'; export { Messages, diff --git a/src/resources/beta/messages/messages.ts b/src/resources/beta/messages/messages.ts index 92239cd8..f56295bb 100644 --- a/src/resources/beta/messages/messages.ts +++ b/src/resources/beta/messages/messages.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../../resource'; -import { APIPromise } from '../../../core'; -import * as Core from '../../../core'; import * as MessagesMessagesAPI from './messages'; import * as BetaAPI from '../beta'; import * as MessagesAPI from '../../messages/messages'; @@ -26,7 +24,9 @@ import { BetaMessageBatchSucceededResult, BetaMessageBatchesPage, } from './batches'; +import { APIPromise } from '../../../api-promise'; import { Stream } from '../../../streaming'; +import { RequestOptions } from '../../../internal/request-options'; export class Messages extends APIResource { batches: BatchesAPI.Batches = new BatchesAPI.Batches(this._client); @@ -38,18 +38,18 @@ export class Messages extends APIResource { * The Messages API can be used for either single queries or stateless multi-turn * conversations. */ - create(params: MessageCreateParamsNonStreaming, options?: Core.RequestOptions): APIPromise; + create(params: MessageCreateParamsNonStreaming, options?: RequestOptions): APIPromise; create( params: MessageCreateParamsStreaming, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise>; create( params: MessageCreateParamsBase, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | BetaMessage>; create( params: MessageCreateParams, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | APIPromise> { const { betas, ...body } = params; return this._client.post('/v1/messages?beta=true', { @@ -72,8 +72,8 @@ export class Messages extends APIResource { */ countTokens( params: MessageCountTokensParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + options?: RequestOptions, + ): APIPromise { const { betas, ...body } = params; return this._client.post('/v1/messages/count_tokens?beta=true', { body, @@ -1070,7 +1070,6 @@ export interface MessageCountTokensParams { } Messages.Batches = Batches; -Messages.BetaMessageBatchesPage = BetaMessageBatchesPage; export declare namespace Messages { export { @@ -1126,7 +1125,7 @@ export declare namespace Messages { type BetaMessageBatchRequestCounts as BetaMessageBatchRequestCounts, type BetaMessageBatchResult as BetaMessageBatchResult, type BetaMessageBatchSucceededResult as BetaMessageBatchSucceededResult, - BetaMessageBatchesPage as BetaMessageBatchesPage, + type BetaMessageBatchesPage as BetaMessageBatchesPage, type BatchCreateParams as BatchCreateParams, type BatchRetrieveParams as BatchRetrieveParams, type BatchListParams as BatchListParams, diff --git a/src/resources/beta/models.ts b/src/resources/beta/models.ts index 48036273..4f8940f5 100644 --- a/src/resources/beta/models.ts +++ b/src/resources/beta/models.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; -import { Page, type PageParams } from '../../pagination'; +import { APIPromise } from '../../api-promise'; +import { Page, type PageParams, PagePromise } from '../../pagination'; +import { RequestOptions } from '../../internal/request-options'; export class Models extends APIResource { /** @@ -12,8 +12,8 @@ export class Models extends APIResource { * The Models API response can be used to determine information about a specific * model or resolve a model alias to a model ID. */ - retrieve(modelId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/v1/models/${modelId}?beta=true`, options); + retrieve(modelID: string, options?: RequestOptions): APIPromise { + return this._client.get(`/v1/models/${modelID}?beta=true`, options); } /** @@ -23,22 +23,14 @@ export class Models extends APIResource { * use in the API. More recently released models are listed first. */ list( - query?: ModelListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - query: ModelListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/v1/models?beta=true', BetaModelInfosPage, { query, ...options }); + query: ModelListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v1/models?beta=true', Page, { query, ...options }); } } -export class BetaModelInfosPage extends Page {} +export type BetaModelInfosPage = Page; export interface BetaModelInfo { /** @@ -67,12 +59,10 @@ export interface BetaModelInfo { export interface ModelListParams extends PageParams {} -Models.BetaModelInfosPage = BetaModelInfosPage; - export declare namespace Models { export { type BetaModelInfo as BetaModelInfo, - BetaModelInfosPage as BetaModelInfosPage, + type BetaModelInfosPage as BetaModelInfosPage, type ModelListParams as ModelListParams, }; } diff --git a/src/resources/completions.ts b/src/resources/completions.ts index 2260681d..23e4bb16 100644 --- a/src/resources/completions.ts +++ b/src/resources/completions.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../resource'; -import { APIPromise } from '../core'; -import * as Core from '../core'; import * as CompletionsAPI from './completions'; import * as MessagesAPI from './messages/messages'; +import { APIPromise } from '../api-promise'; import { Stream } from '../streaming'; +import { RequestOptions } from '../internal/request-options'; export class Completions extends APIResource { /** @@ -18,18 +18,15 @@ export class Completions extends APIResource { * [migration guide](https://docs.anthropic.com/en/api/migrating-from-text-completions-to-messages) * for guidance in migrating from Text Completions to Messages. */ - create(body: CompletionCreateParamsNonStreaming, options?: Core.RequestOptions): APIPromise; - create( - body: CompletionCreateParamsStreaming, - options?: Core.RequestOptions, - ): APIPromise>; + create(body: CompletionCreateParamsNonStreaming, options?: RequestOptions): APIPromise; + create(body: CompletionCreateParamsStreaming, options?: RequestOptions): APIPromise>; create( body: CompletionCreateParamsBase, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | Completion>; create( body: CompletionCreateParams, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | APIPromise> { return this._client.post('/v1/complete', { body, diff --git a/src/resources/index.ts b/src/resources/index.ts index 23366973..5dd6b70f 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -71,4 +71,4 @@ export { type MessageCreateParamsStreaming, type MessageCountTokensParams, } from './messages/messages'; -export { ModelInfosPage, Models, type ModelInfo, type ModelListParams } from './models'; +export { Models, type ModelInfo, type ModelListParams, type ModelInfosPage } from './models'; diff --git a/src/resources/messages/batches.ts b/src/resources/messages/batches.ts index 3d9539cf..fc37faf5 100644 --- a/src/resources/messages/batches.ts +++ b/src/resources/messages/batches.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; import * as Shared from '../shared'; import * as MessagesAPI from './messages'; -import { Page, type PageParams } from '../../pagination'; +import { APIPromise } from '../../api-promise'; +import { Page, type PageParams, PagePromise } from '../../pagination'; +import { RequestOptions } from '../../internal/request-options'; import { JSONLDecoder } from '../../internal/decoders/jsonl'; import { AnthropicError } from '../../error'; @@ -17,7 +17,7 @@ export class Batches extends APIResource { * once. Once a Message Batch is created, it begins processing immediately. Batches * can take up to 24 hours to complete. */ - create(body: BatchCreateParams, options?: Core.RequestOptions): Core.APIPromise { + create(body: BatchCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/v1/messages/batches', { body, ...options }); } @@ -26,8 +26,8 @@ export class Batches extends APIResource { * completion. To access the results of a Message Batch, make a request to the * `results_url` field in the response. */ - retrieve(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/v1/messages/batches/${messageBatchId}`, options); + retrieve(messageBatchID: string, options?: RequestOptions): APIPromise { + return this._client.get(`/v1/messages/batches/${messageBatchID}`, options); } /** @@ -35,18 +35,10 @@ export class Batches extends APIResource { * returned first. */ list( - query?: BatchListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - query: BatchListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/v1/messages/batches', MessageBatchesPage, { query, ...options }); + query: BatchListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v1/messages/batches', Page, { query, ...options }); } /** @@ -54,8 +46,8 @@ export class Batches extends APIResource { * completion. To access the results of a Message Batch, make a request to the * `results_url` field in the response. */ - delete(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.delete(`/v1/messages/batches/${messageBatchId}`, options); + delete(messageBatchID: string, options?: RequestOptions): APIPromise { + return this._client.delete(`/v1/messages/batches/${messageBatchID}`, options); } /** @@ -69,8 +61,8 @@ export class Batches extends APIResource { * Note that cancellation may not result in any canceled requests if they were * non-interruptible. */ - cancel(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.post(`/v1/messages/batches/${messageBatchId}/cancel`, options); + cancel(messageBatchID: string, options?: RequestOptions): APIPromise { + return this._client.post(`/v1/messages/batches/${messageBatchID}/cancel`, options); } /** @@ -80,24 +72,15 @@ export class Batches extends APIResource { * in the Message Batch. Results are not guaranteed to be in the same order as * requests. Use the `custom_id` field to match results to requests. */ - async results( - messageBatchId: string, - options?: Core.RequestOptions, - ): Promise> { - const batch = await this.retrieve(messageBatchId); - if (!batch.results_url) { - throw new AnthropicError( - `No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`, - ); - } - - return this._client - .get(batch.results_url, { ...options, __binaryResponse: true }) - ._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)); + results(messageBatchID: string, options?: RequestOptions): APIPromise { + return this._client.get(`/v1/messages/batches/${messageBatchID}/results`, { + ...options, + __binaryResponse: true, + }); } } -export class MessageBatchesPage extends Page {} +export type MessageBatchesPage = Page; export interface DeletedMessageBatch { /** @@ -302,8 +285,6 @@ export namespace BatchCreateParams { export interface BatchListParams extends PageParams {} -Batches.MessageBatchesPage = MessageBatchesPage; - export declare namespace Batches { export { type DeletedMessageBatch as DeletedMessageBatch, @@ -315,7 +296,7 @@ export declare namespace Batches { type MessageBatchRequestCounts as MessageBatchRequestCounts, type MessageBatchResult as MessageBatchResult, type MessageBatchSucceededResult as MessageBatchSucceededResult, - MessageBatchesPage as MessageBatchesPage, + type MessageBatchesPage as MessageBatchesPage, type BatchCreateParams as BatchCreateParams, type BatchListParams as BatchListParams, }; diff --git a/src/resources/messages/index.ts b/src/resources/messages/index.ts index 1c9178ad..facb9e03 100644 --- a/src/resources/messages/index.ts +++ b/src/resources/messages/index.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - MessageBatchesPage, Batches, type DeletedMessageBatch, type MessageBatch, @@ -14,6 +13,7 @@ export { type MessageBatchSucceededResult, type BatchCreateParams, type BatchListParams, + type MessageBatchesPage, } from './batches'; export { Messages, diff --git a/src/resources/messages/messages.ts b/src/resources/messages/messages.ts index c75a62f5..49425077 100644 --- a/src/resources/messages/messages.ts +++ b/src/resources/messages/messages.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../resource'; -import { APIPromise } from '../../core'; -import * as Core from '../../core'; import * as MessagesAPI from './messages'; import * as BatchesAPI from './batches'; import { @@ -20,7 +18,9 @@ import { MessageBatchSucceededResult, MessageBatchesPage, } from './batches'; +import { APIPromise } from '../../api-promise'; import { Stream } from '../../streaming'; +import { RequestOptions } from '../../internal/request-options'; import { MessageStream } from '../../lib/MessageStream'; export { MessageStream } from '../../lib/MessageStream'; @@ -35,18 +35,18 @@ export class Messages extends APIResource { * The Messages API can be used for either single queries or stateless multi-turn * conversations. */ - create(body: MessageCreateParamsNonStreaming, options?: Core.RequestOptions): APIPromise; + create(body: MessageCreateParamsNonStreaming, options?: RequestOptions): APIPromise; create( body: MessageCreateParamsStreaming, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise>; create( body: MessageCreateParamsBase, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | Message>; create( body: MessageCreateParams, - options?: Core.RequestOptions, + options?: RequestOptions, ): APIPromise | APIPromise> { if (body.model in DEPRECATED_MODELS) { console.warn( @@ -76,10 +76,7 @@ export class Messages extends APIResource { * The Token Count API can be used to count the number of tokens in a Message, * including tools, images, and documents, without creating it. */ - countTokens( - body: MessageCountTokensParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + countTokens(body: MessageCountTokensParams, options?: RequestOptions): APIPromise { return this._client.post('/v1/messages/count_tokens', { body, ...options }); } } @@ -1062,7 +1059,6 @@ export interface MessageCountTokensParams { } Messages.Batches = Batches; -Messages.MessageBatchesPage = MessageBatchesPage; export declare namespace Messages { export { @@ -1124,7 +1120,7 @@ export declare namespace Messages { type MessageBatchRequestCounts as MessageBatchRequestCounts, type MessageBatchResult as MessageBatchResult, type MessageBatchSucceededResult as MessageBatchSucceededResult, - MessageBatchesPage as MessageBatchesPage, + type MessageBatchesPage as MessageBatchesPage, type BatchCreateParams as BatchCreateParams, type BatchListParams as BatchListParams, }; diff --git a/src/resources/models.ts b/src/resources/models.ts index 50e80399..da863b66 100644 --- a/src/resources/models.ts +++ b/src/resources/models.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../resource'; -import { isRequestOptions } from '../core'; -import * as Core from '../core'; -import { Page, type PageParams } from '../pagination'; +import { APIPromise } from '../api-promise'; +import { Page, type PageParams, PagePromise } from '../pagination'; +import { RequestOptions } from '../internal/request-options'; export class Models extends APIResource { /** @@ -12,8 +12,8 @@ export class Models extends APIResource { * The Models API response can be used to determine information about a specific * model or resolve a model alias to a model ID. */ - retrieve(modelId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/v1/models/${modelId}`, options); + retrieve(modelID: string, options?: RequestOptions): APIPromise { + return this._client.get(`/v1/models/${modelID}`, options); } /** @@ -22,20 +22,15 @@ export class Models extends APIResource { * The Models API response can be used to determine which models are available for * use in the API. More recently released models are listed first. */ - list(query?: ModelListParams, options?: Core.RequestOptions): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; list( - query: ModelListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/v1/models', ModelInfosPage, { query, ...options }); + query: ModelListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v1/models', Page, { query, ...options }); } } -export class ModelInfosPage extends Page {} +export type ModelInfosPage = Page; export interface ModelInfo { /** @@ -64,12 +59,10 @@ export interface ModelInfo { export interface ModelListParams extends PageParams {} -Models.ModelInfosPage = ModelInfosPage; - export declare namespace Models { export { type ModelInfo as ModelInfo, - ModelInfosPage as ModelInfosPage, + type ModelInfosPage as ModelInfosPage, type ModelListParams as ModelListParams, }; } diff --git a/src/shims/node.ts b/src/shims/node.ts deleted file mode 100644 index 73df5600..00000000 --- a/src/shims/node.ts +++ /dev/null @@ -1,50 +0,0 @@ -// @ts-ignore -import * as types from '../_shims/node-types'; -import { setShims } from '../_shims/registry'; -import { getRuntime } from '../_shims/node-runtime'; -setShims(getRuntime()); - -declare module '../_shims/manual-types' { - export namespace manual { - // @ts-ignore - export type Agent = types.Agent; - // @ts-ignore - export import fetch = types.fetch; - // @ts-ignore - export type Request = types.Request; - // @ts-ignore - export type RequestInfo = types.RequestInfo; - // @ts-ignore - export type RequestInit = types.RequestInit; - // @ts-ignore - export type Response = types.Response; - // @ts-ignore - export type ResponseInit = types.ResponseInit; - // @ts-ignore - export type ResponseType = types.ResponseType; - // @ts-ignore - export type BodyInit = types.BodyInit; - // @ts-ignore - export type Headers = types.Headers; - // @ts-ignore - export type HeadersInit = types.HeadersInit; - // @ts-ignore - export type BlobPropertyBag = types.BlobPropertyBag; - // @ts-ignore - export type FilePropertyBag = types.FilePropertyBag; - // @ts-ignore - export type FileFromPathOptions = types.FileFromPathOptions; - // @ts-ignore - export import FormData = types.FormData; - // @ts-ignore - export import File = types.File; - // @ts-ignore - export import Blob = types.Blob; - // @ts-ignore - export type Readable = types.Readable; - // @ts-ignore - export type FsReadStream = types.FsReadStream; - // @ts-ignore - export import ReadableStream = types.ReadableStream; - } -} diff --git a/src/shims/web.ts b/src/shims/web.ts deleted file mode 100644 index f72d7844..00000000 --- a/src/shims/web.ts +++ /dev/null @@ -1,50 +0,0 @@ -// @ts-ignore -import * as types from '../_shims/web-types'; -import { setShims } from '../_shims/registry'; -import { getRuntime } from '../_shims/web-runtime'; -setShims(getRuntime({ manuallyImported: true })); - -declare module '../_shims/manual-types' { - export namespace manual { - // @ts-ignore - export type Agent = types.Agent; - // @ts-ignore - export import fetch = types.fetch; - // @ts-ignore - export type Request = types.Request; - // @ts-ignore - export type RequestInfo = types.RequestInfo; - // @ts-ignore - export type RequestInit = types.RequestInit; - // @ts-ignore - export type Response = types.Response; - // @ts-ignore - export type ResponseInit = types.ResponseInit; - // @ts-ignore - export type ResponseType = types.ResponseType; - // @ts-ignore - export type BodyInit = types.BodyInit; - // @ts-ignore - export type Headers = types.Headers; - // @ts-ignore - export type HeadersInit = types.HeadersInit; - // @ts-ignore - export type BlobPropertyBag = types.BlobPropertyBag; - // @ts-ignore - export type FilePropertyBag = types.FilePropertyBag; - // @ts-ignore - export type FileFromPathOptions = types.FileFromPathOptions; - // @ts-ignore - export import FormData = types.FormData; - // @ts-ignore - export import File = types.File; - // @ts-ignore - export import Blob = types.Blob; - // @ts-ignore - export type Readable = types.Readable; - // @ts-ignore - export type FsReadStream = types.FsReadStream; - // @ts-ignore - export import ReadableStream = types.ReadableStream; - } -} diff --git a/src/streaming.ts b/src/streaming.ts index 2eff6ef9..8dd0fcd3 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,8 +1,8 @@ -import { ReadableStream, type Response } from './_shims/index'; import { AnthropicError } from './error'; +import { type ReadableStream } from './internal/shim-types'; +import { makeReadableStream } from './internal/shims'; import { LineDecoder } from './internal/decoders/line'; -import { createResponseHeaders } from './core'; import { APIError } from './error'; type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; @@ -66,12 +66,7 @@ export class Stream implements AsyncIterable { } if (sse.event === 'error') { - throw APIError.generate( - undefined, - `SSE Error: ${sse.data}`, - sse.data, - createResponseHeaders(response.headers), - ); + throw APIError.generate(undefined, `SSE Error: ${sse.data}`, sse.data, response.headers); } } done = true; @@ -177,7 +172,7 @@ export class Stream implements AsyncIterable { let iter: AsyncIterator; const encoder = new TextEncoder(); - return new ReadableStream({ + return makeReadableStream({ async start() { iter = self[Symbol.asyncIterator](); }, diff --git a/src/uploads.ts b/src/uploads.ts index 8fd2154d..58f3782b 100644 --- a/src/uploads.ts +++ b/src/uploads.ts @@ -1,15 +1,7 @@ -import { type RequestOptions } from './core'; -import { - FormData, - File, - type Blob, - type FilePropertyBag, - getMultipartRequestOptions, - type FsReadStream, - isFsReadStream, -} from './_shims/index'; -import { MultipartBody } from './_shims/MultipartBody'; -export { fileFromPath } from './_shims/index'; +import { type RequestOptions } from './internal/request-options'; +import { type FilePropertyBag } from './internal/builtin-types'; +import { isFsReadStreamLike, type FsReadStreamLike } from './internal/shims'; +import './internal/polyfill/file.node.js'; type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView; export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView; @@ -23,10 +15,10 @@ export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Arra * For convenience, you can also pass a fetch Response, or in Node, * the result of fs.createReadStream(). */ -export type Uploadable = FileLike | ResponseLike | FsReadStream; +export type Uploadable = FileLike | ResponseLike | FsReadStreamLike; /** - * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. + * Intended to match web.Blob, node.Blob, undici.Blob, etc. */ export interface BlobLike { /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ @@ -37,11 +29,22 @@ export interface BlobLike { text(): Promise; /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ slice(start?: number, end?: number): BlobLike; - // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method } /** - * Intended to match web.File, node.File, node-fetch.File, etc. + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function' && + typeof value.slice === 'function' && + typeof value.arrayBuffer === 'function'; + +/** + * Intended to match web.File, node.File, undici.File, etc. */ export interface FileLike extends BlobLike { /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ @@ -49,9 +52,20 @@ export interface FileLike extends BlobLike { /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ readonly name: string; } +declare var FileClass: { + prototype: FileLike; + new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): FileLike; +}; + +export const isFileLike = (value: any): value is FileLike => + value != null && + typeof value === 'object' && + typeof value.name === 'string' && + typeof value.lastModified === 'number' && + isBlobLike(value); /** - * Intended to match web.Response, node.Response, node-fetch.Response, etc. + * Intended to match web.Response, node.Response, undici.Response, etc. */ export interface ResponseLike { url: string; @@ -64,32 +78,26 @@ export const isResponseLike = (value: any): value is ResponseLike => typeof value.url === 'string' && typeof value.blob === 'function'; -export const isFileLike = (value: any): value is FileLike => - value != null && - typeof value === 'object' && - typeof value.name === 'string' && - typeof value.lastModified === 'number' && - isBlobLike(value); - -/** - * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check - * adds the arrayBuffer() method type because it is available and used at runtime - */ -export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.size === 'number' && - typeof value.type === 'string' && - typeof value.text === 'function' && - typeof value.slice === 'function' && - typeof value.arrayBuffer === 'function'; - export const isUploadable = (value: any): value is Uploadable => { - return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); + return isFileLike(value) || isResponseLike(value) || isFsReadStreamLike(value); }; export type ToFileInput = Uploadable | Exclude | AsyncIterable; +/** + * Construct a `File` instance. This is used to ensure a helpful error is thrown + * for environments that don't define a global `File` yet and so that we don't + * accidentally rely on a global `File` type in our annotations. + */ +function makeFile(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): FileLike { + const File = (globalThis as any).File as typeof FileClass | undefined; + if (typeof File === 'undefined') { + throw new Error('`File` is not defined as a global which is required for file uploads'); + } + + return new File(fileBits, fileName, options); +} + /** * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s @@ -121,7 +129,7 @@ export async function toFile( // in `new File` interpreting it as a string instead of binary data. const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; - return new File(data, name, options); + return makeFile(data, name, options); } const bits = await getBytes(value); @@ -135,7 +143,7 @@ export async function toFile( } } - return new File(bits, name, options); + return makeFile(bits, name, options); } async function getBytes(value: ToFileInput): Promise> { @@ -187,27 +195,22 @@ const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefi const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; -export const isMultipartBody = (body: any): body is MultipartBody => - body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody'; - /** * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. * Otherwise returns the request as is. */ -export const maybeMultipartFormRequestOptions = async >( - opts: RequestOptions, -): Promise> => { +export const maybeMultipartFormRequestOptions = async (opts: RequestOptions): Promise => { if (!hasUploadableValue(opts.body)) return opts; - const form = await createForm(opts.body); - return getMultipartRequestOptions(form, opts); + return { ...opts, body: await createForm(opts.body) }; }; -export const multipartFormRequestOptions = async >( - opts: RequestOptions, -): Promise> => { - const form = await createForm(opts.body); - return getMultipartRequestOptions(form, opts); +type MultipartFormRequestOptions = Omit & { body: unknown }; + +export const multipartFormRequestOptions = async ( + opts: MultipartFormRequestOptions, +): Promise => { + return { ...opts, body: await createForm(opts.body) }; }; export const createForm = async >(body: T | undefined): Promise => { @@ -240,7 +243,7 @@ const addFormValue = async (form: FormData, key: string, value: unknown): Promis form.append(key, String(value)); } else if (isUploadable(value)) { const file = await toFile(value); - form.append(key, file as File); + form.append(key, file as any); } else if (Array.isArray(value)) { await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); } else if (typeof value === 'object') { diff --git a/tests/api-resources/beta/messages/batches.test.ts b/tests/api-resources/beta/messages/batches.test.ts index 1ebd0cf4..77380750 100644 --- a/tests/api-resources/beta/messages/batches.test.ts +++ b/tests/api-resources/beta/messages/batches.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', @@ -86,13 +85,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.beta.messages.batches.retrieve('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -115,13 +107,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.beta.messages.batches.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Anthropic.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -143,13 +128,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('delete: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.beta.messages.batches.delete('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('delete: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -172,13 +150,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('cancel: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.beta.messages.batches.cancel('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('cancel: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -190,13 +161,6 @@ describe('resource batches', () => { ).rejects.toThrow(Anthropic.NotFoundError); }); - test('results: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.beta.messages.batches.results('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('results: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/beta/messages/messages.test.ts b/tests/api-resources/beta/messages/messages.test.ts index ec73d9c0..8b77c33d 100644 --- a/tests/api-resources/beta/messages/messages.test.ts +++ b/tests/api-resources/beta/messages/messages.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', diff --git a/tests/api-resources/beta/models.test.ts b/tests/api-resources/beta/models.test.ts index f155b632..92df4bc3 100644 --- a/tests/api-resources/beta/models.test.ts +++ b/tests/api-resources/beta/models.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', @@ -20,13 +19,6 @@ describe('resource models', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.beta.models.retrieve('model_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('list', async () => { const responsePromise = client.beta.models.list(); const rawResponse = await responsePromise.asResponse(); @@ -38,13 +30,6 @@ describe('resource models', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.beta.models.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Anthropic.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/completions.test.ts b/tests/api-resources/completions.test.ts index fcd0a68c..bef08dd6 100644 --- a/tests/api-resources/completions.test.ts +++ b/tests/api-resources/completions.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', diff --git a/tests/api-resources/messages/batches.test.ts b/tests/api-resources/messages/batches.test.ts index bc92b160..0016d399 100644 --- a/tests/api-resources/messages/batches.test.ts +++ b/tests/api-resources/messages/batches.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', @@ -83,13 +82,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.messages.batches.retrieve('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('list', async () => { const responsePromise = client.messages.batches.list(); const rawResponse = await responsePromise.asResponse(); @@ -101,13 +93,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.messages.batches.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Anthropic.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -129,13 +114,6 @@ describe('resource batches', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('delete: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.messages.batches.delete('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - test('cancel', async () => { const responsePromise = client.messages.batches.cancel('message_batch_id'); const rawResponse = await responsePromise.asResponse(); @@ -146,18 +124,4 @@ describe('resource batches', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); - - test('cancel: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.messages.batches.cancel('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); - - test('results: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.messages.batches.results('message_batch_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Anthropic.NotFoundError); - }); }); diff --git a/tests/api-resources/messages/messages.test.ts b/tests/api-resources/messages/messages.test.ts index 3ae41d32..1fd80bc8 100644 --- a/tests/api-resources/messages/messages.test.ts +++ b/tests/api-resources/messages/messages.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', diff --git a/tests/api-resources/models.test.ts b/tests/api-resources/models.test.ts index 7f5c0411..f1530064 100644 --- a/tests/api-resources/models.test.ts +++ b/tests/api-resources/models.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Anthropic from '@anthropic-ai/sdk'; -import { Response } from 'node-fetch'; const client = new Anthropic({ apiKey: 'my-anthropic-api-key', @@ -20,13 +19,6 @@ describe('resource models', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.models.retrieve('model_id', { path: '/_stainless_unknown_path' })).rejects.toThrow( - Anthropic.NotFoundError, - ); - }); - test('list', async () => { const responsePromise = client.models.list(); const rawResponse = await responsePromise.asResponse(); @@ -38,13 +30,6 @@ describe('resource models', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.models.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Anthropic.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/base64.test.ts b/tests/base64.test.ts new file mode 100644 index 00000000..eb937b1e --- /dev/null +++ b/tests/base64.test.ts @@ -0,0 +1,80 @@ +import { fromBase64, toBase64 } from '@anthropic-ai/sdk/internal/utils/base64'; + +describe.each(['Buffer', 'atob'])('with %s', (mode) => { + let originalBuffer: BufferConstructor; + beforeAll(() => { + if (mode === 'atob') { + originalBuffer = globalThis.Buffer; + // @ts-expect-error Can't assign undefined to BufferConstructor + delete globalThis.Buffer; + } + }); + afterAll(() => { + if (mode === 'atob') { + globalThis.Buffer = originalBuffer; + } + }); + test('toBase64', () => { + const testCases = [ + { + input: 'hello world', + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: undefined, + expected: '', + }, + { + input: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + }, + { + input: '✓', + expected: '4pyT', + }, + { + input: new Uint8Array([226, 156, 147]), + expected: '4pyT', + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(toBase64(input)).toBe(expected); + }); + }); + + test('fromBase64', () => { + const testCases = [ + { + input: 'aGVsbG8gd29ybGQ=', + expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + }, + { + input: '', + expected: new Uint8Array([]), + }, + { + input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + expected: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + }, + { + input: '4pyT', + expected: new Uint8Array([226, 156, 147]), + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(fromBase64(input)).toEqual(expected); + }); + }); +}); diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts new file mode 100644 index 00000000..2589cc3f --- /dev/null +++ b/tests/buildHeaders.test.ts @@ -0,0 +1,88 @@ +import { inspect } from 'node:util'; +import { buildHeaders, type HeadersLike, type NullableHeaders } from '@anthropic-ai/sdk/internal/headers'; + +function inspectNullableHeaders(headers: NullableHeaders) { + return `NullableHeaders {${[ + ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), + ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), + ].join(', ')} }`; +} + +describe('buildHeaders', () => { + const cases: [HeadersLike[], string][] = [ + [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': undefined, + }, + ], + `NullableHeaders { 'content-type': 'text/plain' }`, + ], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': null, + }, + ], + `NullableHeaders { 'content-type': null }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: 'name2=value2', + }, + ], + `NullableHeaders { 'cookie': 'name2=value2' }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: undefined, + }, + ], + `NullableHeaders { 'cookie': 'name1=value1' }`, + ], + [ + [ + { + cookie: ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, + ], + [ + [ + { + 'x-foo': ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, + ], + [ + [ + [ + ['cookie', 'name1=value1'], + ['cookie', 'name2=value2'], + ['Cookie', 'name3=value3'], + ], + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, + ], + [[undefined], `NullableHeaders { }`], + [[null], `NullableHeaders { }`], + ]; + for (const [input, expected] of cases) { + test(expected, () => { + expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); + }); + } +}); diff --git a/tests/form.test.ts b/tests/form.test.ts index d3a86992..ac5c2101 100644 --- a/tests/form.test.ts +++ b/tests/form.test.ts @@ -1,6 +1,4 @@ -import { multipartFormRequestOptions, createForm } from '@anthropic-ai/sdk/core'; -import { Blob } from '@anthropic-ai/sdk/_shims/index'; -import { toFile } from '@anthropic-ai/sdk'; +import { multipartFormRequestOptions, createForm, toFile } from '@anthropic-ai/sdk'; describe('form data validation', () => { test('valid values do not error', async () => { diff --git a/tests/index.test.ts b/tests/index.test.ts index 9402a819..a0ed1b54 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import util from 'node:util'; import Anthropic from '@anthropic-ai/sdk'; import { APIUserAbortError } from '@anthropic-ai/sdk'; -import { Headers } from '@anthropic-ai/sdk/core'; -import defaultFetch, { Response, type RequestInit, type RequestInfo } from 'node-fetch'; +const defaultFetch = fetch; describe('instantiate client', () => { const env = process.env; @@ -28,7 +28,7 @@ describe('instantiate client', () => { test('they are used in the request', () => { const { req } = client.buildRequest({ path: '/foo', method: 'post' }); - expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); + expect(req.headers.get('x-my-default-header')).toEqual('2'); }); test('can ignore `undefined` and leave the default', () => { @@ -37,7 +37,7 @@ describe('instantiate client', () => { method: 'post', headers: { 'X-My-Default-Header': undefined }, }); - expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); + expect(req.headers.get('x-my-default-header')).toEqual('2'); }); test('can be removed with `null`', () => { @@ -46,7 +46,7 @@ describe('instantiate client', () => { method: 'post', headers: { 'X-My-Default-Header': null }, }); - expect(req.headers as Headers).not.toHaveProperty('x-my-default-header'); + expect(req.headers.has('x-my-default-header')).toBe(false); }); }); @@ -124,7 +124,7 @@ describe('instantiate client', () => { test('normalized method', async () => { let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { capturedRequest = init; return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }; @@ -211,18 +211,6 @@ describe('instantiate client', () => { describe('request building', () => { const client = new Anthropic({ apiKey: 'my-anthropic-api-key' }); - describe('Content-Length', () => { - test('handles multi-byte characters', () => { - const { req } = client.buildRequest({ path: '/foo', method: 'post', body: { value: '—' } }); - expect((req.headers as Record)['content-length']).toEqual('20'); - }); - - test('handles standard characters', () => { - const { req } = client.buildRequest({ path: '/foo', method: 'post', body: { value: 'hello' } }); - expect((req.headers as Record)['content-length']).toEqual('22'); - }); - }); - describe('custom headers', () => { test('handles undefined', () => { const { req } = client.buildRequest({ @@ -231,18 +219,92 @@ describe('request building', () => { body: { value: 'hello' }, headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, }); - expect((req.headers as Record)['x-foo']).toEqual('bar'); - expect((req.headers as Record)['x-Foo']).toEqual(undefined); - expect((req.headers as Record)['X-Foo']).toEqual(undefined); - expect((req.headers as Record)['x-baz']).toEqual(undefined); + expect(req.headers.get('x-foo')).toEqual('bar'); + expect(req.headers.get('x-Foo')).toEqual('bar'); + expect(req.headers.get('X-Foo')).toEqual('bar'); + expect(req.headers.get('x-baz')).toEqual(null); + }); + }); +}); + +describe('default encoder', () => { + const client = new Anthropic({ apiKey: 'my-anthropic-api-key' }); + + class Serializable { + toJSON() { + return { $type: 'Serializable' }; + } + } + class Collection { + #things: T[]; + constructor(things: T[]) { + this.#things = Array.from(things); + } + toJSON() { + return Array.from(this.#things); + } + [Symbol.iterator]() { + return this.#things[Symbol.iterator]; + } + } + for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { + test(`serializes ${util.inspect(jsonValue)} as json`, () => { + const { req } = client.buildRequest({ + path: '/foo', + method: 'post', + body: jsonValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('application/json'); + expect(req.body).toBe(JSON.stringify(jsonValue)); }); + } + + const encoder = new TextEncoder(); + const asyncIterable = (async function* () { + yield encoder.encode('a\n'); + yield encoder.encode('b\n'); + yield encoder.encode('c\n'); + })(); + for (const streamValue of [ + [encoder.encode('a\nb\nc\n')][Symbol.iterator](), + new Response('a\nb\nc\n').body, + asyncIterable, + ]) { + test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { + const { req } = client.buildRequest({ + path: '/foo', + method: 'post', + body: streamValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual(null); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); + } + + test(`can set content-type for ReadableStream`, async () => { + const { req } = client.buildRequest({ + path: '/foo', + method: 'post', + body: new Response('a\nb\nc\n').body, + headers: { 'Content-Type': 'text/plain' }, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('text/plain'); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); }); }); describe('retries', () => { test('retry on timeout', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Promise( (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), @@ -267,7 +329,7 @@ describe('retries', () => { test('retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -285,14 +347,14 @@ describe('retries', () => { expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2'); + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); expect(count).toEqual(3); }); test('omit retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -315,13 +377,13 @@ describe('retries', () => { }), ).toEqual({ a: 1 }); - expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); + expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); }); test('omit retry count header by default', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -354,7 +416,7 @@ describe('retries', () => { test('overwrite retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -377,12 +439,15 @@ describe('retries', () => { }), ).toEqual({ a: 1 }); - expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toBe('42'); + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); }); test('retry on 429 with retry-after', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Response(undefined, { status: 429, @@ -409,7 +474,10 @@ describe('retries', () => { test('retry on 429 with retry-after-ms', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Response(undefined, { status: 429, diff --git a/tests/responses.test.ts b/tests/responses.test.ts index db58e0b7..e69de29b 100644 --- a/tests/responses.test.ts +++ b/tests/responses.test.ts @@ -1,154 +0,0 @@ -import { APIPromise, createResponseHeaders } from '@anthropic-ai/sdk/core'; -import Anthropic from '@anthropic-ai/sdk/index'; -import { Headers } from '@anthropic-ai/sdk/_shims/index'; -import { Response } from 'node-fetch'; -import { compareType } from './utils/typing'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); - -describe('request id', () => { - test('types', () => { - compareType>, string>(true); - compareType>, number>(true); - compareType>, null>(true); - compareType>, void>(true); - compareType>, Response>(true); - compareType>, Response>(true); - compareType>, { foo: string } & { _request_id?: string | null }>( - true, - ); - compareType>>, Array<{ foo: string }>>(true); - }); - - test('withResponse', async () => { - const client = new Anthropic({ - apiKey: 'dummy', - fetch: async () => - new Response(JSON.stringify({ id: 'bar' }), { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, - }), - }); - - const { - data: message, - response, - request_id, - } = await client.messages - .create({ messages: [], model: 'claude-3-opus-20240229', max_tokens: 1024 }) - .withResponse(); - - expect(request_id).toBe('req_xxx'); - expect(response.headers.get('request-id')).toBe('req_xxx'); - expect(message.id).toBe('bar'); - expect(JSON.stringify(message)).toBe('{"id":"bar"}'); - }); - - test('object response', async () => { - const client = new Anthropic({ - apiKey: 'dummy', - fetch: async () => - new Response(JSON.stringify({ id: 'bar' }), { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, - }), - }); - - const rsp = await client.messages.create({ - messages: [], - model: 'claude-3-opus-20240229', - max_tokens: 1024, - }); - expect(rsp.id).toBe('bar'); - expect(rsp._request_id).toBe('req_xxx'); - expect(JSON.stringify(rsp)).toBe('{"id":"bar"}'); - }); - - test('envelope response', async () => { - const promise = new APIPromise<{ data: { foo: string } }>( - (async () => { - return { - response: new Response(JSON.stringify({ data: { foo: 'bar' } }), { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, - }), - controller: {} as any, - options: {} as any, - }; - })(), - )._thenUnwrap((d) => d.data); - - const rsp = await promise; - expect(rsp.foo).toBe('bar'); - expect(rsp._request_id).toBe('req_xxx'); - }); - - test('page response', async () => { - const client = new Anthropic({ - apiKey: 'dummy', - fetch: async () => - new Response(JSON.stringify({ data: [{ foo: 'bar' }] }), { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, - }), - }); - - const page = await client.beta.messages.batches.list(); - expect(page.data).toMatchObject([{ foo: 'bar' }]); - expect((page as any)._request_id).toBeUndefined(); - }); - - test('array response', async () => { - const promise = new APIPromise>( - (async () => { - return { - response: new Response(JSON.stringify([{ foo: 'bar' }]), { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, - }), - controller: {} as any, - options: {} as any, - }; - })(), - ); - - const rsp = await promise; - expect(rsp.length).toBe(1); - expect(rsp[0]).toMatchObject({ foo: 'bar' }); - expect((rsp as any)._request_id).toBeUndefined(); - }); - - test('string response', async () => { - const promise = new APIPromise( - (async () => { - return { - response: new Response('hello world', { - headers: { 'request-id': 'req_xxx', 'content-type': 'application/text' }, - }), - controller: {} as any, - options: {} as any, - }; - })(), - ); - - const result = await promise; - expect(result).toBe('hello world'); - expect((result as any)._request_id).toBeUndefined(); - }); -}); diff --git a/tests/streaming.test.ts b/tests/streaming.test.ts index d82b7484..90967c32 100644 --- a/tests/streaming.test.ts +++ b/tests/streaming.test.ts @@ -1,4 +1,3 @@ -import { Response } from 'node-fetch'; import { PassThrough } from 'stream'; import assert from 'assert'; import { Stream, _iterSSEMessages, _decodeChunks as decodeChunks } from '@anthropic-ai/sdk/streaming'; diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts index 31f52b0b..ab55f1b9 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -1,6 +1,5 @@ import fs from 'fs'; import { toFile, type ResponseLike } from '@anthropic-ai/sdk/uploads'; -import { File } from '@anthropic-ai/sdk/_shims/index'; class MyClass { name: string = 'foo'; @@ -63,3 +62,14 @@ describe('toFile', () => { expect(file.type).toBe('jsonl'); }); }); + +test('missing File error message', async () => { + // @ts-ignore + globalThis.File = undefined; + + await expect( + toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), + ).rejects.toMatchInlineSnapshot( + `[Error: \`File\` is not defined as a global which is required for file uploads]`, + ); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json index b7bf27fa..0ad4e576 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "include": ["dist/src"], - "exclude": ["dist/src/_shims/*-deno.ts"], + "exclude": [], "compilerOptions": { "rootDir": "./dist/src", "paths": { diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json index e9f2d70b..0f6aba91 100644 --- a/tsconfig.dist-src.json +++ b/tsconfig.dist-src.json @@ -5,7 +5,7 @@ "include": ["index.ts"], "compilerOptions": { "target": "es2015", - "lib": ["DOM"], + "lib": ["DOM", "DOM.Iterable"], "moduleResolution": "node" } } diff --git a/tsconfig.json b/tsconfig.json index a6c8fbd0..c830a9da 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "include": ["src", "tests", "examples"], - "exclude": ["src/_shims/**/*-deno.ts"], + "exclude": [], "compilerOptions": { "target": "es2020", "lib": ["es2020"], @@ -9,7 +9,6 @@ "esModuleInterop": true, "baseUrl": "./", "paths": { - "@anthropic-ai/sdk/_shims/auto/*": ["src/_shims/auto/*-node"], "@anthropic-ai/sdk/*": ["src/*"], "@anthropic-ai/sdk": ["src/index.ts"] }, diff --git a/yarn.lock b/yarn.lock index bb179420..475b81a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,37 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@andrewbranch/untar.js@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" + integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== + +"@arethetypeswrong/cli@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.0.tgz#f97f10926b3f9f9eb5117550242d2e06c25cadac" + integrity sha512-xSMW7bfzVWpYw5JFgZqBXqr6PdR0/REmn3DkxCES5N0JTcB0CVgbIynJCvKBFmXaPc3hzmmTrb7+yPDRoOSZdA== + dependencies: + "@arethetypeswrong/core" "0.17.0" + chalk "^4.1.2" + cli-table3 "^0.6.3" + commander "^10.0.1" + marked "^9.1.2" + marked-terminal "^7.1.0" + semver "^7.5.4" + +"@arethetypeswrong/core@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.0.tgz#abb3b5f425056d37193644c2a2de4aecf866b76b" + integrity sha512-FHyhFizXNetigTVsIhqXKGYLpazPS5YNojEPpZEUcBPt9wVvoEbNIvG+hybuBR+pjlRcbyuqhukHZm1fr+bDgA== + dependencies: + "@andrewbranch/untar.js" "^1.0.3" + cjs-module-lexer "^1.2.3" + fflate "^0.8.2" + lru-cache "^10.4.3" + semver "^7.5.4" + typescript "5.6.1-rc" + validate-npm-package-name "^5.0.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" @@ -302,6 +333,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -655,6 +691,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sinonjs/commons@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" @@ -851,14 +892,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node-fetch@^2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.4.tgz#1bc3a26de814f6bf466b25aeb1473fa1afe6a660" - integrity sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node@*": version "20.10.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" @@ -866,10 +899,12 @@ dependencies: undici-types "~5.26.4" -"@types/node@^18.11.18": - version "18.11.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" - integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== +"@types/node@^20.17.6": + version "20.17.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.6.tgz#6e4073230c180d3579e8c60141f99efdf5df0081" + integrity sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ== + dependencies: + undici-types "~6.19.2" "@types/semver@^7.5.0": version "7.5.8" @@ -910,7 +945,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.7.0": +"@typescript-eslint/parser@^6.0.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== @@ -979,13 +1014,6 @@ "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1006,15 +1034,6 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -agentkeepalive@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1040,11 +1059,23 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1064,6 +1095,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1094,11 +1130,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1263,7 +1294,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1271,6 +1302,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -1286,11 +1322,46 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +cjs-module-lexer@^1.2.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-table3@^0.6.3, cli-table3@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1334,12 +1405,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== concat-map@0.0.1: version "0.0.1" @@ -1385,7 +1454,7 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.4: +debug@^4.3.4, debug@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -1430,16 +1499,6 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -depd@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1484,6 +1543,16 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1625,11 +1694,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -1686,18 +1750,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.12: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -1732,6 +1785,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1775,28 +1833,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -formdata-node@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.3.3.tgz#21415225be66e2c87a917bfc0fedab30a119c23c" - integrity sha512-coTew7WODO2vF+XhpUdmYz4UBvlsiTMSNaFYZlrXIqYbFd4W7bMwnoALNLE6uvNgzTg2j1JDF0ZImEfF06VPAA== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.1" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1863,6 +1899,17 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -1914,6 +1961,11 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -1929,13 +1981,6 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= - dependencies: - ms "^2.0.0" - iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -1943,6 +1988,13 @@ iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + ignore@^5.2.0, ignore@^5.2.4: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -2578,6 +2630,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lru-cache@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2611,6 +2668,24 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +marked-terminal@^7.1.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" + integrity sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ== + dependencies: + ansi-escapes "^7.0.0" + ansi-regex "^6.1.0" + chalk "^5.3.0" + cli-highlight "^2.1.11" + cli-table3 "^0.6.5" + node-emoji "^2.1.3" + supports-hyperlinks "^3.1.0" + +marked@^9.1.2: + version "9.1.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" + integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -2629,18 +2704,6 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== - -mime-types@^2.1.12: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== - dependencies: - mime-db "1.51.0" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -2665,37 +2728,56 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^2.6.7: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== +node-emoji@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" + integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== dependencies: - whatwg-url "^5.0.0" + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" node-int64@^0.4.0: version "0.4.0" @@ -2712,6 +2794,28 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-packlist@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2726,6 +2830,11 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2833,6 +2942,23 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2868,6 +2994,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -2919,6 +3050,15 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +publint@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" + integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== + dependencies: + npm-packlist "^5.1.3" + picocolors "^1.1.1" + sade "^1.8.1" + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -3010,6 +3150,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -3059,6 +3206,13 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3152,10 +3306,10 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -superstruct@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" - integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg== +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== supports-color@^5.3.0: version "5.5.0" @@ -3164,7 +3318,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -3178,6 +3332,14 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -3205,6 +3367,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" @@ -3227,11 +3403,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - ts-api-utils@^1.0.1: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" @@ -3270,21 +3441,20 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" -tsc-multi@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tsc-multi/-/tsc-multi-1.1.0.tgz#0e2b03c0ed0ac58ecb556f11709441102d202680" - integrity sha512-THE6X+sse7EZ2qMhqXvBhd2HMTvXyWwYnx+2T/ijqdp/6Rf7rUc2uPRzPdrrljZCNcYDeL0qP2P7tqm2IwayTg== +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.3/tsc-multi.tgz": + version "1.1.3" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.3/tsc-multi.tgz#8fc21fc95b247b86721b95fabfb10c6a436134c3" dependencies: - debug "^4.3.4" - fast-glob "^3.2.12" + debug "^4.3.7" + fast-glob "^3.3.2" get-stdin "^8.0.0" p-all "^3.0.0" - picocolors "^1.0.0" + picocolors "^1.1.1" signal-exit "^3.0.7" string-to-stream "^3.0.1" - superstruct "^1.0.3" - tslib "^2.5.0" - yargs "^17.7.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" tsconfig-paths@^4.0.0: version "4.2.0" @@ -3295,16 +3465,16 @@ tsconfig-paths@^4.0.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" - integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== - tslib@^2.6.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -3327,6 +3497,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +typescript@5.6.1-rc: + version "5.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" + integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== + typescript@^4.8.2: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" @@ -3337,6 +3512,16 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -3376,6 +3561,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -3383,24 +3573,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web-streams-polyfill@4.0.0-beta.1: - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" - integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -3445,12 +3617,30 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.3.1, yargs@^17.7.1: +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 6f5bd98055e90b29ae299d322465cf0d735ae1a7 Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 12:19:19 +0000 Subject: [PATCH 06/10] fixes --- src/api-promise.ts | 37 ++++--- src/client.ts | 2 + src/core.ts | 0 src/internal/decoders/jsonl.ts | 1 - src/internal/parse.ts | 27 +++++- src/lib/MessageStream.ts | 16 +-- src/pagination.ts | 10 +- src/resources/beta/messages/batches.ts | 11 +-- src/resources/messages/batches.ts | 19 +++- src/resources/messages/messages.ts | 2 +- tests/responses.test.ts | 129 +++++++++++++++++++++++++ 11 files changed, 212 insertions(+), 42 deletions(-) delete mode 100644 src/core.ts diff --git a/src/api-promise.ts b/src/api-promise.ts index 31b52325..66ecbd35 100644 --- a/src/api-promise.ts +++ b/src/api-promise.ts @@ -1,18 +1,25 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { type PromiseOrValue } from './internal/types'; -import { APIResponseProps, defaultParseResponse } from './internal/parse'; +import { + type APIResponseProps, + type WithRequestID, + defaultParseResponse, + addRequestID, +} from './internal/parse'; /** * A subclass of `Promise` providing additional helper methods * for interacting with the SDK. */ -export class APIPromise extends Promise { - private parsedPromise: Promise | undefined; +export class APIPromise extends Promise> { + private parsedPromise: Promise> | undefined; constructor( private responsePromise: Promise, - private parseResponse: (props: APIResponseProps) => PromiseOrValue = defaultParseResponse, + private parseResponse: ( + props: APIResponseProps, + ) => PromiseOrValue> = defaultParseResponse, ) { super((resolve) => { // this is maybe a bit weird but this has to be a no-op to not implicitly @@ -24,7 +31,7 @@ export class APIPromise extends Promise { _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { return new APIPromise(this.responsePromise, async (props) => - transform(await this.parseResponse(props), props), + addRequestID(transform(await this.parseResponse(props), props), props.response), ); } @@ -44,7 +51,9 @@ export class APIPromise extends Promise { } /** - * Gets the parsed response data and the raw `Response` instance. + * Gets the parsed response data, the raw `Response` instance and the ID of the request, + * returned via the `request-id` header which is useful for debugging requests and resporting + * issues to Anthropic. * * If you just want to get the raw `Response` instance without parsing it, * you can use {@link asResponse()}. @@ -53,20 +62,20 @@ export class APIPromise extends Promise { * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` * to your `tsconfig.json`. */ - async withResponse(): Promise<{ data: T; response: Response }> { + async withResponse(): Promise<{ data: T; response: Response; request_id: string | null | undefined }> { const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response }; + return { data, response, request_id: response.headers.get('request-id') }; } - private parse(): Promise { + private parse(): Promise> { if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then(this.parseResponse); + this.parsedPromise = this.responsePromise.then(this.parseResponse) as any as Promise>; } return this.parsedPromise; } - override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + override then, TResult2 = never>( + onfulfilled?: ((value: WithRequestID) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, ): Promise { return this.parse().then(onfulfilled, onrejected); @@ -74,11 +83,11 @@ export class APIPromise extends Promise { override catch( onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise { + ): Promise | TResult> { return this.parse().catch(onrejected); } - override finally(onfinally?: (() => void) | undefined | null): Promise { + override finally(onfinally?: (() => void) | undefined | null): Promise> { return this.parse().finally(onfinally); } } diff --git a/src/client.ts b/src/client.ts index 3528c04a..b409e685 100644 --- a/src/client.ts +++ b/src/client.ts @@ -61,6 +61,7 @@ import { ImageBlockParam, InputJSONDelta, Message, + MessageStreamParams, MessageCountTokensParams, MessageCreateParams, MessageCreateParamsNonStreaming, @@ -820,6 +821,7 @@ export declare namespace Anthropic { type MessageCreateParams as MessageCreateParams, type MessageCreateParamsNonStreaming as MessageCreateParamsNonStreaming, type MessageCreateParamsStreaming as MessageCreateParamsStreaming, + type MessageStreamParams as MessageStreamParams, type MessageCountTokensParams as MessageCountTokensParams, }; diff --git a/src/core.ts b/src/core.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/internal/decoders/jsonl.ts b/src/internal/decoders/jsonl.ts index 8d3693d8..8886529f 100644 --- a/src/internal/decoders/jsonl.ts +++ b/src/internal/decoders/jsonl.ts @@ -1,6 +1,5 @@ import { AnthropicError } from '../../error'; import { readableStreamAsyncIterable } from '../../streaming'; -import { type Response } from '../../_shims/index'; import { LineDecoder, type Bytes } from './line'; export class JSONLDecoder { diff --git a/src/internal/parse.ts b/src/internal/parse.ts index df65a386..7a4e8611 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -3,6 +3,7 @@ import { debug } from './utils/log'; import { FinalRequestOptions } from './request-options'; import { Stream } from '../streaming'; +import { type AbstractPage } from '../pagination'; export type APIResponseProps = { response: Response; @@ -10,7 +11,12 @@ export type APIResponseProps = { controller: AbortController; }; -export async function defaultParseResponse(props: APIResponseProps): Promise { +export type WithRequestID = + T extends Array | Response | AbstractPage ? T + : T extends Record ? T & { _request_id?: string | null } + : T; + +export async function defaultParseResponse(props: APIResponseProps): Promise> { const { response } = props; if (props.options.stream) { debug('response', response.status, response.url, response.headers, response.body); @@ -27,11 +33,11 @@ export async function defaultParseResponse(props: APIResponseProps): Promise< // fetch refuses to read the body when the status code is 204. if (response.status === 204) { - return null as T; + return null as WithRequestID; } if (props.options.__binaryResponse) { - return response as unknown as T; + return response as unknown as WithRequestID; } const contentType = response.headers.get('content-type'); @@ -42,12 +48,23 @@ export async function defaultParseResponse(props: APIResponseProps): Promise< debug('response', response.status, response.url, response.headers, json); - return json as T; + return addRequestID(json as T, response); } const text = await response.text(); debug('response', response.status, response.url, response.headers, text); // TODO handle blob, arraybuffer, other content types, etc. - return text as unknown as T; + return text as unknown as WithRequestID; +} + +export function addRequestID(value: T, response: Response): WithRequestID { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return value as WithRequestID; + } + + return Object.defineProperty(value, '_request_id', { + value: response.headers.get('request-id'), + enumerable: false, + }) as WithRequestID; } diff --git a/src/lib/MessageStream.ts b/src/lib/MessageStream.ts index dea739f1..6e55f72d 100644 --- a/src/lib/MessageStream.ts +++ b/src/lib/MessageStream.ts @@ -1,5 +1,4 @@ -import * as Core from '@anthropic-ai/sdk/core'; -import { AnthropicError, APIUserAbortError } from '@anthropic-ai/sdk/error'; +import { AnthropicError, APIUserAbortError } from '../error'; import { type ContentBlock, Messages, @@ -9,10 +8,11 @@ import { type MessageCreateParams, type MessageCreateParamsBase, type TextBlock, -} from '@anthropic-ai/sdk/resources/messages'; -import { type ReadableStream } from '@anthropic-ai/sdk/_shims/index'; -import { Stream } from '@anthropic-ai/sdk/streaming'; +} from '../resources/messages'; +import { Stream } from '../streaming'; import { partialParse } from '../_vendor/partial-json-parser/parser'; +import { RequestOptions } from '../internal/request-options'; +import { type ReadableStream } from '../internal/shim-types'; export interface MessageStreamEvents { connect: () => void; @@ -91,7 +91,7 @@ export class MessageStream implements AsyncIterable { static createMessage( messages: Messages, params: MessageCreateParamsBase, - options?: Core.RequestOptions, + options?: RequestOptions, ): MessageStream { const runner = new MessageStream(); for (const message of params.messages) { @@ -128,7 +128,7 @@ export class MessageStream implements AsyncIterable { protected async _createMessage( messages: Messages, params: MessageCreateParams, - options?: Core.RequestOptions, + options?: RequestOptions, ): Promise { const signal = options?.signal; if (signal) { @@ -416,7 +416,7 @@ export class MessageStream implements AsyncIterable { protected async _fromReadableStream( readableStream: ReadableStream, - options?: Core.RequestOptions, + options?: RequestOptions, ): Promise { const signal = options?.signal; if (signal) { diff --git a/src/pagination.ts b/src/pagination.ts index a3d3881f..ccd364f6 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -3,7 +3,7 @@ import type { Anthropic } from './client'; import { AnthropicError } from './error'; import { FinalRequestOptions } from './internal/request-options'; -import { defaultParseResponse } from './internal/parse'; +import { defaultParseResponse, type WithRequestID } from './internal/parse'; import { APIPromise } from './api-promise'; import { type APIResponseProps } from './internal/parse'; import { maybeObj } from './internal/utils/values'; @@ -87,7 +87,13 @@ export class PagePromise< ) { super( request, - async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options), + async (props) => + new Page( + client, + props.response, + await defaultParseResponse(props), + props.options, + ) as WithRequestID, ); } diff --git a/src/resources/beta/messages/batches.ts b/src/resources/beta/messages/batches.ts index cfb4eb5b..6862c1f9 100644 --- a/src/resources/beta/messages/batches.ts +++ b/src/resources/beta/messages/batches.ts @@ -2,8 +2,7 @@ import { APIResource } from '../../../resource'; import * as BetaAPI from '../beta'; -import * as MessagesAPI from '../../messages/messages'; -import * as MessagesMessagesAPI from './messages'; +import * as BetaMessagesAPI from './messages'; import { APIPromise } from '../../../api-promise'; import { Page, type PageParams, PagePromise } from '../../../pagination'; import { RequestOptions } from '../../../internal/request-options'; @@ -122,19 +121,19 @@ export class Batches extends APIResource { * in the Message Batch. Results are not guaranteed to be in the same order as * requests. Use the `custom_id` field to match results to requests. */ - results( + async results( messageBatchID: string, params: BatchResultsParams | null | undefined = {}, options?: RequestOptions, - ): APIPromise { - const batch = await this.retrieve(messageBatchId); + ): Promise> { + const batch = await this.retrieve(messageBatchID); if (!batch.results_url) { throw new AnthropicError( `No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`, ); } - const { betas } = params; + const { betas } = params ?? {}; return this._client .get(batch.results_url, { ...options, diff --git a/src/resources/messages/batches.ts b/src/resources/messages/batches.ts index fc37faf5..23fe87d1 100644 --- a/src/resources/messages/batches.ts +++ b/src/resources/messages/batches.ts @@ -72,11 +72,20 @@ export class Batches extends APIResource { * in the Message Batch. Results are not guaranteed to be in the same order as * requests. Use the `custom_id` field to match results to requests. */ - results(messageBatchID: string, options?: RequestOptions): APIPromise { - return this._client.get(`/v1/messages/batches/${messageBatchID}/results`, { - ...options, - __binaryResponse: true, - }); + async results( + messageBatchID: string, + options?: RequestOptions, + ): Promise> { + const batch = await this.retrieve(messageBatchID); + if (!batch.results_url) { + throw new AnthropicError( + `No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`, + ); + } + + return this._client + .get(batch.results_url, { ...options, __binaryResponse: true }) + ._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)); } } diff --git a/src/resources/messages/messages.ts b/src/resources/messages/messages.ts index 49425077..0ce089bf 100644 --- a/src/resources/messages/messages.ts +++ b/src/resources/messages/messages.ts @@ -66,7 +66,7 @@ export class Messages extends APIResource { /** * Create a Message stream */ - stream(body: MessageStreamParams, options?: Core.RequestOptions): MessageStream { + stream(body: MessageStreamParams, options?: RequestOptions): MessageStream { return MessageStream.createMessage(this, body, options); } diff --git a/tests/responses.test.ts b/tests/responses.test.ts index e69de29b..587e5508 100644 --- a/tests/responses.test.ts +++ b/tests/responses.test.ts @@ -0,0 +1,129 @@ +import { APIPromise } from '@anthropic-ai/sdk/api-promise'; +import Anthropic from '@anthropic-ai/sdk/index'; +import { compareType } from './utils/typing'; + +describe('request id', () => { + test('types', () => { + compareType>, string>(true); + compareType>, number>(true); + compareType>, null>(true); + compareType>, void>(true); + compareType>, Response>(true); + compareType>, Response>(true); + compareType>, { foo: string } & { _request_id?: string | null }>( + true, + ); + compareType>>, Array<{ foo: string }>>(true); + }); + + test('withResponse', async () => { + const client = new Anthropic({ + apiKey: 'dummy', + fetch: async () => + new Response(JSON.stringify({ id: 'bar' }), { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, + }), + }); + + const { + data: message, + response, + request_id, + } = await client.messages + .create({ messages: [], model: 'claude-3-opus-20240229', max_tokens: 1024 }) + .withResponse(); + + expect(request_id).toBe('req_xxx'); + expect(response.headers.get('request-id')).toBe('req_xxx'); + expect(message.id).toBe('bar'); + expect(JSON.stringify(message)).toBe('{"id":"bar"}'); + }); + + test('object response', async () => { + const client = new Anthropic({ + apiKey: 'dummy', + fetch: async () => + new Response(JSON.stringify({ id: 'bar' }), { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, + }), + }); + + const rsp = await client.messages.create({ + messages: [], + model: 'claude-3-opus-20240229', + max_tokens: 1024, + }); + expect(rsp.id).toBe('bar'); + expect(rsp._request_id).toBe('req_xxx'); + expect(JSON.stringify(rsp)).toBe('{"id":"bar"}'); + }); + + test('envelope response', async () => { + const promise = new APIPromise<{ data: { foo: string } }>( + (async () => { + return { + response: new Response(JSON.stringify({ data: { foo: 'bar' } }), { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, + }), + controller: {} as any, + options: {} as any, + }; + })(), + )._thenUnwrap((d) => d.data); + + const rsp = await promise; + expect(rsp.foo).toBe('bar'); + expect(rsp._request_id).toBe('req_xxx'); + }); + + test('page response', async () => { + const client = new Anthropic({ + apiKey: 'dummy', + fetch: async () => + new Response(JSON.stringify({ data: [{ foo: 'bar' }] }), { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, + }), + }); + + const page = await client.beta.messages.batches.list(); + expect(page.data).toMatchObject([{ foo: 'bar' }]); + expect((page as any)._request_id).toBeUndefined(); + }); + + test('array response', async () => { + const promise = new APIPromise>( + (async () => { + return { + response: new Response(JSON.stringify([{ foo: 'bar' }]), { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/json' }, + }), + controller: {} as any, + options: {} as any, + }; + })(), + ); + + const rsp = await promise; + expect(rsp.length).toBe(1); + expect(rsp[0]).toMatchObject({ foo: 'bar' }); + expect((rsp as any)._request_id).toBeUndefined(); + }); + + test('string response', async () => { + const promise = new APIPromise( + (async () => { + return { + response: new Response('hello world', { + headers: { 'request-id': 'req_xxx', 'content-type': 'application/text' }, + }), + controller: {} as any, + options: {} as any, + }; + })(), + ); + + const result = await promise; + expect(result).toBe('hello world'); + expect((result as any)._request_id).toBeUndefined(); + }); +}); From 9fe69e06899f357ff7505b7d008322da8ba9efbe Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 13:25:07 +0000 Subject: [PATCH 07/10] CI: run on alpha branches --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbd361f3..b5123d7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,12 @@ on: push: branches: - main + - alpha pull_request: branches: - main - next + - alpha jobs: lint: @@ -64,4 +66,3 @@ jobs: - name: Run tests run: ./scripts/test - From 0c844f96b2edaf389a6d6d04364a4eb043ee180c Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 13:28:30 +0000 Subject: [PATCH 08/10] chore(internal): temporarily disable updated for 3p clients --- scripts/build-all | 11 ++++++----- scripts/lint | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/build-all b/scripts/build-all index 26fcc0ca..a2fe3f6c 100755 --- a/scripts/build-all +++ b/scripts/build-all @@ -5,8 +5,9 @@ set -exuo pipefail bash ./scripts/build -for dir in packages/*; do - if [ -d "$dir" ]; then - (cd "$dir" && yarn install && yarn build) - fi -done +# temporarily disabled +# for dir in packages/*; do +# if [ -d "$dir" ]; then +# (cd "$dir" && yarn install && yarn build) +# fi +# done diff --git a/scripts/lint b/scripts/lint index 0096d1e5..5aa027aa 100755 --- a/scripts/lint +++ b/scripts/lint @@ -5,7 +5,7 @@ set -e cd "$(dirname "$0")/.." echo "==> Running eslint" -ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . +ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . --ignore-pattern="packages/" echo "==> Building" ./scripts/build # also checks types From 670b08350a46fe851b59eb8ac7edfd16f423a53c Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 13:33:25 +0000 Subject: [PATCH 09/10] fix tests --- jest.config.ts | 1 - tests/api-resources/MessageStream.test.ts | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 98b9666d..8a613dcb 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -10,7 +10,6 @@ const config: JestConfigWithTsJest = { '^@anthropic-ai/sdk$': '/src/index.ts', '^@anthropic-ai/sdk/(.*)$': '/src/$1', }, - setupFilesAfterEnv: ['/jest.setup.ts'], modulePathIgnorePatterns: [ '/ecosystem-tests/', '/dist/', diff --git a/tests/api-resources/MessageStream.test.ts b/tests/api-resources/MessageStream.test.ts index 0051d397..048dd7cc 100644 --- a/tests/api-resources/MessageStream.test.ts +++ b/tests/api-resources/MessageStream.test.ts @@ -1,10 +1,9 @@ import { PassThrough } from 'stream'; -import { Response } from 'node-fetch'; import Anthropic, { APIConnectionError, APIUserAbortError } from '@anthropic-ai/sdk'; import { Message, MessageStreamEvent } from '@anthropic-ai/sdk/resources/messages'; import { type RequestInfo, type RequestInit } from '@anthropic-ai/sdk/_shims/index'; -type Fetch = (req: string | RequestInfo, init?: RequestInit) => Promise; +type Fetch = typeof fetch; function assertNever(x: never): never { throw new Error(`unreachable: ${x}`); @@ -74,7 +73,11 @@ async function* messageIterable(message: Message): AsyncGenerator void; + handleMessageStreamEvents: (iter: AsyncIterable) => void; +} { const queue: Promise[] = []; const readResolvers: ((handler: typeof fetch) => void)[] = []; @@ -131,7 +134,7 @@ function mockFetch() { }); } - return { fetch, handleRequest, handleMessageStreamEvents }; + return { fetch: fetch as any, handleRequest, handleMessageStreamEvents }; } describe('MessageStream class', () => { From 0eae77a37bdf25443b1ce2ade1c1af0164f3429d Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Fri, 20 Dec 2024 13:27:47 +0000 Subject: [PATCH 10/10] chore: bump version to 0.34.0-alpha.0 --- .release-please-manifest.json | 2 +- package.json | 2 +- src/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2053c67b..1c924e72 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,5 +1,5 @@ { - ".": "0.33.1", + ".": "0.34.0-alpha.0", "packages/vertex-sdk": "0.6.1", "packages/bedrock-sdk": "0.12.0" } diff --git a/package.json b/package.json index b07a2f30..5504f094 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@anthropic-ai/sdk", - "version": "0.33.1", + "version": "0.34.0-alpha.0", "description": "The official TypeScript library for the Anthropic API", "author": "Anthropic ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index 4a46c186..0ef4aa74 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.33.1'; // x-release-please-version +export const VERSION = '0.34.0-alpha.0'; // x-release-please-version