core.mjs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
  2. if (kind === "m") throw new TypeError("Private method is not writable");
  3. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
  4. if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
  5. return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
  6. };
  7. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
  8. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
  9. if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
  10. return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
  11. };
  12. var _AbstractPage_client;
  13. import { VERSION } from "./version.mjs";
  14. import { Stream } from "./streaming.mjs";
  15. import { OpenAIError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, } from "./error.mjs";
  16. import { kind as shimsKind, getDefaultAgent, fetch, init, } from "./_shims/index.mjs";
  17. // try running side effects outside of _shims/index to workaround https://github.com/vercel/next.js/issues/76881
  18. init();
  19. import { isBlobLike, isMultipartBody } from "./uploads.mjs";
  20. export { maybeMultipartFormRequestOptions, multipartFormRequestOptions, createForm, } from "./uploads.mjs";
  21. async function defaultParseResponse(props) {
  22. const { response } = props;
  23. if (props.options.stream) {
  24. debug('response', response.status, response.url, response.headers, response.body);
  25. // Note: there is an invariant here that isn't represented in the type system
  26. // that if you set `stream: true` the response type must also be `Stream<T>`
  27. if (props.options.__streamClass) {
  28. return props.options.__streamClass.fromSSEResponse(response, props.controller);
  29. }
  30. return Stream.fromSSEResponse(response, props.controller);
  31. }
  32. // fetch refuses to read the body when the status code is 204.
  33. if (response.status === 204) {
  34. return null;
  35. }
  36. if (props.options.__binaryResponse) {
  37. return response;
  38. }
  39. const contentType = response.headers.get('content-type');
  40. const mediaType = contentType?.split(';')[0]?.trim();
  41. const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
  42. if (isJSON) {
  43. const json = await response.json();
  44. debug('response', response.status, response.url, response.headers, json);
  45. return _addRequestID(json, response);
  46. }
  47. const text = await response.text();
  48. debug('response', response.status, response.url, response.headers, text);
  49. // TODO handle blob, arraybuffer, other content types, etc.
  50. return text;
  51. }
  52. function _addRequestID(value, response) {
  53. if (!value || typeof value !== 'object' || Array.isArray(value)) {
  54. return value;
  55. }
  56. return Object.defineProperty(value, '_request_id', {
  57. value: response.headers.get('x-request-id'),
  58. enumerable: false,
  59. });
  60. }
  61. /**
  62. * A subclass of `Promise` providing additional helper methods
  63. * for interacting with the SDK.
  64. */
  65. export class APIPromise extends Promise {
  66. constructor(responsePromise, parseResponse = defaultParseResponse) {
  67. super((resolve) => {
  68. // this is maybe a bit weird but this has to be a no-op to not implicitly
  69. // parse the response body; instead .then, .catch, .finally are overridden
  70. // to parse the response
  71. resolve(null);
  72. });
  73. this.responsePromise = responsePromise;
  74. this.parseResponse = parseResponse;
  75. }
  76. _thenUnwrap(transform) {
  77. return new APIPromise(this.responsePromise, async (props) => _addRequestID(transform(await this.parseResponse(props), props), props.response));
  78. }
  79. /**
  80. * Gets the raw `Response` instance instead of parsing the response
  81. * data.
  82. *
  83. * If you want to parse the response body but still get the `Response`
  84. * instance, you can use {@link withResponse()}.
  85. *
  86. * 👋 Getting the wrong TypeScript type for `Response`?
  87. * Try setting `"moduleResolution": "NodeNext"` if you can,
  88. * or add one of these imports before your first `import … from 'openai'`:
  89. * - `import 'openai/shims/node'` (if you're running on Node)
  90. * - `import 'openai/shims/web'` (otherwise)
  91. */
  92. asResponse() {
  93. return this.responsePromise.then((p) => p.response);
  94. }
  95. /**
  96. * Gets the parsed response data, the raw `Response` instance and the ID of the request,
  97. * returned via the X-Request-ID header which is useful for debugging requests and reporting
  98. * issues to OpenAI.
  99. *
  100. * If you just want to get the raw `Response` instance without parsing it,
  101. * you can use {@link asResponse()}.
  102. *
  103. *
  104. * 👋 Getting the wrong TypeScript type for `Response`?
  105. * Try setting `"moduleResolution": "NodeNext"` if you can,
  106. * or add one of these imports before your first `import … from 'openai'`:
  107. * - `import 'openai/shims/node'` (if you're running on Node)
  108. * - `import 'openai/shims/web'` (otherwise)
  109. */
  110. async withResponse() {
  111. const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
  112. return { data, response, request_id: response.headers.get('x-request-id') };
  113. }
  114. parse() {
  115. if (!this.parsedPromise) {
  116. this.parsedPromise = this.responsePromise.then(this.parseResponse);
  117. }
  118. return this.parsedPromise;
  119. }
  120. then(onfulfilled, onrejected) {
  121. return this.parse().then(onfulfilled, onrejected);
  122. }
  123. catch(onrejected) {
  124. return this.parse().catch(onrejected);
  125. }
  126. finally(onfinally) {
  127. return this.parse().finally(onfinally);
  128. }
  129. }
  130. export class APIClient {
  131. constructor({ baseURL, maxRetries = 2, timeout = 600000, // 10 minutes
  132. httpAgent, fetch: overriddenFetch, }) {
  133. this.baseURL = baseURL;
  134. this.maxRetries = validatePositiveInteger('maxRetries', maxRetries);
  135. this.timeout = validatePositiveInteger('timeout', timeout);
  136. this.httpAgent = httpAgent;
  137. this.fetch = overriddenFetch ?? fetch;
  138. }
  139. authHeaders(opts) {
  140. return {};
  141. }
  142. /**
  143. * Override this to add your own default headers, for example:
  144. *
  145. * {
  146. * ...super.defaultHeaders(),
  147. * Authorization: 'Bearer 123',
  148. * }
  149. */
  150. defaultHeaders(opts) {
  151. return {
  152. Accept: 'application/json',
  153. 'Content-Type': 'application/json',
  154. 'User-Agent': this.getUserAgent(),
  155. ...getPlatformHeaders(),
  156. ...this.authHeaders(opts),
  157. };
  158. }
  159. /**
  160. * Override this to add your own headers validation:
  161. */
  162. validateHeaders(headers, customHeaders) { }
  163. defaultIdempotencyKey() {
  164. return `stainless-node-retry-${uuid4()}`;
  165. }
  166. get(path, opts) {
  167. return this.methodRequest('get', path, opts);
  168. }
  169. post(path, opts) {
  170. return this.methodRequest('post', path, opts);
  171. }
  172. patch(path, opts) {
  173. return this.methodRequest('patch', path, opts);
  174. }
  175. put(path, opts) {
  176. return this.methodRequest('put', path, opts);
  177. }
  178. delete(path, opts) {
  179. return this.methodRequest('delete', path, opts);
  180. }
  181. methodRequest(method, path, opts) {
  182. return this.request(Promise.resolve(opts).then(async (opts) => {
  183. const body = opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer())
  184. : opts?.body instanceof DataView ? opts.body
  185. : opts?.body instanceof ArrayBuffer ? new DataView(opts.body)
  186. : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer)
  187. : opts?.body;
  188. return { method, path, ...opts, body };
  189. }));
  190. }
  191. getAPIList(path, Page, opts) {
  192. return this.requestAPIList(Page, { method: 'get', path, ...opts });
  193. }
  194. calculateContentLength(body) {
  195. if (typeof body === 'string') {
  196. if (typeof Buffer !== 'undefined') {
  197. return Buffer.byteLength(body, 'utf8').toString();
  198. }
  199. if (typeof TextEncoder !== 'undefined') {
  200. const encoder = new TextEncoder();
  201. const encoded = encoder.encode(body);
  202. return encoded.length.toString();
  203. }
  204. }
  205. else if (ArrayBuffer.isView(body)) {
  206. return body.byteLength.toString();
  207. }
  208. return null;
  209. }
  210. buildRequest(inputOptions, { retryCount = 0 } = {}) {
  211. const options = { ...inputOptions };
  212. const { method, path, query, headers: headers = {} } = options;
  213. const body = ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ?
  214. options.body
  215. : isMultipartBody(options.body) ? options.body.body
  216. : options.body ? JSON.stringify(options.body, null, 2)
  217. : null;
  218. const contentLength = this.calculateContentLength(body);
  219. const url = this.buildURL(path, query);
  220. if ('timeout' in options)
  221. validatePositiveInteger('timeout', options.timeout);
  222. options.timeout = options.timeout ?? this.timeout;
  223. const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
  224. const minAgentTimeout = options.timeout + 1000;
  225. if (typeof httpAgent?.options?.timeout === 'number' &&
  226. minAgentTimeout > (httpAgent.options.timeout ?? 0)) {
  227. // Allow any given request to bump our agent active socket timeout.
  228. // This may seem strange, but leaking active sockets should be rare and not particularly problematic,
  229. // and without mutating agent we would need to create more of them.
  230. // This tradeoff optimizes for performance.
  231. httpAgent.options.timeout = minAgentTimeout;
  232. }
  233. if (this.idempotencyHeader && method !== 'get') {
  234. if (!inputOptions.idempotencyKey)
  235. inputOptions.idempotencyKey = this.defaultIdempotencyKey();
  236. headers[this.idempotencyHeader] = inputOptions.idempotencyKey;
  237. }
  238. const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount });
  239. const req = {
  240. method,
  241. ...(body && { body: body }),
  242. headers: reqHeaders,
  243. ...(httpAgent && { agent: httpAgent }),
  244. // @ts-ignore node-fetch uses a custom AbortSignal type that is
  245. // not compatible with standard web types
  246. signal: options.signal ?? null,
  247. };
  248. return { req, url, timeout: options.timeout };
  249. }
  250. buildHeaders({ options, headers, contentLength, retryCount, }) {
  251. const reqHeaders = {};
  252. if (contentLength) {
  253. reqHeaders['content-length'] = contentLength;
  254. }
  255. const defaultHeaders = this.defaultHeaders(options);
  256. applyHeadersMut(reqHeaders, defaultHeaders);
  257. applyHeadersMut(reqHeaders, headers);
  258. // let builtin fetch set the Content-Type for multipart bodies
  259. if (isMultipartBody(options.body) && shimsKind !== 'node') {
  260. delete reqHeaders['content-type'];
  261. }
  262. // Don't set theses headers if they were already set or removed through default headers or by the caller.
  263. // We check `defaultHeaders` and `headers`, which can contain nulls, instead of `reqHeaders` to account
  264. // for the removal case.
  265. if (getHeader(defaultHeaders, 'x-stainless-retry-count') === undefined &&
  266. getHeader(headers, 'x-stainless-retry-count') === undefined) {
  267. reqHeaders['x-stainless-retry-count'] = String(retryCount);
  268. }
  269. if (getHeader(defaultHeaders, 'x-stainless-timeout') === undefined &&
  270. getHeader(headers, 'x-stainless-timeout') === undefined &&
  271. options.timeout) {
  272. reqHeaders['x-stainless-timeout'] = String(Math.trunc(options.timeout / 1000));
  273. }
  274. this.validateHeaders(reqHeaders, headers);
  275. return reqHeaders;
  276. }
  277. /**
  278. * Used as a callback for mutating the given `FinalRequestOptions` object.
  279. */
  280. async prepareOptions(options) { }
  281. /**
  282. * Used as a callback for mutating the given `RequestInit` object.
  283. *
  284. * This is useful for cases where you want to add certain headers based off of
  285. * the request properties, e.g. `method` or `url`.
  286. */
  287. async prepareRequest(request, { url, options }) { }
  288. parseHeaders(headers) {
  289. return (!headers ? {}
  290. : Symbol.iterator in headers ?
  291. Object.fromEntries(Array.from(headers).map((header) => [...header]))
  292. : { ...headers });
  293. }
  294. makeStatusError(status, error, message, headers) {
  295. return APIError.generate(status, error, message, headers);
  296. }
  297. request(options, remainingRetries = null) {
  298. return new APIPromise(this.makeRequest(options, remainingRetries));
  299. }
  300. async makeRequest(optionsInput, retriesRemaining) {
  301. const options = await optionsInput;
  302. const maxRetries = options.maxRetries ?? this.maxRetries;
  303. if (retriesRemaining == null) {
  304. retriesRemaining = maxRetries;
  305. }
  306. await this.prepareOptions(options);
  307. const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining });
  308. await this.prepareRequest(req, { url, options });
  309. debug('request', url, options, req.headers);
  310. if (options.signal?.aborted) {
  311. throw new APIUserAbortError();
  312. }
  313. const controller = new AbortController();
  314. const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
  315. if (response instanceof Error) {
  316. if (options.signal?.aborted) {
  317. throw new APIUserAbortError();
  318. }
  319. if (retriesRemaining) {
  320. return this.retryRequest(options, retriesRemaining);
  321. }
  322. if (response.name === 'AbortError') {
  323. throw new APIConnectionTimeoutError();
  324. }
  325. throw new APIConnectionError({ cause: response });
  326. }
  327. const responseHeaders = createResponseHeaders(response.headers);
  328. if (!response.ok) {
  329. if (retriesRemaining && this.shouldRetry(response)) {
  330. const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
  331. debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders);
  332. return this.retryRequest(options, retriesRemaining, responseHeaders);
  333. }
  334. const errText = await response.text().catch((e) => castToError(e).message);
  335. const errJSON = safeJSON(errText);
  336. const errMessage = errJSON ? undefined : errText;
  337. const retryMessage = retriesRemaining ? `(error; no more retries left)` : `(error; not retryable)`;
  338. debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders, errMessage);
  339. const err = this.makeStatusError(response.status, errJSON, errMessage, responseHeaders);
  340. throw err;
  341. }
  342. return { response, options, controller };
  343. }
  344. requestAPIList(Page, options) {
  345. const request = this.makeRequest(options, null);
  346. return new PagePromise(this, request, Page);
  347. }
  348. buildURL(path, query) {
  349. const url = isAbsoluteURL(path) ?
  350. new URL(path)
  351. : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
  352. const defaultQuery = this.defaultQuery();
  353. if (!isEmptyObj(defaultQuery)) {
  354. query = { ...defaultQuery, ...query };
  355. }
  356. if (typeof query === 'object' && query && !Array.isArray(query)) {
  357. url.search = this.stringifyQuery(query);
  358. }
  359. return url.toString();
  360. }
  361. stringifyQuery(query) {
  362. return Object.entries(query)
  363. .filter(([_, value]) => typeof value !== 'undefined')
  364. .map(([key, value]) => {
  365. if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
  366. return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
  367. }
  368. if (value === null) {
  369. return `${encodeURIComponent(key)}=`;
  370. }
  371. throw new OpenAIError(`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.`);
  372. })
  373. .join('&');
  374. }
  375. async fetchWithTimeout(url, init, ms, controller) {
  376. const { signal, ...options } = init || {};
  377. if (signal)
  378. signal.addEventListener('abort', () => controller.abort());
  379. const timeout = setTimeout(() => controller.abort(), ms);
  380. const fetchOptions = {
  381. signal: controller.signal,
  382. ...options,
  383. };
  384. if (fetchOptions.method) {
  385. // Custom methods like 'patch' need to be uppercased
  386. // See https://github.com/nodejs/undici/issues/2294
  387. fetchOptions.method = fetchOptions.method.toUpperCase();
  388. }
  389. return (
  390. // use undefined this binding; fetch errors if bound to something else in browser/cloudflare
  391. this.fetch.call(undefined, url, fetchOptions).finally(() => {
  392. clearTimeout(timeout);
  393. }));
  394. }
  395. shouldRetry(response) {
  396. // Note this is not a standard header.
  397. const shouldRetryHeader = response.headers.get('x-should-retry');
  398. // If the server explicitly says whether or not to retry, obey.
  399. if (shouldRetryHeader === 'true')
  400. return true;
  401. if (shouldRetryHeader === 'false')
  402. return false;
  403. // Retry on request timeouts.
  404. if (response.status === 408)
  405. return true;
  406. // Retry on lock timeouts.
  407. if (response.status === 409)
  408. return true;
  409. // Retry on rate limits.
  410. if (response.status === 429)
  411. return true;
  412. // Retry internal errors.
  413. if (response.status >= 500)
  414. return true;
  415. return false;
  416. }
  417. async retryRequest(options, retriesRemaining, responseHeaders) {
  418. let timeoutMillis;
  419. // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it.
  420. const retryAfterMillisHeader = responseHeaders?.['retry-after-ms'];
  421. if (retryAfterMillisHeader) {
  422. const timeoutMs = parseFloat(retryAfterMillisHeader);
  423. if (!Number.isNaN(timeoutMs)) {
  424. timeoutMillis = timeoutMs;
  425. }
  426. }
  427. // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
  428. const retryAfterHeader = responseHeaders?.['retry-after'];
  429. if (retryAfterHeader && !timeoutMillis) {
  430. const timeoutSeconds = parseFloat(retryAfterHeader);
  431. if (!Number.isNaN(timeoutSeconds)) {
  432. timeoutMillis = timeoutSeconds * 1000;
  433. }
  434. else {
  435. timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
  436. }
  437. }
  438. // If the API asks us to wait a certain amount of time (and it's a reasonable amount),
  439. // just do what it says, but otherwise calculate a default
  440. if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) {
  441. const maxRetries = options.maxRetries ?? this.maxRetries;
  442. timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
  443. }
  444. await sleep(timeoutMillis);
  445. return this.makeRequest(options, retriesRemaining - 1);
  446. }
  447. calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries) {
  448. const initialRetryDelay = 0.5;
  449. const maxRetryDelay = 8.0;
  450. const numRetries = maxRetries - retriesRemaining;
  451. // Apply exponential backoff, but not more than the max.
  452. const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);
  453. // Apply some jitter, take up to at most 25 percent of the retry time.
  454. const jitter = 1 - Math.random() * 0.25;
  455. return sleepSeconds * jitter * 1000;
  456. }
  457. getUserAgent() {
  458. return `${this.constructor.name}/JS ${VERSION}`;
  459. }
  460. }
  461. export class AbstractPage {
  462. constructor(client, response, body, options) {
  463. _AbstractPage_client.set(this, void 0);
  464. __classPrivateFieldSet(this, _AbstractPage_client, client, "f");
  465. this.options = options;
  466. this.response = response;
  467. this.body = body;
  468. }
  469. hasNextPage() {
  470. const items = this.getPaginatedItems();
  471. if (!items.length)
  472. return false;
  473. return this.nextPageInfo() != null;
  474. }
  475. async getNextPage() {
  476. const nextInfo = this.nextPageInfo();
  477. if (!nextInfo) {
  478. throw new OpenAIError('No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.');
  479. }
  480. const nextOptions = { ...this.options };
  481. if ('params' in nextInfo && typeof nextOptions.query === 'object') {
  482. nextOptions.query = { ...nextOptions.query, ...nextInfo.params };
  483. }
  484. else if ('url' in nextInfo) {
  485. const params = [...Object.entries(nextOptions.query || {}), ...nextInfo.url.searchParams.entries()];
  486. for (const [key, value] of params) {
  487. nextInfo.url.searchParams.set(key, value);
  488. }
  489. nextOptions.query = undefined;
  490. nextOptions.path = nextInfo.url.toString();
  491. }
  492. return await __classPrivateFieldGet(this, _AbstractPage_client, "f").requestAPIList(this.constructor, nextOptions);
  493. }
  494. async *iterPages() {
  495. // eslint-disable-next-line @typescript-eslint/no-this-alias
  496. let page = this;
  497. yield page;
  498. while (page.hasNextPage()) {
  499. page = await page.getNextPage();
  500. yield page;
  501. }
  502. }
  503. async *[(_AbstractPage_client = new WeakMap(), Symbol.asyncIterator)]() {
  504. for await (const page of this.iterPages()) {
  505. for (const item of page.getPaginatedItems()) {
  506. yield item;
  507. }
  508. }
  509. }
  510. }
  511. /**
  512. * This subclass of Promise will resolve to an instantiated Page once the request completes.
  513. *
  514. * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
  515. *
  516. * for await (const item of client.items.list()) {
  517. * console.log(item)
  518. * }
  519. */
  520. export class PagePromise extends APIPromise {
  521. constructor(client, request, Page) {
  522. super(request, async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options));
  523. }
  524. /**
  525. * Allow auto-paginating iteration on an unawaited list call, eg:
  526. *
  527. * for await (const item of client.items.list()) {
  528. * console.log(item)
  529. * }
  530. */
  531. async *[Symbol.asyncIterator]() {
  532. const page = await this;
  533. for await (const item of page) {
  534. yield item;
  535. }
  536. }
  537. }
  538. export const createResponseHeaders = (headers) => {
  539. return new Proxy(Object.fromEntries(
  540. // @ts-ignore
  541. headers.entries()), {
  542. get(target, name) {
  543. const key = name.toString();
  544. return target[key.toLowerCase()] || target[key];
  545. },
  546. });
  547. };
  548. // This is required so that we can determine if a given object matches the RequestOptions
  549. // type at runtime. While this requires duplication, it is enforced by the TypeScript
  550. // compiler such that any missing / extraneous keys will cause an error.
  551. const requestOptionsKeys = {
  552. method: true,
  553. path: true,
  554. query: true,
  555. body: true,
  556. headers: true,
  557. maxRetries: true,
  558. stream: true,
  559. timeout: true,
  560. httpAgent: true,
  561. signal: true,
  562. idempotencyKey: true,
  563. __metadata: true,
  564. __binaryRequest: true,
  565. __binaryResponse: true,
  566. __streamClass: true,
  567. };
  568. export const isRequestOptions = (obj) => {
  569. return (typeof obj === 'object' &&
  570. obj !== null &&
  571. !isEmptyObj(obj) &&
  572. Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k)));
  573. };
  574. const getPlatformProperties = () => {
  575. if (typeof Deno !== 'undefined' && Deno.build != null) {
  576. return {
  577. 'X-Stainless-Lang': 'js',
  578. 'X-Stainless-Package-Version': VERSION,
  579. 'X-Stainless-OS': normalizePlatform(Deno.build.os),
  580. 'X-Stainless-Arch': normalizeArch(Deno.build.arch),
  581. 'X-Stainless-Runtime': 'deno',
  582. 'X-Stainless-Runtime-Version': typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
  583. };
  584. }
  585. if (typeof EdgeRuntime !== 'undefined') {
  586. return {
  587. 'X-Stainless-Lang': 'js',
  588. 'X-Stainless-Package-Version': VERSION,
  589. 'X-Stainless-OS': 'Unknown',
  590. 'X-Stainless-Arch': `other:${EdgeRuntime}`,
  591. 'X-Stainless-Runtime': 'edge',
  592. 'X-Stainless-Runtime-Version': process.version,
  593. };
  594. }
  595. // Check if Node.js
  596. if (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') {
  597. return {
  598. 'X-Stainless-Lang': 'js',
  599. 'X-Stainless-Package-Version': VERSION,
  600. 'X-Stainless-OS': normalizePlatform(process.platform),
  601. 'X-Stainless-Arch': normalizeArch(process.arch),
  602. 'X-Stainless-Runtime': 'node',
  603. 'X-Stainless-Runtime-Version': process.version,
  604. };
  605. }
  606. const browserInfo = getBrowserInfo();
  607. if (browserInfo) {
  608. return {
  609. 'X-Stainless-Lang': 'js',
  610. 'X-Stainless-Package-Version': VERSION,
  611. 'X-Stainless-OS': 'Unknown',
  612. 'X-Stainless-Arch': 'unknown',
  613. 'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
  614. 'X-Stainless-Runtime-Version': browserInfo.version,
  615. };
  616. }
  617. // TODO add support for Cloudflare workers, etc.
  618. return {
  619. 'X-Stainless-Lang': 'js',
  620. 'X-Stainless-Package-Version': VERSION,
  621. 'X-Stainless-OS': 'Unknown',
  622. 'X-Stainless-Arch': 'unknown',
  623. 'X-Stainless-Runtime': 'unknown',
  624. 'X-Stainless-Runtime-Version': 'unknown',
  625. };
  626. };
  627. // Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
  628. function getBrowserInfo() {
  629. if (typeof navigator === 'undefined' || !navigator) {
  630. return null;
  631. }
  632. // NOTE: The order matters here!
  633. const browserPatterns = [
  634. { key: 'edge', pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
  635. { key: 'ie', pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
  636. { key: 'ie', pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
  637. { key: 'chrome', pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
  638. { key: 'firefox', pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
  639. { key: 'safari', pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
  640. ];
  641. // Find the FIRST matching browser
  642. for (const { key, pattern } of browserPatterns) {
  643. const match = pattern.exec(navigator.userAgent);
  644. if (match) {
  645. const major = match[1] || 0;
  646. const minor = match[2] || 0;
  647. const patch = match[3] || 0;
  648. return { browser: key, version: `${major}.${minor}.${patch}` };
  649. }
  650. }
  651. return null;
  652. }
  653. const normalizeArch = (arch) => {
  654. // Node docs:
  655. // - https://nodejs.org/api/process.html#processarch
  656. // Deno docs:
  657. // - https://doc.deno.land/deno/stable/~/Deno.build
  658. if (arch === 'x32')
  659. return 'x32';
  660. if (arch === 'x86_64' || arch === 'x64')
  661. return 'x64';
  662. if (arch === 'arm')
  663. return 'arm';
  664. if (arch === 'aarch64' || arch === 'arm64')
  665. return 'arm64';
  666. if (arch)
  667. return `other:${arch}`;
  668. return 'unknown';
  669. };
  670. const normalizePlatform = (platform) => {
  671. // Node platforms:
  672. // - https://nodejs.org/api/process.html#processplatform
  673. // Deno platforms:
  674. // - https://doc.deno.land/deno/stable/~/Deno.build
  675. // - https://github.com/denoland/deno/issues/14799
  676. platform = platform.toLowerCase();
  677. // NOTE: this iOS check is untested and may not work
  678. // Node does not work natively on IOS, there is a fork at
  679. // https://github.com/nodejs-mobile/nodejs-mobile
  680. // however it is unknown at the time of writing how to detect if it is running
  681. if (platform.includes('ios'))
  682. return 'iOS';
  683. if (platform === 'android')
  684. return 'Android';
  685. if (platform === 'darwin')
  686. return 'MacOS';
  687. if (platform === 'win32')
  688. return 'Windows';
  689. if (platform === 'freebsd')
  690. return 'FreeBSD';
  691. if (platform === 'openbsd')
  692. return 'OpenBSD';
  693. if (platform === 'linux')
  694. return 'Linux';
  695. if (platform)
  696. return `Other:${platform}`;
  697. return 'Unknown';
  698. };
  699. let _platformHeaders;
  700. const getPlatformHeaders = () => {
  701. return (_platformHeaders ?? (_platformHeaders = getPlatformProperties()));
  702. };
  703. export const safeJSON = (text) => {
  704. try {
  705. return JSON.parse(text);
  706. }
  707. catch (err) {
  708. return undefined;
  709. }
  710. };
  711. // https://url.spec.whatwg.org/#url-scheme-string
  712. const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
  713. const isAbsoluteURL = (url) => {
  714. return startsWithSchemeRegexp.test(url);
  715. };
  716. export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  717. const validatePositiveInteger = (name, n) => {
  718. if (typeof n !== 'number' || !Number.isInteger(n)) {
  719. throw new OpenAIError(`${name} must be an integer`);
  720. }
  721. if (n < 0) {
  722. throw new OpenAIError(`${name} must be a positive integer`);
  723. }
  724. return n;
  725. };
  726. export const castToError = (err) => {
  727. if (err instanceof Error)
  728. return err;
  729. if (typeof err === 'object' && err !== null) {
  730. try {
  731. return new Error(JSON.stringify(err));
  732. }
  733. catch { }
  734. }
  735. return new Error(err);
  736. };
  737. export const ensurePresent = (value) => {
  738. if (value == null)
  739. throw new OpenAIError(`Expected a value to be given but received ${value} instead.`);
  740. return value;
  741. };
  742. /**
  743. * Read an environment variable.
  744. *
  745. * Trims beginning and trailing whitespace.
  746. *
  747. * Will return undefined if the environment variable doesn't exist or cannot be accessed.
  748. */
  749. export const readEnv = (env) => {
  750. if (typeof process !== 'undefined') {
  751. return process.env?.[env]?.trim() ?? undefined;
  752. }
  753. if (typeof Deno !== 'undefined') {
  754. return Deno.env?.get?.(env)?.trim();
  755. }
  756. return undefined;
  757. };
  758. export const coerceInteger = (value) => {
  759. if (typeof value === 'number')
  760. return Math.round(value);
  761. if (typeof value === 'string')
  762. return parseInt(value, 10);
  763. throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
  764. };
  765. export const coerceFloat = (value) => {
  766. if (typeof value === 'number')
  767. return value;
  768. if (typeof value === 'string')
  769. return parseFloat(value);
  770. throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
  771. };
  772. export const coerceBoolean = (value) => {
  773. if (typeof value === 'boolean')
  774. return value;
  775. if (typeof value === 'string')
  776. return value === 'true';
  777. return Boolean(value);
  778. };
  779. export const maybeCoerceInteger = (value) => {
  780. if (value === undefined) {
  781. return undefined;
  782. }
  783. return coerceInteger(value);
  784. };
  785. export const maybeCoerceFloat = (value) => {
  786. if (value === undefined) {
  787. return undefined;
  788. }
  789. return coerceFloat(value);
  790. };
  791. export const maybeCoerceBoolean = (value) => {
  792. if (value === undefined) {
  793. return undefined;
  794. }
  795. return coerceBoolean(value);
  796. };
  797. // https://stackoverflow.com/a/34491287
  798. export function isEmptyObj(obj) {
  799. if (!obj)
  800. return true;
  801. for (const _k in obj)
  802. return false;
  803. return true;
  804. }
  805. // https://eslint.org/docs/latest/rules/no-prototype-builtins
  806. export function hasOwn(obj, key) {
  807. return Object.prototype.hasOwnProperty.call(obj, key);
  808. }
  809. /**
  810. * Copies headers from "newHeaders" onto "targetHeaders",
  811. * using lower-case for all properties,
  812. * ignoring any keys with undefined values,
  813. * and deleting any keys with null values.
  814. */
  815. function applyHeadersMut(targetHeaders, newHeaders) {
  816. for (const k in newHeaders) {
  817. if (!hasOwn(newHeaders, k))
  818. continue;
  819. const lowerKey = k.toLowerCase();
  820. if (!lowerKey)
  821. continue;
  822. const val = newHeaders[k];
  823. if (val === null) {
  824. delete targetHeaders[lowerKey];
  825. }
  826. else if (val !== undefined) {
  827. targetHeaders[lowerKey] = val;
  828. }
  829. }
  830. }
  831. const SENSITIVE_HEADERS = new Set(['authorization', 'api-key']);
  832. export function debug(action, ...args) {
  833. if (typeof process !== 'undefined' && process?.env?.['DEBUG'] === 'true') {
  834. const modifiedArgs = args.map((arg) => {
  835. if (!arg) {
  836. return arg;
  837. }
  838. // Check for sensitive headers in request body 'headers' object
  839. if (arg['headers']) {
  840. // clone so we don't mutate
  841. const modifiedArg = { ...arg, headers: { ...arg['headers'] } };
  842. for (const header in arg['headers']) {
  843. if (SENSITIVE_HEADERS.has(header.toLowerCase())) {
  844. modifiedArg['headers'][header] = 'REDACTED';
  845. }
  846. }
  847. return modifiedArg;
  848. }
  849. let modifiedArg = null;
  850. // Check for sensitive headers in headers object
  851. for (const header in arg) {
  852. if (SENSITIVE_HEADERS.has(header.toLowerCase())) {
  853. // avoid making a copy until we need to
  854. modifiedArg ?? (modifiedArg = { ...arg });
  855. modifiedArg[header] = 'REDACTED';
  856. }
  857. }
  858. return modifiedArg ?? arg;
  859. });
  860. console.log(`OpenAI:DEBUG:${action}`, ...modifiedArgs);
  861. }
  862. }
  863. /**
  864. * https://stackoverflow.com/a/2117523
  865. */
  866. const uuid4 = () => {
  867. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  868. const r = (Math.random() * 16) | 0;
  869. const v = c === 'x' ? r : (r & 0x3) | 0x8;
  870. return v.toString(16);
  871. });
  872. };
  873. export const isRunningInBrowser = () => {
  874. return (
  875. // @ts-ignore
  876. typeof window !== 'undefined' &&
  877. // @ts-ignore
  878. typeof window.document !== 'undefined' &&
  879. // @ts-ignore
  880. typeof navigator !== 'undefined');
  881. };
  882. export const isHeadersProtocol = (headers) => {
  883. return typeof headers?.get === 'function';
  884. };
  885. export const getRequiredHeader = (headers, header) => {
  886. const foundHeader = getHeader(headers, header);
  887. if (foundHeader === undefined) {
  888. throw new Error(`Could not find ${header} header`);
  889. }
  890. return foundHeader;
  891. };
  892. export const getHeader = (headers, header) => {
  893. const lowerCasedHeader = header.toLowerCase();
  894. if (isHeadersProtocol(headers)) {
  895. // to deal with the case where the header looks like Stainless-Event-Id
  896. const intercapsHeader = header[0]?.toUpperCase() +
  897. header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase());
  898. for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) {
  899. const value = headers.get(key);
  900. if (value) {
  901. return value;
  902. }
  903. }
  904. }
  905. for (const [key, value] of Object.entries(headers)) {
  906. if (key.toLowerCase() === lowerCasedHeader) {
  907. if (Array.isArray(value)) {
  908. if (value.length <= 1)
  909. return value[0];
  910. console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`);
  911. return value[0];
  912. }
  913. return value;
  914. }
  915. }
  916. return undefined;
  917. };
  918. /**
  919. * Encodes a string to Base64 format.
  920. */
  921. export const toBase64 = (str) => {
  922. if (!str)
  923. return '';
  924. if (typeof Buffer !== 'undefined') {
  925. return Buffer.from(str).toString('base64');
  926. }
  927. if (typeof btoa !== 'undefined') {
  928. return btoa(str);
  929. }
  930. throw new OpenAIError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
  931. };
  932. /**
  933. * Converts a Base64 encoded string to a Float32Array.
  934. * @param base64Str - The Base64 encoded string.
  935. * @returns An Array of numbers interpreted as Float32 values.
  936. */
  937. export const toFloat32Array = (base64Str) => {
  938. if (typeof Buffer !== 'undefined') {
  939. // for Node.js environment
  940. const buf = Buffer.from(base64Str, 'base64');
  941. return Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.length / Float32Array.BYTES_PER_ELEMENT));
  942. }
  943. else {
  944. // for legacy web platform APIs
  945. const binaryStr = atob(base64Str);
  946. const len = binaryStr.length;
  947. const bytes = new Uint8Array(len);
  948. for (let i = 0; i < len; i++) {
  949. bytes[i] = binaryStr.charCodeAt(i);
  950. }
  951. return Array.from(new Float32Array(bytes.buffer));
  952. }
  953. };
  954. export function isObj(obj) {
  955. return obj != null && typeof obj === 'object' && !Array.isArray(obj);
  956. }
  957. //# sourceMappingURL=core.mjs.map