diff --git a/package.json b/package.json index 8751cc47..ea7866cf 100644 --- a/package.json +++ b/package.json @@ -48,17 +48,17 @@ "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, - "./*": { + "./*.mjs": { "types": "./dist/*.d.ts", - "require": "./dist/*.js", "default": "./dist/*.mjs" }, "./*.js": { "types": "./dist/*.d.ts", "default": "./dist/*.js" }, - "./*.mjs": { + "./*": { "types": "./dist/*.d.ts", + "require": "./dist/*.js", "default": "./dist/*.mjs" } }, @@ -74,14 +74,12 @@ "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", - "@types/qs": "^6.9.7", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "digest-fetch": "^1.3.0", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "qs": "^6.10.3" + "node-fetch": "^2.6.7" }, "devDependencies": { "@types/jest": "^29.4.0", diff --git a/src/_shims/fetch.js b/src/_shims/fetch.js index 28637363..3c3821fd 100644 --- a/src/_shims/fetch.js +++ b/src/_shims/fetch.js @@ -10,4 +10,4 @@ exports.Request = Request; exports.Response = Response; exports.Headers = Headers; -exports.isPolyfilled = true; +exports.isPolyfilled = false; diff --git a/src/core.ts b/src/core.ts index d5d0c043..e3a59f86 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,4 +1,3 @@ -import * as qs from 'qs'; import { VERSION } from './version'; import { Stream } from './streaming'; import { APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from './error'; @@ -82,18 +81,6 @@ export abstract class APIClient { */ protected validateHeaders(headers: Headers, customHeaders: Headers) {} - /** - * Override this to add your own qs.stringify options, for example: - * - * { - * ...super.qsOptions(), - * strictNullHandling: true, - * } - */ - protected qsOptions(): qs.IStringifyOptions | undefined { - return {}; - } - protected defaultIdempotencyKey(): string { return `stainless-node-retry-${uuid4()}`; } @@ -128,9 +115,11 @@ export abstract class APIClient { return Buffer.byteLength(body, 'utf8').toString(); } - const encoder = new TextEncoder(); - const encoded = encoder.encode(body); - return encoded.length.toString(); + if (typeof TextEncoder !== 'undefined') { + const encoder = new TextEncoder(); + const encoded = encoder.encode(body); + return encoded.length.toString(); + } } return null; @@ -302,12 +291,29 @@ export abstract class APIClient { } if (query) { - url.search = qs.stringify(query, this.qsOptions()); + url.search = this.stringifyQuery(query); } 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 Error( + `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, diff --git a/src/index.ts b/src/index.ts index 64c7a73e..de884e69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,12 @@ // File generated from our OpenAPI spec by Stainless. -import * as qs from 'qs'; import * as Core from './core'; import * as API from './resources/index'; import * as Errors from './error'; import type { Agent } from '@anthropic-ai/sdk/_shims/agent'; import * as Uploads from './uploads'; -type Config = { +export interface ClientOptions { /** * Defaults to process.env["ANTHROPIC_API_KEY"]. Set it to null if you want to send unauthenticated requests. */ @@ -68,20 +67,20 @@ type Config = { defaultQuery?: Core.DefaultQuery; authToken?: string | null; -}; +} /** Instantiate the API Client. */ export class Anthropic extends Core.APIClient { apiKey: string | null; authToken?: string | null; - private _options: Config; + private _options: ClientOptions; - constructor(config?: Config) { - const options: Config = { + constructor(opts?: ClientOptions) { + const options: ClientOptions = { apiKey: typeof process === 'undefined' ? '' : process.env['ANTHROPIC_API_KEY'] || '', baseURL: 'https://api.anthropic.com', - ...config, + ...opts, }; super({ @@ -94,7 +93,7 @@ export class Anthropic extends Core.APIClient { this.apiKey = options.apiKey || null; this._options = options; - this.authToken = config?.authToken || process.env['ANTHROPIC_AUTH_TOKEN'] || null; + this.authToken = opts?.authToken || process.env['ANTHROPIC_AUTH_TOKEN'] || null; } completions: API.Completions = new API.Completions(this); @@ -157,10 +156,6 @@ export class Anthropic extends Core.APIClient { return { Authorization: `Bearer ${this.authToken}` }; } - protected override qsOptions(): qs.IStringifyOptions { - return { arrayFormat: 'comma' }; - } - static Anthropic = this; static HUMAN_PROMPT = '\n\nHuman:'; static AI_PROMPT = '\n\nAssistant:'; diff --git a/src/resources/completions.ts b/src/resources/completions.ts index 3b6c51ad..dcf8111e 100644 --- a/src/resources/completions.ts +++ b/src/resources/completions.ts @@ -72,7 +72,7 @@ export namespace CompletionCreateParams { * [models](https://docs.anthropic.com/claude/reference/selecting-a-model) for * additional details. */ - model: string; + model: (string & {}) | 'claude-2' | 'claude-instant-1'; /** * The prompt that you want Claude to complete. @@ -175,7 +175,7 @@ export namespace CompletionCreateParams { * [models](https://docs.anthropic.com/claude/reference/selecting-a-model) for * additional details. */ - model: string; + model: (string & {}) | 'claude-2' | 'claude-instant-1'; /** * The prompt that you want Claude to complete. diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts new file mode 100644 index 00000000..82320845 --- /dev/null +++ b/tests/stringifyQuery.test.ts @@ -0,0 +1,26 @@ +import { APIClient } from '@anthropic-ai/sdk/core'; + +const { stringifyQuery } = APIClient.prototype as any; + +describe('APIClient.stringifyQuery', () => { + for (const [input, expected] of [ + [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], + [{ a: null, b: false, c: undefined }, 'a=&b=false'], + [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], + [ + { 'a/b': 'c/d', 'e=f': 'g&h' }, + `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( + 'e=f', + )}=${encodeURIComponent('g&h')}`, + ], + ]) { + it(`${JSON.stringify(input)} -> ${expected}`, () => { + expect(stringifyQuery(input)).toEqual(expected); + }); + } + for (const value of [[], {}, new Date()]) { + it(`${JSON.stringify(value)} -> `, () => { + expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); + }); + } +}); diff --git a/yarn.lock b/yarn.lock index cc48c9c1..9629d1d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -845,11 +845,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== -"@types/qs@^6.9.7": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -1260,14 +1255,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2027,15 +2014,6 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -2126,11 +2104,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3053,11 +3026,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -object-inspect@^1.9.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3397,13 +3365,6 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== -qs@^6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - queue-lit@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.5.0.tgz#8197fdafda1edd615c8a0fc14c48353626e5160a" @@ -3597,15 +3558,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - sigmund@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"