UNPKG

@anthropic-ai/bedrock-sdk

Version:

The official TypeScript library for the Anthropic Bedrock API

136 lines 5.77 kB
import { ReadableStreamFrom } from "./shims.mjs"; export const checkFileSupport = () => { if (typeof File === 'undefined') { const { process } = globalThis; const isOldNode = typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; throw new Error('`File` is not defined as a global, which is required for file uploads.' + (isOldNode ? " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." : '')); } }; /** * Construct a `File` instance. This is used to ensure a helpful error is thrown * for environments that don't define a global `File` yet. */ export function makeFile(fileBits, fileName, options) { checkFileSupport(); return new File(fileBits, fileName ?? 'unknown_file', options); } export function getName(value) { return (((typeof value === 'object' && value !== null && (('name' in value && value.name && String(value.name)) || ('url' in value && value.url && String(value.url)) || ('filename' in value && value.filename && String(value.filename)) || ('path' in value && value.path && String(value.path)))) || '') .split(/[\\/]/) .pop() || undefined); } export const isAsyncIterable = (value) => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; /** * 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, fetch) => { if (!hasUploadableValue(opts.body)) return opts; return { ...opts, body: await createForm(opts.body, fetch) }; }; export const multipartFormRequestOptions = async (opts, fetch) => { return { ...opts, body: await createForm(opts.body, fetch) }; }; const supportsFormDataMap = new WeakMap(); /** * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". * This function detects if the fetch function provided supports the global FormData object to avoid * confusing error messages later on. */ function supportsFormData(fetchObject) { const fetch = typeof fetchObject === 'function' ? fetchObject : fetchObject.fetch; const cached = supportsFormDataMap.get(fetch); if (cached) return cached; const promise = (async () => { try { const FetchResponse = ('Response' in fetch ? fetch.Response : (await fetch('data:,')).constructor); const data = new FormData(); if (data.toString() === (await new FetchResponse(data).text())) { return false; } return true; } catch { // avoid false negatives return true; } })(); supportsFormDataMap.set(fetch, promise); return promise; } export const createForm = async (body, fetch) => { if (!(await supportsFormData(fetch))) { throw new TypeError('The provided fetch function does not support file uploads with the current global FormData class.'); } const form = new FormData(); await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); return form; }; // We check for Blob not File because Bun.File doesn't inherit from File, // but they both inherit from Blob and have a `name` property at runtime. const isNamedBlob = (value) => value instanceof Blob && 'name' in value; const isUploadable = (value) => typeof value === 'object' && value !== null && (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); const hasUploadableValue = (value) => { if (isUploadable(value)) return true; if (Array.isArray(value)) return value.some(hasUploadableValue); if (value && typeof value === 'object') { for (const k in value) { if (hasUploadableValue(value[k])) return true; } } return false; }; const addFormValue = async (form, key, value) => { if (value === undefined) return; if (value == null) { throw new TypeError(`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`); } // TODO: make nested formats configurable if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { form.append(key, String(value)); } else if (value instanceof Response) { let options = {}; const contentType = value.headers.get('Content-Type'); if (contentType) { options = { type: contentType }; } form.append(key, makeFile([await value.blob()], getName(value), options)); } else if (isAsyncIterable(value)) { form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); } else if (isNamedBlob(value)) { form.append(key, makeFile([value], getName(value), { type: value.type })); } else if (Array.isArray(value)) { await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); } else if (typeof value === 'object') { await Promise.all(Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop))); } else { throw new TypeError(`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`); } }; //# sourceMappingURL=uploads.mjs.map