@ai-sdk/provider-utils
Version:
5,306 lines • 181 kB
JavaScript
import { AISDKError, APICallError, EmptyResponseBodyError, InvalidArgumentError, InvalidResponseDataError, JSONParseError, LoadAPIKeyError, LoadSettingError, NoSuchProviderReferenceError, TypeValidationError, UnsupportedFunctionalityError, getErrorMessage } from "@ai-sdk/provider";
import { safeParseAsync } from "zod/v4";
import { config, toJSONSchema } from "zod/v4/core";
import en from "zod/v4/locales/en.js";
import { EventSourceParserStream, EventSourceParserStream as EventSourceParserStream$1 } from "eventsource-parser/stream";
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
//#region src/as-array.ts
/**
* Normalizes a possibly undefined or non-array value into an array.
*/
function asArray(value) {
return value === void 0 ? [] : Array.isArray(value) ? value : [value];
}
//#endregion
//#region src/combine-headers.ts
function combineHeaders(...headers) {
return headers.reduce((combinedHeaders, currentHeaders) => ({
...combinedHeaders,
...currentHeaders
}), {});
}
//#endregion
//#region src/remove-undefined-entries.ts
/**
* Removes entries from a record where the value is null or undefined.
* @param record - The input object whose entries may be null or undefined.
* @returns A new object containing only entries with non-null and non-undefined values.
*/
function removeUndefinedEntries(record) {
return Object.fromEntries(Object.entries(record).filter(([_key, value]) => value != null));
}
//#endregion
//#region src/delay.ts
/**
* Creates a Promise that resolves after a specified delay
* @param delayInMs - The delay duration in milliseconds. If null or undefined, resolves immediately.
* @param signal - Optional AbortSignal to cancel the delay
* @returns A Promise that resolves after the specified delay
* @throws {DOMException} When the signal is aborted
*/
async function delay(delayInMs, options) {
if (delayInMs == null) return;
const signal = options?.abortSignal;
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(createAbortError());
return;
}
const timeoutId = setTimeout(() => {
cleanup();
resolve();
}, delayInMs);
const cleanup = () => {
clearTimeout(timeoutId);
signal?.removeEventListener("abort", onAbort);
};
const onAbort = () => {
cleanup();
reject(createAbortError());
};
signal?.addEventListener("abort", onAbort);
});
}
function createAbortError() {
return new DOMException("Delay was aborted", "AbortError");
}
//#endregion
//#region src/websocket.ts
function getWebSocketConstructor(webSocket) {
const WebSocketConstructor = webSocket ?? globalThis.WebSocket;
if (WebSocketConstructor == null) throw new Error("No WebSocket implementation available.");
return WebSocketConstructor;
}
/**
* Converts an http(s) URL to the corresponding ws(s) URL.
*/
function toWebSocketUrl(url) {
const wsUrl = new URL(url);
if (wsUrl.protocol === "http:") wsUrl.protocol = "ws:";
else if (wsUrl.protocol === "https:") wsUrl.protocol = "wss:";
return wsUrl;
}
const textDecoder$1 = new TextDecoder();
/**
* Reads WebSocket message data as text, handling string, binary,
* and Blob payloads.
*/
async function readWebSocketMessageText(data) {
if (typeof data === "string") return data;
if (data instanceof ArrayBuffer) return textDecoder$1.decode(data);
if (ArrayBuffer.isView(data)) return textDecoder$1.decode(data);
if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
return String(data);
}
const WEBSOCKET_OPEN_STATE = 1;
/**
* Waits until the socket's send buffer drains below `highWaterMark` bytes.
* No-op for implementations that do not expose `bufferedAmount`. There is no
* portable drain event, so this polls. Returns as soon as the socket is no
* longer open or the signal aborts — `bufferedAmount` never drains on a
* closed socket, so waiting on would poll forever.
*/
async function waitForWebSocketBufferDrain(socket, { highWaterMark = 1048576, pollIntervalMs = 20, abortSignal } = {}) {
while (socket.readyState === WEBSOCKET_OPEN_STATE && (socket.bufferedAmount ?? 0) > highWaterMark) {
if (abortSignal?.aborted === true) return;
await delay(pollIntervalMs);
}
}
//#endregion
//#region src/connect-to-websocket.ts
/**
* Opens a WebSocket for a provider model, owning the transport-generic layer
* (analogous to `postToApi` for HTTP): constructor resolution, header hygiene,
* abort wiring, and message decoding. Callers own the URL, the auth channel
* (subprotocols vs headers), and the wire protocol.
*/
function connectToWebSocket({ url, protocols, headers, webSocket, abortSignal, onOpen, onMessageText, onProcessingError, onSocketError, onClose, onAbort }) {
let socket;
let abortListener;
const close = (code) => {
if (abortListener != null) {
abortSignal?.removeEventListener("abort", abortListener);
abortListener = void 0;
}
try {
socket?.close(code);
} catch {}
};
if (abortSignal?.aborted) {
onAbort?.(abortSignal.reason ?? /* @__PURE__ */ new Error("Aborted"));
return {
socket: void 0,
close
};
}
try {
socket = new (getWebSocketConstructor(webSocket))(url, protocols, { headers: removeUndefinedEntries(headers ?? {}) });
} catch (error) {
onProcessingError(error);
return {
socket: void 0,
close
};
}
if (abortSignal != null && onAbort != null) {
abortListener = () => onAbort(abortSignal.reason ?? /* @__PURE__ */ new Error("Aborted"));
abortSignal.addEventListener("abort", abortListener, { once: true });
}
const openedSocket = socket;
socket.onopen = () => {
try {
onOpen?.(openedSocket);
} catch (error) {
onProcessingError(error);
}
};
let tail = Promise.resolve();
socket.onmessage = (event) => {
tail = tail.then(() => readWebSocketMessageText(event.data)).then((text) => onMessageText(text)).catch(onProcessingError);
};
socket.onerror = () => {
tail = tail.then(() => onSocketError?.()).catch(onProcessingError);
};
socket.onclose = (event) => {
const closeEvent = event;
const code = typeof closeEvent?.code === "number" ? closeEvent.code : void 0;
const reason = typeof closeEvent?.reason === "string" ? closeEvent.reason : void 0;
tail = tail.then(() => onClose?.({
code,
reason
})).catch(onProcessingError);
};
return {
socket,
close
};
}
//#endregion
//#region src/convert-async-iterator-to-readable-stream.ts
/**
* Converts an AsyncIterator to a ReadableStream.
*
* @template T - The type of elements produced by the AsyncIterator.
* @param { <T>} iterator - The AsyncIterator to convert.
* @returns {ReadableStream<T>} - A ReadableStream that provides the same data as the AsyncIterator.
*/
function convertAsyncIteratorToReadableStream(iterator) {
let cancelled = false;
return new ReadableStream({
/**
* Called when the consumer wants to pull more data from the stream.
*
* @param {ReadableStreamDefaultController<T>} controller - The controller to enqueue data into the stream.
* @returns {Promise<void>}
*/
async pull(controller) {
if (cancelled) return;
try {
const { value, done } = await iterator.next();
if (done) controller.close();
else controller.enqueue(value);
} catch (error) {
controller.error(error);
}
},
/**
* Called when the consumer cancels the stream.
*/
async cancel(reason) {
cancelled = true;
if (iterator.return) try {
await iterator.return(reason);
} catch {}
}
});
}
//#endregion
//#region src/uint8-utils.ts
const { btoa, atob } = globalThis;
function convertBase64ToUint8Array(base64String) {
const base64Url = base64String.replace(/-/g, "+").replace(/_/g, "/");
const latin1string = atob(base64Url);
const bytes = new Uint8Array(latin1string.length);
for (let i = 0; i < latin1string.length; i++) bytes[i] = latin1string.charCodeAt(i);
return bytes;
}
function convertUint8ArrayToBase64(array) {
if (typeof globalThis.Buffer?.from === "function") return globalThis.Buffer.from(array.buffer, array.byteOffset, array.byteLength).toString("base64");
const chunks = [];
const chunkSize = 4096;
for (let i = 0; i < array.length; i += chunkSize) chunks.push(String.fromCodePoint(...array.subarray(i, i + chunkSize)));
return btoa(chunks.join(""));
}
const typedArrayTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag).get;
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
function isUint8Array(value) {
return ArrayBuffer.isView(value) && typedArrayTag.call(value) === "Uint8Array";
}
function isArrayBuffer(value) {
if (typeof value !== "object" || value === null || !("byteLength" in value)) return false;
try {
arrayBufferByteLength.call(value);
return true;
} catch {
return false;
}
}
function convertToBase64(value) {
return isUint8Array(value) ? convertUint8ArrayToBase64(value) : value;
}
//#endregion
//#region src/convert-inline-file-data-to-uint8-array.ts
/**
* Converts inline file data (a tagged `data` or `text` shape) into raw bytes.
*
* - `{ type: 'text', text }` → UTF-8 encoded bytes
* - `{ type: 'data', data: Uint8Array | Buffer }` → returned as-is
* - `{ type: 'data', data: ArrayBuffer }` → wrapped in a `Uint8Array`
* - `{ type: 'data', data: string }` → decoded as base64
*
* `{ type: 'stream' }` data is rejected: providers without streaming upload
* support funnel here and surface a clear `UnsupportedFunctionalityError`.
*/
function convertInlineFileDataToUint8Array(data) {
if (data.type === "stream") {
const error = new UnsupportedFunctionalityError({ functionality: "streaming file upload" });
data.stream.cancel(error).catch(() => {});
throw error;
}
if (data.type === "text") return new TextEncoder().encode(data.text);
if (data.data instanceof Uint8Array) return data.data;
if (isUint8Array(data.data)) return new Uint8Array(data.data.buffer, data.data.byteOffset, data.data.byteLength);
if (isArrayBuffer(data.data)) return new Uint8Array(data.data);
return convertBase64ToUint8Array(data.data);
}
//#endregion
//#region src/convert-image-model-file-to-data-uri.ts
/**
* Convert an ImageModelV4File to a URL or data URI string.
*
* If the file is a URL, it returns the URL as-is.
* If the file is base64 data, it returns a data URI with the base64 data.
* If the file is a Uint8Array, it converts it to base64 and returns a data URI.
*/
function convertImageModelFileToDataUri(file) {
if (file.type === "url") return file.url;
return `data:${file.mediaType};base64,${typeof file.data === "string" ? file.data : convertUint8ArrayToBase64(file.data)}`;
}
//#endregion
//#region src/convert-json-response-tool-stream.ts
/**
* Providers that cannot use native structured output for a request can use a
* JSON response tool instead. This utility converts that tool's streamed input
* into text events so the SDK can parse it as structured output.
*
* Conversational text is suppressed. Application tool calls pass through so the
* caller can execute them. A response containing only the JSON tool finishes
* with `stop`; a response containing application calls keeps its finish reason.
*/
function convertJsonResponseToolStream(toolName) {
const responseToolIds = /* @__PURE__ */ new Set();
const responseToolsWithDeltas = /* @__PURE__ */ new Set();
let hasResponseTool = false;
let hasApplicationTool = false;
return new TransformStream({ transform(part, controller) {
switch (part.type) {
case "text-start":
case "text-delta":
case "text-end": return;
case "tool-input-start":
if (part.toolName === toolName) {
responseToolIds.add(part.id);
controller.enqueue({
type: "text-start",
id: part.id,
...part.providerMetadata && { providerMetadata: part.providerMetadata }
});
return;
}
break;
case "tool-input-delta":
if (responseToolIds.has(part.id)) {
if (part.delta.length > 0) responseToolsWithDeltas.add(part.id);
controller.enqueue({
type: "text-delta",
id: part.id,
delta: part.delta,
...part.providerMetadata && { providerMetadata: part.providerMetadata }
});
return;
}
break;
case "tool-input-end":
if (responseToolIds.has(part.id)) return;
break;
case "tool-call":
if (part.toolName === toolName) {
if (!responseToolsWithDeltas.has(part.toolCallId)) controller.enqueue({
type: "text-delta",
id: part.toolCallId,
delta: part.input,
...part.providerMetadata && { providerMetadata: part.providerMetadata }
});
controller.enqueue({
type: "text-end",
id: part.toolCallId,
...part.providerMetadata && { providerMetadata: part.providerMetadata }
});
hasResponseTool = true;
return;
}
if (!part.providerExecuted) hasApplicationTool = true;
break;
case "finish": if (hasResponseTool && !hasApplicationTool && part.finishReason.unified === "tool-calls") {
controller.enqueue({
...part,
finishReason: {
...part.finishReason,
unified: "stop"
}
});
return;
}
}
controller.enqueue(part);
} });
}
//#endregion
//#region src/convert-to-form-data.ts
/**
* Converts an input object to FormData for multipart/form-data requests.
*
* Handles the following cases:
* - `null` or `undefined` values are skipped
* - Arrays with a single element are appended as a single value
* - Arrays with multiple elements are appended with `[]` suffix (e.g., `image[]`)
* unless `useArrayBrackets` is set to `false`
* - All other values are appended directly
*
* @param input - The input object to convert. Use a generic type for type validation.
* @param options - Optional configuration object.
* @param options.useArrayBrackets - Whether to add `[]` suffix for multi-element arrays.
* Defaults to `true`. Set to `false` for APIs that expect repeated keys without brackets.
* @returns A FormData object containing the input values.
*
* @example
* ```ts
* type MyInput = {
* model: string;
* prompt: string;
* images: Blob[];
* };
*
* const formData = convertToFormData<MyInput>({
* model: 'gpt-image-1',
* prompt: 'A cat',
* images: [blob1, blob2],
* });
* ```
*/
function convertToFormData(input, options = {}) {
const { useArrayBrackets = true } = options;
const formData = new FormData();
for (const [key, value] of Object.entries(input)) {
if (value == null) continue;
if (Array.isArray(value)) {
if (value.length === 1) {
formData.append(key, value[0]);
continue;
}
const arrayKey = useArrayBrackets ? `${key}[]` : key;
for (const item of value) formData.append(arrayKey, item);
continue;
}
formData.append(key, value);
}
return formData;
}
//#endregion
//#region src/create-language-model-response-metadata.ts
/**
* Converts common provider response fields into language model response
* metadata.
*/
function createLanguageModelResponseMetadata({ id, model, created }) {
return {
id: id ?? void 0,
modelId: model ?? void 0,
timestamp: created != null ? /* @__PURE__ */ new Date(created * 1e3) : void 0
};
}
//#endregion
//#region src/create-null-language-model-usage.ts
/**
* Creates an empty language model usage result for unavailable usage data.
*/
function createNullLanguageModelUsage() {
return {
inputTokens: {
total: void 0,
noCache: void 0,
cacheRead: void 0,
cacheWrite: void 0
},
outputTokens: {
total: void 0,
text: void 0,
reasoning: void 0
},
raw: void 0
};
}
//#endregion
//#region src/create-tool-name-mapping.ts
/**
* @param tools - Tools that were passed to the language model.
* @param providerToolNames - Maps the provider tool ids to the provider tool names.
*/
function createToolNameMapping({ tools = [], providerToolNames }) {
const customToolNameToProviderToolName = {};
const providerToolNameToCustomToolName = {};
for (const tool of tools) if (tool.type === "provider" && tool.id in providerToolNames) {
const providerToolName = providerToolNames[tool.id];
customToolNameToProviderToolName[tool.name] = providerToolName;
providerToolNameToCustomToolName[providerToolName] = tool.name;
}
return {
toProviderToolName: (customToolName) => customToolNameToProviderToolName[customToolName] ?? customToolName,
toCustomToolName: (providerToolName) => providerToolNameToCustomToolName[providerToolName] ?? providerToolName
};
}
//#endregion
//#region src/create-provider-stream-error.ts
const marker$2 = Symbol.for("vercel.ai.providerStreamError");
/**
* Adds provider-owned status and retry metadata to a stream error payload
* without requiring provider packages to depend on AI SDK Core.
*/
function createProviderStreamError({ message, type, code, statusCode, isRetryable, data }) {
const error = {
message,
type,
code,
statusCode,
isRetryable,
data
};
Object.defineProperty(error, marker$2, { value: true });
return error;
}
function isProviderStreamError(error) {
return typeof error === "object" && error != null && error[marker$2] === true;
}
//#endregion
//#region src/delayed-promise.ts
/**
* Delayed promise. It is only constructed once the value is accessed.
* This is useful to avoid unhandled promise rejections when the promise is created
* but not accessed.
*/
var DelayedPromise = class {
constructor() {
this.status = { type: "pending" };
this._resolve = void 0;
this._reject = void 0;
}
get promise() {
if (this._promise) return this._promise;
this._promise = new Promise((resolve, reject) => {
if (this.status.type === "resolved") resolve(this.status.value);
else if (this.status.type === "rejected") reject(this.status.error);
this._resolve = resolve;
this._reject = reject;
});
return this._promise;
}
resolve(value) {
this.status = {
type: "resolved",
value
};
if (this._promise) this._resolve?.(value);
}
reject(error) {
this.status = {
type: "rejected",
error
};
if (this._promise) this._reject?.(error);
}
isResolved() {
return this.status.type === "resolved";
}
isRejected() {
return this.status.type === "rejected";
}
isPending() {
return this.status.type === "pending";
}
};
//#endregion
//#region src/extract-response-headers.ts
/**
* Extracts the headers from a response object and returns them as a key-value object.
*
* @param response - The response object to extract headers from.
* @returns The headers as a key-value object.
*/
function extractResponseHeaders(response) {
return Object.fromEntries([...response.headers]);
}
//#endregion
//#region src/get-runtime-environment-user-agent.ts
function getRuntimeEnvironmentUserAgent(globalThisAny = globalThis) {
if (globalThisAny.navigator?.userAgent) return globalThisAny.navigator.userAgent.toLowerCase();
if (globalThisAny.process?.versions?.node) return `node.js/${globalThisAny.process.version}`;
if (globalThisAny.EdgeRuntime) return "vercel-edge";
return "";
}
//#endregion
//#region src/is-abort-error.ts
function isAbortError(error) {
return (error instanceof Error || typeof DOMException === "function" && error instanceof DOMException) && (error.name === "AbortError" || error.name === "ResponseAborted" || error.name === "TimeoutError");
}
//#endregion
//#region src/handle-fetch-error.ts
const FETCH_FAILED_ERROR_MESSAGES = ["fetch failed", "failed to fetch"];
const RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
"ConnectionRefused",
"ConnectionClosed",
"FailedToOpenSocket",
"ECONNRESET",
"ECONNREFUSED",
"ETIMEDOUT",
"EPIPE",
"UND_ERR_SOCKET",
"UND_ERR_HEADERS_TIMEOUT",
"UND_ERR_BODY_TIMEOUT",
"UND_ERR_CONNECT_TIMEOUT"
]);
function findNetworkError(error) {
const visited = /* @__PURE__ */ new Set();
let current = error;
while (current instanceof Error && !visited.has(current)) {
visited.add(current);
const errorWithCode = current;
if (typeof errorWithCode.code === "string" && RETRYABLE_NETWORK_ERROR_CODES.has(errorWithCode.code)) return errorWithCode;
current = current.cause;
}
}
function handleFetchError({ error, url, requestBodyValues }) {
if (isAbortError(error)) return error;
if (error instanceof TypeError && FETCH_FAILED_ERROR_MESSAGES.includes(error.message.toLowerCase())) {
const cause = error.cause;
if (cause != null) return new APICallError({
message: `Cannot connect to API: ${cause.message}`,
cause,
url,
requestBodyValues,
isRetryable: true
});
}
const networkError = findNetworkError(error);
if (networkError != null) {
if (APICallError.isInstance(error)) return new APICallError({
message: error.message,
cause: error.cause,
url: error.url,
requestBodyValues: error.requestBodyValues,
statusCode: error.statusCode,
responseHeaders: error.responseHeaders,
responseBody: error.responseBody,
data: error.data,
isRetryable: true
});
return new APICallError({
message: `Cannot connect to API: ${error instanceof Error ? error.message : networkError.message}`,
cause: error,
url,
requestBodyValues,
isRetryable: true
});
}
return error;
}
//#endregion
//#region src/version.ts
const VERSION = "5.0.81";
//#endregion
//#region src/normalize-headers.ts
/**
* Normalizes different header inputs into a plain record with lower-case keys.
* Entries with `undefined` or `null` values are removed.
*
* @param headers - Input headers (`Headers`, tuples array, plain record) to normalize.
* @returns A record containing the normalized header entries.
*/
function normalizeHeaders(headers) {
if (headers == null) return {};
const normalized = {};
if (headers instanceof Headers) headers.forEach((value, key) => {
normalized[key.toLowerCase()] = value;
});
else {
if (!Array.isArray(headers)) headers = Object.entries(headers);
for (const [key, value] of headers) if (value != null) normalized[key.toLowerCase()] = value;
}
return normalized;
}
//#endregion
//#region src/with-user-agent-suffix.ts
/**
* Appends suffix parts to the `user-agent` header.
* If a `user-agent` header already exists, the suffix parts are appended to it.
* If no `user-agent` header exists, a new one is created with the suffix parts.
* Automatically removes undefined entries from the headers.
*
* @param headers - The original headers.
* @param userAgentSuffixParts - The parts to append to the `user-agent` header.
* @returns The new headers with the `user-agent` header set or updated.
*/
function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
const normalizedHeaders = new Headers(normalizeHeaders(headers));
const currentUserAgentHeader = normalizedHeaders.get("user-agent") || "";
normalizedHeaders.set("user-agent", [currentUserAgentHeader, ...userAgentSuffixParts].filter(Boolean).join(" "));
return Object.fromEntries(normalizedHeaders.entries());
}
//#endregion
//#region src/delete-from-api.ts
const getOriginalFetch$3 = () => globalThis.fetch;
/**
* Sends a DELETE request. For URLs built from developer-configured endpoints
* only — there is no untrusted-URL validation path (use `getFromApi` with
* `validateUrl` for response-supplied URLs).
*/
const deleteFromApi = async ({ url, headers = {}, failedResponseHandler, successfulResponseHandler, abortSignal, fetch = getOriginalFetch$3() }) => {
try {
const response = await fetch(url, {
method: "DELETE",
headers: withUserAgentSuffix(headers, `ai-sdk-provider-utils/${VERSION}`, getRuntimeEnvironmentUserAgent()),
signal: abortSignal
});
const responseHeaders = extractResponseHeaders(response);
if (!response.ok) {
let errorInformation;
try {
errorInformation = await failedResponseHandler({
response,
url,
requestBodyValues: {}
});
} catch (error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
throw new APICallError({
message: "Failed to process error response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: {}
});
}
throw errorInformation.value;
}
try {
return await successfulResponseHandler({
response,
url,
requestBodyValues: {}
});
} catch (error) {
if (error instanceof Error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
}
throw new APICallError({
message: "Failed to process successful response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: {}
});
}
} catch (error) {
throw handleFetchError({
error,
url,
requestBodyValues: {}
});
}
};
//#endregion
//#region src/detect-media-type.ts
const imageMediaTypeSignatures = [
{
mediaType: "image/gif",
bytesPrefix: [
71,
73,
70,
56,
55,
97
]
},
{
mediaType: "image/gif",
bytesPrefix: [
71,
73,
70,
56,
57,
97
]
},
{
mediaType: "image/png",
bytesPrefix: [
137,
80,
78,
71
]
},
{
mediaType: "image/jpeg",
bytesPrefix: [255, 216]
},
{
mediaType: "image/webp",
bytesPrefix: [
82,
73,
70,
70,
null,
null,
null,
null,
87,
69,
66,
80
]
},
{
mediaType: "image/bmp",
bytesPrefix: [
66,
77,
null,
null,
null,
null,
0,
0,
0,
0
]
},
{
mediaType: "image/tiff",
bytesPrefix: [
73,
73,
42,
0
]
},
{
mediaType: "image/tiff",
bytesPrefix: [
77,
77,
0,
42
]
},
{
mediaType: "image/avif",
bytesPrefix: [
0,
0,
0,
null,
102,
116,
121,
112,
97,
118,
105,
102
]
},
{
mediaType: "image/heic",
bytesPrefix: [
0,
0,
0,
null,
102,
116,
121,
112,
104,
101,
105,
99
]
}
];
const documentMediaTypeSignatures = [{
mediaType: "application/pdf",
bytesPrefix: [
37,
80,
68,
70
]
}];
const audioMediaTypeSignaturesWithoutMp4 = [
{
mediaType: "audio/aac",
bytesPrefix: [255, 240]
},
{
mediaType: "audio/aac",
bytesPrefix: [255, 241]
},
{
mediaType: "audio/aac",
bytesPrefix: [255, 248]
},
{
mediaType: "audio/aac",
bytesPrefix: [255, 249]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 251]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 250]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 243]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 242]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 227]
},
{
mediaType: "audio/mpeg",
bytesPrefix: [255, 226]
},
{
mediaType: "audio/wav",
bytesPrefix: [
82,
73,
70,
70,
null,
null,
null,
null,
87,
65,
86,
69
]
},
{
mediaType: "audio/ogg",
bytesPrefix: [
79,
103,
103,
83
]
},
{
mediaType: "audio/flac",
bytesPrefix: [
102,
76,
97,
67
]
},
{
mediaType: "audio/aac",
bytesPrefix: [
64,
21,
0,
0
]
},
{
mediaType: "audio/webm",
bytesPrefix: [
26,
69,
223,
163
]
}
];
const audioMediaTypeSignatures = [...audioMediaTypeSignaturesWithoutMp4, {
mediaType: "audio/mp4",
bytesPrefix: [
0,
0,
0,
null,
102,
116,
121,
112
]
}];
const videoMediaTypeSignatures = [
{
mediaType: "video/mp4",
bytesPrefix: [
0,
0,
0,
null,
102,
116,
121,
112
]
},
{
mediaType: "video/webm",
bytesPrefix: [
26,
69,
223,
163
]
},
{
mediaType: "video/quicktime",
bytesPrefix: [
0,
0,
0,
20,
102,
116,
121,
112,
113,
116
]
},
{
mediaType: "video/x-msvideo",
bytesPrefix: [
82,
73,
70,
70
]
}
];
const DEFAULT_SNIFF_BYTES = 18;
const ID3_SCAN_BYTES = 131084;
function decodePrefix(data, maxBytes) {
if (typeof data !== "string") return data.length > maxBytes ? data.subarray(0, maxBytes) : data;
const maxChars = Math.ceil(maxBytes / 3) * 4;
const bytes = convertBase64ToUint8Array(data.substring(0, Math.min(data.length, maxChars)));
return bytes.length > maxBytes ? bytes.subarray(0, maxBytes) : bytes;
}
function hasID3(bytes) {
return bytes.length > 10 && bytes[0] === 73 && bytes[1] === 68 && bytes[2] === 51;
}
const stripID3 = (bytes) => {
const id3Size = (bytes[6] & 127) << 21 | (bytes[7] & 127) << 14 | (bytes[8] & 127) << 7 | bytes[9] & 127;
return bytes.subarray(id3Size + 10);
};
function detectMediaTypeBySignatures({ data, signatures }) {
let bytes = decodePrefix(data, DEFAULT_SNIFF_BYTES);
if (hasID3(bytes)) bytes = stripID3(decodePrefix(data, ID3_SCAN_BYTES));
for (const signature of signatures) if (bytes.length >= signature.bytesPrefix.length && signature.bytesPrefix.every((byte, index) => byte === null || bytes[index] === byte)) return signature.mediaType;
}
const topLevelSignatureTables = {
image: imageMediaTypeSignatures,
audio: audioMediaTypeSignatures,
video: videoMediaTypeSignatures,
application: documentMediaTypeSignatures
};
/**
* Detect the IANA media type of a file from its raw bytes or base64 string.
*
* - When `topLevelType` is omitted, every known signature is considered
* (image, audio, video, and application). Returns `undefined` when the
* bytes do not match any known signature.
* - When `topLevelType` is provided, only signatures for that top-level
* segment are considered. Returns `undefined` for unsupported segments
* (e.g. `"text"`) or when no signature matches.
*/
function detectMediaType({ data, topLevelType }) {
if (topLevelType === void 0) return detectMediaTypeBySignatures({
data,
signatures: [
...imageMediaTypeSignatures,
...documentMediaTypeSignatures,
...audioMediaTypeSignaturesWithoutMp4,
...videoMediaTypeSignatures
]
});
if (!Object.prototype.hasOwnProperty.call(topLevelSignatureTables, topLevelType)) return;
return detectMediaTypeBySignatures({
data,
signatures: topLevelSignatureTables[topLevelType]
});
}
/**
* Returns the top-level segment of a media type (the portion before `/`).
*
* Examples:
* - `"image/png"` -> `"image"`
* - `"image/*"` -> `"image"`
* - `"image"` -> `"image"`
* - `"image/"` -> `"image"`
* - `""` -> `""`
* - `"/"` -> `""`
*/
function getTopLevelMediaType(mediaType) {
const slashIndex = mediaType.indexOf("/");
return slashIndex === -1 ? mediaType : mediaType.substring(0, slashIndex);
}
/**
* Returns `true` only when the given media type has a non-empty, non-wildcard
* subtype (i.e. matches the form `type/subtype`, and `subtype` is not `*`).
*
* Examples:
* - `"image/png"` -> `true`
* - `"image/*"` -> `false`
* - `"image"` -> `false`
* - `"image/"` -> `false`
* - `""` -> `false`
* - `"/"` -> `false`
*/
function isFullMediaType(mediaType) {
const slashIndex = mediaType.indexOf("/");
if (slashIndex === -1) return false;
const subtype = mediaType.substring(slashIndex + 1);
return subtype.length > 0 && subtype !== "*";
}
//#endregion
//#region src/cancel-response-body.ts
/**
* Cancels a response body to release the underlying connection.
*
* When a fetch Response is rejected without consuming its body (e.g. a failed
* status code, an open-redirect rejection, or a Content-Length that exceeds the
* size limit), the underlying TCP socket is not returned to the connection pool
* and may stay open until the process runs out of file descriptors. Cancelling
* the body avoids this leak.
*
* Errors thrown while cancelling are ignored: the body may already be locked,
* disturbed, or absent, none of which should mask the original rejection.
*/
async function cancelResponseBody(response) {
try {
await response.body?.cancel();
} catch {}
}
//#endregion
//#region src/download-error.ts
const name$1 = "AI_DownloadError";
const marker$1 = `vercel.ai.error.${name$1}`;
const symbol$1 = Symbol.for(marker$1);
var DownloadError = class extends AISDKError {
constructor({ url, statusCode, statusText, cause, message = cause == null ? `Failed to download ${url}: ${statusCode} ${statusText}` : `Failed to download ${url}: ${cause}` }) {
super({
name: name$1,
message,
cause
});
this[symbol$1] = true;
this.url = url;
this.statusCode = statusCode;
this.statusText = statusText;
}
static isInstance(error) {
return AISDKError.hasMarker(error, marker$1);
}
};
//#endregion
//#region src/is-browser-runtime.ts
/**
* Returns `true` when running in a browser.
*
* Detection keys on the presence of a global `window`, matching the browser
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
* so the SDK has a single, consistent definition of "browser". Server runtimes
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
*/
function isBrowserRuntime(globalThisAny = globalThis) {
return globalThisAny.window != null;
}
//#endregion
//#region src/is-same-origin.ts
/**
* Returns true when `url` has the same origin (scheme + host + port) as
* `baseUrl`.
*
* Used to decide whether provider credentials may be attached to a request to a
* URL taken from a provider response (e.g. a polling or media-download URL).
* Credentials must only be sent to the provider's own origin; a response that
* names a foreign host (a CDN, or an attacker-controlled host if the response
* is tampered with) must not receive the API key.
*
* Returns false if either value is not a valid absolute URL (fail-closed).
*/
function isSameOrigin(url, baseUrl) {
try {
return new URL(url).origin === new URL(baseUrl).origin;
} catch {
return false;
}
}
//#endregion
//#region src/sanitize-request-headers.ts
/**
* Request headers stripped before fetching an untrusted URL: host/virtual-host
* routing, proxy/origin spoofing, cloud-metadata, cookies, and hop-by-hop
* transport headers (RFC 7230 §6.1).
*
* `Authorization` and other credential-bearing caller headers (e.g. `x-key`)
* are intentionally not listed because trusted provider requests may need
* them. `fetchUntrustedUrl` separately restricts an untrusted first hop to an
* explicit allowlist of non-credential request metadata. Both it and
* `fetchWithValidatedRedirects` drop all caller headers except the user-agent
* on a cross-origin redirect.
*/
const BLOCKED_REQUEST_HEADERS = [
"connection",
"keep-alive",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"host",
"forwarded",
"proxy-authorization",
"via",
"x-forwarded-for",
"x-forwarded-host",
"x-forwarded-proto",
"x-real-ip",
"metadata",
"metadata-flavor",
"x-aws-ec2-metadata-token",
"x-metadata-token",
"cookie",
"set-cookie"
];
/**
* Returns a fresh `Headers` built from `input` with {@link BLOCKED_REQUEST_HEADERS}
* removed. The input is never mutated.
*/
function sanitizeRequestHeaders(input) {
const headers = new Headers(input);
for (const name of BLOCKED_REQUEST_HEADERS) headers.delete(name);
return headers;
}
//#endregion
//#region src/validate-download-url.ts
/**
* Validates that a URL is safe to download from, blocking private/internal addresses
* to prevent SSRF attacks.
*
* This function performs string/literal-IP checks only. DNS validation must
* be provided by the fetch transport or network egress controls.
*
* @param url - The URL string to validate.
* @throws DownloadError if the URL is unsafe.
*/
function validateDownloadUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new DownloadError({
url,
message: `Invalid URL: ${url}`
});
}
if (parsed.protocol === "data:") return;
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new DownloadError({
url,
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
});
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, "");
if (!hostname) throw new DownloadError({
url,
message: `URL must have a hostname`
});
if (hostname === "localhost" || hostname.endsWith(".local") || hostname.endsWith(".localhost") || hostname.endsWith(".internal")) throw new DownloadError({
url,
message: `URL with hostname ${hostname} is not allowed`
});
if (hostname.startsWith("[") && hostname.endsWith("]")) {
if (isPrivateIPv6(hostname.slice(1, -1))) throw new DownloadError({
url,
message: `URL with IPv6 address ${hostname} is not allowed`
});
return;
}
if (isIPv4(hostname)) {
if (isPrivateIPv4(hostname)) throw new DownloadError({
url,
message: `URL with IP address ${hostname} is not allowed`
});
}
}
function isIPv4(hostname) {
const parts = hostname.split(".");
if (parts.length !== 4) return false;
return parts.every((part) => {
const num = Number(part);
return Number.isInteger(num) && num >= 0 && num <= 255 && String(num) === part;
});
}
function isPrivateIPv4(ip) {
const [a, b, c] = ip.split(".").map(Number);
if (a === 0) return true;
if (a === 10) return true;
if (a === 100 && b >= 64 && b <= 127) return true;
if (a === 127) return true;
if (a === 169 && b === 254) return true;
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 192 && b === 0 && c === 0) return true;
if (a === 192 && b === 0 && c === 2) return true;
if (a === 192 && b === 168) return true;
if (a === 198 && (b === 18 || b === 19)) return true;
if (a === 198 && b === 51 && c === 100) return true;
if (a === 203 && b === 0 && c === 113) return true;
if (a >= 224) return true;
return false;
}
/**
* Expands an IPv6 address string into its 8 16-bit groups, handling `::`
* compression and an optional dotted-decimal IPv4 tail (e.g. `::ffff:127.0.0.1`).
*
* @returns the 8 groups, or null if the input is not a parseable IPv6 address.
*/
function parseIPv6(ip) {
let address = ip.toLowerCase();
const zoneIndex = address.indexOf("%");
if (zoneIndex !== -1) address = address.slice(0, zoneIndex);
const halves = address.split("::");
if (halves.length > 2) return null;
const toGroups = (segment) => {
if (segment === "") return [];
const groups = [];
const parts = segment.split(":");
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (part.includes(".")) {
if (i !== parts.length - 1 || !isIPv4(part)) return null;
const [a, b, c, d] = part.split(".").map(Number);
groups.push(a << 8 | b, c << 8 | d);
continue;
}
if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
groups.push(parseInt(part, 16));
}
return groups;
};
const head = toGroups(halves[0]);
if (head === null) return null;
if (halves.length === 2) {
const tail = toGroups(halves[1]);
if (tail === null) return null;
const fill = 8 - head.length - tail.length;
if (fill < 0) return null;
return [
...head,
...new Array(fill).fill(0),
...tail
];
}
return head.length === 8 ? head : null;
}
function isPrivateIPv6(ip) {
const groups = parseIPv6(ip);
if (groups === null) return true;
const topZero = (count) => groups.slice(0, count).every((group) => group === 0);
if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
if ((groups[0] & 65024) === 64512) return true;
if ((groups[0] & 65472) === 65152) return true;
if ((groups[0] & 65472) === 65216) return true;
if ((groups[0] & 65280) === 65280) return true;
if (groups[0] === 8193 && groups[1] === 3512) return true;
if (groups[0] === 16383 && (groups[1] & 61440) === 0) return true;
if (topZero(6) || topZero(5) && groups[5] === 65535 || topZero(4) && groups[4] === 65535 && groups[5] === 0 || groups[0] === 100 && groups[1] === 65435 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 0 || groups[0] === 100 && groups[1] === 65435 && groups[2] === 1) return isPrivateIPv4(`${groups[6] >> 8 & 255}.${groups[6] & 255}.${groups[7] >> 8 & 255}.${groups[7] & 255}`);
return false;
}
//#endregion
//#region src/fetch-with-validated-redirects.ts
const MAX_DOWNLOAD_REDIRECTS = 10;
const REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([
301,
302,
303,
307,
308
]);
/**
* Fetches one validated URL without following redirects.
*
* Uses the supplied fetch or the runtime's global fetch. URL validation does
* not resolve DNS; inject a transport such as guarded-fetch on Node.js
* when connection-time DNS validation is needed.
* Redirects are rejected by default. Callers using `redirect: 'manual'` must
* validate the Location target before issuing another request.
*/
async function fetchWithValidatedEndpoint({ url, init, fetch: customFetch, trustedOrigin, redirect = "error", requireDnsValidation = false }) {
const urlText = url.toString();
if (!(trustedOrigin !== void 0 && isSameOrigin(urlText, trustedOrigin))) {
validateDownloadUrl(urlText);
if (requireDnsValidation && (customFetch == null || customFetch === globalThis.fetch) && !(isBrowserRuntime() && typeof document !== "undefined")) throw new DownloadError({
url: urlText,
message: "This server runtime requires a custom fetch with connect-time DNS validation for untrusted URLs"
});
}
return await (customFetch ?? globalThis.fetch)(url, {
...init,
redirect
});
}
/**
* Fetches a URL while enforcing the download guard on every hop.
*
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
* on the default `redirect: 'follow'` would issue the request to a redirect
* target (e.g. an internal address) before we ever see its URL, defeating the
* guard.
*
* Manually followed redirects from HTTPS to HTTP are rejected before requesting
* the target.
*
* Request headers are also protected: {@link sanitizeRequestHeaders} strips
* proxy/metadata/cookie/hop-by-hop headers before the first request, and all
* caller headers except `User-Agent` are dropped on a cross-origin redirect.
* Credentials and custom headers are preserved on the first hop for backwards
* compatibility. The caller must ensure that the initial URL may receive them.
* Use `fetchUntrustedUrl` for URLs that require first-hop credential isolation.
* The fetch spec only strips `Authorization` on cross-origin redirects because
* in a browser, CORS preflighting protects custom headers; there is no CORS on
* the server, so provider API keys carried in custom headers (e.g. `x-key`)
* must be dropped here as well.
*
* A `redirect: 'manual'` request yields an unreadable opaque response in the
* browser (and in other spec-compliant fetch implementations), so the redirect
* target cannot be validated here. In a real browser this is safe to follow
* natively because reaching an internal network is not possible (fetch is
* constrained by CORS and cannot reach a server's internal network or
* cloud-metadata). On any other runtime we cannot validate the hop, so we fail
* closed rather than follow it blindly and bypass the guard.
*
* A hop that is same-origin with `trustedOrigin` (the developer-configured
* provider endpoint) skips target validation: that origin is exactly what an
* unvalidated, config-derived request would fetch anyway, and validating it
* would break legitimate self-hosted / localhost deployments whose response
* URLs point back at the configured host. Hops on any other origin are always
* validated.
*
* The returned response is the final (non-redirect) response. The caller is
* responsible for checking `response.ok` and reading the body.
*
* URL validation does not resolve DNS. Use guarded-fetch on Node.js
* for connection-time DNS validation, or enforce network egress restrictions.
*
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
* a redirect cannot be validated on a non-browser runtime.
*/
async function fetchWithValidatedRedirects({ url, headers, abortSignal, maxRedirects = MAX_DOWNLOAD_REDIRECTS, fetch: customFetch, trustedOrigin }) {
let currentHeaders = headers === void 0 ? void 0 : sanitizeRequestHeaders(headers);
const perHopInit = (redirect) => {
const init = {
signal: abortSignal,
redirect
};
if (currentHeaders !== void 0) init.headers = new Headers(currentHeaders);
return init;
};
let currentUrl = url;
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
if (!(trustedOrigin !== void 0 && isSameOrigin(currentUrl, trustedOrigin))) validateDownloadUrl(currentUrl);
const fetch = customFetch ?? globalThis.fetch;
const response = await fetch(currentUrl, perHopInit("manual"));
if (response.type === "opaqueredirect") {
if (!isBrowserRuntime()) throw new DownloadError({
url,
message: `Redirect from ${currentUrl} could not be validated and was blocked`
});
return await fetch(currentUrl, perHopInit("follow"));
}
const location = response.headers?.get("location");
if (REDIRECT_STATUS_CODES.has(response.status) && location) {
cancelResponseBody(response);
const nextUrl = new URL(location, currentUrl).toString();
if (new URL(currentUrl).protocol === "https:" && new URL(nextUrl).protocol === "http:") throw new DownloadError({
url,
message: "Redirect from HTTPS to HTTP was blocked"
});
if (currentHeaders !== void 0 && !isSameOrigin(nextUrl, currentUrl)) {
const userAgent = currentHeaders.get("user-agent");
currentHeaders = new Headers(userAgent == null ? void 0 : { "user-agent": userAgent });
}
currentUrl = nextUrl;
continue;
}
return response;
}
throw new DownloadError({
url,
message: `Too many redirects (max ${maxRedirects})`
});
}
//#endregion
//#region src/fetch-untrusted-url.ts
const SAFE_UNTRUSTED_FIRST_HOP_HEADERS = /* @__PURE__ */ new Set([
"accept",
"accept-language",
"baggage",
"cache-control",
"idempotency-key",
"if-match",
"if-modified-since",
"if-none-match",
"if-range",
"if-unmodified-since",
"pragma",
"range",
"traceparent",
"tracestate",
"user-agent",
"x-correlation-id",
"x-request-id"
]);
/**
* Fetches an untrusted URL with first-hop credential isolation and validated
* redirects. Uses the URL validation, runtime fetch transport, redirect
* limits, and cross-origin header stripping of {@link fetchWithValidatedRedirects}.
* Inject guarded-fetch on Node.js for connect-time DNS validation.
*
* Without a matching `credentialedOrigin` (or `trustedOrigin` when it is
* omitted), only allowlisted request metadata is sent on the first hop.
* Arbitrary caller headers require an explicit matching origin because vendor
* credential names cannot be inferred safely. Proxy, metadata, cookie, and
* hop-by-hop headers are sanitized even for a matching origin.
*
* `trustedOrigin` also exempts same-origin hops from URL validation, allowing
* developer-configured private endpoints. Both origin options must come from
* developer configuration, never from untrusted response data.
*
* This is an opt-in alternative to `fetchWithValidatedRedirects`, whose
* existing first-hop header behavior is preserved for compatibility.
*/
async function fetchUntrustedUrl({ headers, credentialedOrigin, untrustedFirstHopHeaders, ...options }) {
let firstHopHeaders;
if (headers !== void 0) {
firstHopHeaders = sanitizeRequestHeaders(headers);
const origin = credentialedOrigin ?? options.trustedOrigin;
if (origin === void 0 || !isSameOrigin(options.url, origin)) {
const allowedHeaders = /* @__PURE__ */ new Set([...SAFE_UNTRUSTED_FIRST_HOP_HEADERS, ...(untrustedFirstHopHeaders ?? []).map((name) => name.toLowerCase())]);
firstHopHeaders = new Headers([...firstHopHeaders].filter(([name]) => allowedHeaders.has(name)));
}
}
return fetchWithValidatedRedirects({
...options,
headers: firstHopHeaders
});
}
//#endregion
//#region src/read-response-with-size-limit.ts
/**
* Default maximum download size: 2 GiB.
*
* `fetch().arrayBuffer()` has ~2x peak memory overhead (undici buffers the
* body internally, then creates the JS ArrayBuffer), so very large downloads
* risk exceeding the default V8 heap limit on 64-bit systems and terminating
* the process with an out-of-memory error.
*
* Setting this limit converts an unrecoverable OOM crash into a catchable
* `DownloadError`.
*/
const DEFAULT_MAX_DOWNLOAD_SIZE = 2147483648;
/**
* Reads a fetch Response body with a size limit to prevent memory exhaustion.
*
* Checks the Content-Length header for early rejection, then reads the body
* incrementally via ReadableStream and aborts with a DownloadError when the
* limit is exceeded.
*
* @param response - The fetch Response to read.
* @param url - The URL being downloaded (used in error messages).
* @param maxBytes - Maximum allowed bytes. Defaults to DEFAULT_MAX_DOWNLOAD_SIZE.
* @param onChunk - Optional. Receives each chunk instead of the chunks being
* collected, so the body is not held in memory, and the returned array is empty.
* The chunk may be a view on a larger buffer, copy what you keep.
* @returns A Uint8Array containing the response body.
* @throws DownloadError if the response exceeds maxBytes.
*/
async function readResponseWithSizeLimit({ response, url, maxBytes = DEFAULT_MAX_DOWNLOAD_SIZE, onChunk }) {
const contentLength = response.headers.get("content-length");
if (contentLength != null) {
const length = parseInt(contentLength, 10);
if (!isNaN(length) && length > maxBytes) {
await cancelResponseBody(response);
throw new DownloadError({
url,
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`
});
}
}
const body = response.body;
if (body == null) return /* @__PURE__ */ new Uint8Array(0);
const reader = body.getReader();
const chunks = [];
let totalBytes = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
if (totalBytes > maxBytes) throw new DownloadError({
url,
message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes.`
});
if (onChunk != null) {
onChunk(value);
continue;
}
chunks.push(value.byteLength < value.buffer.byteLength ? new Uint8Array(value) : value);
}
} finally {
try {
await reader.cancel();
} catch {} finally {
reader.releaseLock();
}
}
const result = new Uint8Array(onChunk != null ? 0 : totalBytes);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
//#endregion
//#region src/download-blob.ts
/**
* Download a file from a URL and return it as a Blob.
*
* @param url - The URL to download from.
* @param options - Optional settings for the download.
* @param options.maxBytes - Maximum allowed download size in bytes. Defaults to 100 MiB.
* @param options.abortSignal - An optional abort signal to cancel the download.
* @param options.fetch - Fetch transport, e.g. guarded-fetch for DNS validation on Node.js.
* @returns A Promise that resolves to the downloaded Blob.
*
* @throws DownloadError if the download fails or exceeds maxBytes.
*/
async function downloadBlob(url, options) {
try {
const response = await fetchUntrustedUrl({
url,
abortSignal: options?.abortSignal,
fetch: options?.fetch
});
if (!response.ok) {
await cancelResponseBody(response);
throw new DownloadError({
url,
statusCode: response.status,
statusText: response.statusText
});
}
const data = await readResponseWithSizeLimit({
response,
url,
maxBytes: options?.maxBytes ?? 2147483648
});
const contentType = response.headers.get("content-type") ?? void 0;
return new Blob([data], contentType ? { type: contentType } : void 0);
} catch (error) {
if (DownloadError.isInstance(error)) throw error;
throw new DownloadError({
url,
cause: error
});
}
}
//#endregion
//#region src/embedding-model-capabilities.ts
/**
* Symbol for exposing the UTF-8 input byte budget of an embedding model.
*
* This capability is experimental and intentionally lives outside the versioned
* embedding model specification.
*/
const EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL = Symbol.for("vercel.ai.embeddingModel.maxInputBytesPerCall");
/**
* Symbol for transforming provider options for an automatically batched
* embedding model call.
*
* This capability is experimental and intentionally lives outside the versioned
* embedding model specification.
*/
const EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER = Symbol.for("vercel.ai.embeddingModel.providerOptionsTransformer");
//#endregion
//#region src/encode-path-segment.ts
/**
* Encodes a raw identifier as one URL path segment, including standalone dot
* segments that URL parsers would otherwise normalize. Pass unencoded values;
* for resource paths with intentional slashes, encode each segment separately.
*/
function encodePathSegment(value) {
const encodedValue = encodeURIComponent(value);
return encodedValue === "." ? "%252E" : encodedValue === ".." ? "%252E%252E" : encodedValue;
}
//#endregion
//#region src/extract-lines.ts
/**
* Extracts a 1-based inclusive line range from `text`, auto-detecting the
* file's line ending (`\r\n`, `\n`, or `\r`, in that priority).
*
* Mixed line endings are not supported: detection picks one and uses it for
* both the split and the rejoin, so files that mix conventions will not slice
* cleanly. When neither `startLine` nor `endLine` is provided, the input is
* returned unchanged. `endLine` past EOF clamps to the last line.
*/
function extractLines({ text, startLine, endLine }) {
if (startLine == null && endLine == null) return text;
const lineEnding = text.includes("\r\n") ? "\r\n" : text.includes("\n") ? "\n" : text.includes("\r") ? "\r" : "\n";
const lines = text.split(lineEnding);
const start = Math.max(1, startLine ?? 1) - 1;
const end = Math.min(lines.length, endLine ?? lines.length);
return lines.slice(start, end).join(lineEnding);
}
//#endregion
//#region src/filter-nullable.ts
/**
* Filters `null` and `undefined` values out of a list of values.
*
* @param values - The values to filter.
* @returns A new array containing only non-nullish values.
*/
function filterNullable(...values) {
return values.filter((value) => value != null);
}
//#endregion
//#region src/generate-id.ts
/**
* Creates an ID generator.
* The total length of the ID is the sum of the prefix, separator, and random part length.
* Not cryptographically secure.
*
* @param alphabet - The alphabet to use for the ID. Default: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
* @param prefix - The prefix of the ID to generate. Optional.
* @param separator - The separator between the prefix and the random part of the ID. Default: '-'.
* @param size - The size of the random part of the ID to generate. Default: 16.
*/
const createIdGenerator = ({ prefix, size = 16, alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", separator = "-" } = {}) => {
const generator = () => {
const alphabetLength = alphabet.length;
const chars = new Array(size);
for (let i = 0; i < size; i++) chars[i] = alphabet[Math.random() * alphabetLength | 0];
return chars.join("");
};
if (prefix == null) return generator;
if (alphabet.includes(separator)) throw new InvalidArgumentError({
argument: "separator",
message: `The separator "${separator}" must not be part of the alphabet "${alphabet}".`
});
return () => `${prefix}${separator}${generator()}`;
};
/**
* Generates a 16-character random string to use for IDs.
* Not cryptographically secure.
*/
const generateId = createIdGenerator();
//#endregion
//#region src/get-from-api.ts
const getOriginalFetch$2 = () => globalThis.fetch;
const getFromApi = async ({ url, headers = {}, successfulResponseHandler, failedResponseHandler, abortSignal, fetch, validateUrl, credentialedOrigin, trustedOrigin }) => {
try {
const requestFetch = fetch ?? getOriginalFetch$2();
const requestHeaders = withUserAgentSuffix(credentialedOrigin !== void 0 && !isSameOrigin(url, credentialedOrigin) ? {} : headers, `ai-sdk-provider-utils/${VERSION}`, getRuntimeEnvironmentUserAgent());
const response = validateUrl ? await fetchWithValidatedRedirects({
url,
headers: requestHeaders,
abortSignal,
fetch,
trustedOrigin
}) : await requestFetch(url, {
method: "GET",
headers: requestHeaders,
signal: abortSignal
});
const responseHeaders = extractResponseHeaders(response);
if (!response.ok) {
let errorInformation;
try {
errorInformation = await failedResponseHandler({
response,
url,
requestBodyValues: {}
});
} catch (error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
throw new APICallError({
message: "Failed to process error response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: {}
});
}
throw errorInformation.value;
}
try {
return await successfulResponseHandler({
response,
url,
requestBodyValues: {}
});
} catch (error) {
if (error instanceof Error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
}
throw new APICallError({
message: "Failed to process successful response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: {}
});
}
} catch (error) {
throw handleFetchError({
error,
url,
requestBodyValues: {}
});
}
};
//#endregion
//#region src/inject-json-instruction.ts
const DEFAULT_SCHEMA_PREFIX = "JSON schema:";
const DEFAULT_SCHEMA_SUFFIX = "You MUST answer with a JSON object that matches the JSON schema above.";
const DEFAULT_GENERIC_SUFFIX = "You MUST answer with JSON.";
function injectJsonInstruction({ prompt, schema, schemaPrefix = schema != null ? DEFAULT_SCHEMA_PREFIX : void 0, schemaSuffix = schema != null ? DEFAULT_SCHEMA_SUFFIX : DEFAULT_GENERIC_SUFFIX }) {
return [
prompt != null && prompt.length > 0 ? prompt : void 0,
prompt != null && prompt.length > 0 ? "" : void 0,
schemaPrefix,
schema != null ? JSON.stringify(schema) : void 0,
schemaSuffix
].filter((line) => line != null).join("\n");
}
function injectJsonInstructionIntoMessages({ messages, schema, schemaPrefix, schemaSuffix }) {
const systemMessage = messages[0]?.role === "system" ? { ...messages[0] } : {
role: "system",
content: ""
};
systemMessage.content = injectJsonInstruction({
prompt: systemMessage.content,
schema,
schemaPrefix,
schemaSuffix
});
return [systemMessage, ...messages[0]?.role === "system" ? messages.slice(1) : messages];
}
//#endregion
//#region src/is-buffer.ts
/**
* Type-guard for Node.js `Buffer` instances.
*
* Uses optional chaining on `globalThis.Buffer` so it returns `false` in
* runtimes where `Buffer` is not available (e.g. CloudFlare Workers).
*/
function isBuffer(value) {
return globalThis.Buffer?.isBuffer(value) ?? false;
}
//#endregion
//#region src/is-non-nullable.ts
/**
* Type guard that checks whether a value is not `null` or `undefined`.
*
* @template T - The type of the value to check.
* @param value - The value to check.
* @returns `true` if the value is neither `null` nor `undefined`, otherwise `false`.
*/
function isNonNullable(value) {
return value != null;
}
//#endregion
//#region src/is-provider-reference.ts
/**
* Checks whether a value is a provider reference (a mapping of provider names
* to provider-specific identifiers) as opposed to raw bytes, a URL, or a
* tagged `{ type: ... }` object.
*/
function isProviderReference(data) {
return typeof data === "object" && data !== null && !isUint8Array(data) && !(data instanceof URL) && !isArrayBuffer(data) && !isBuffer(data) && !("type" in data);
}
//#endregion
//#region src/is-record.ts
/**
* Checks whether a value is a non-null, non-array object.
*/
function isRecord(value) {
return value != null && typeof value === "object" && !Array.isArray(value);
}
//#endregion
//#region src/is-url-supported.ts
/**
* Checks if the given URL is supported natively by the model.
*
* @param mediaType - The media type of the URL. Case-insensitive. May be a full
* `type/subtype`, a wildcard `type/*`, or just the
* top-level segment (e.g. `image`).
* @param url - The URL to check.
* @param supportedUrls - A record where keys are case-insensitive media types (or '*')
* and values are arrays of RegExp patterns for URLs.
*
* @returns `true` if the URL matches a pattern under the specific media type
* or the wildcard '*', `false` otherwise.
*/
function isUrlSupported({ mediaType, url, supportedUrls }) {
mediaType = mediaType.toLowerCase();
const isTopLevelOnly = !mediaType.includes("/");
return Object.entries(supportedUrls).map(([key, value]) => {
const mediaType = key.toLowerCase();
return mediaType === "*" || mediaType === "*/*" ? {
mediaTypePrefix: "",
regexes: value
} : {
mediaTypePrefix: mediaType.replace(/\*/, ""),
regexes: value
};
}).filter(({ mediaTypePrefix }) => {
if (mediaTypePrefix === "") return true;
if (isTopLevelOnly) return `${mediaType}/` === mediaTypePrefix;
return mediaTypePrefix.endsWith("/") ? mediaType.startsWith(mediaTypePrefix) : mediaType === mediaTypePrefix;
}).flatMap(({ regexes }) => regexes).some((pattern) => testRegExpFromStart(pattern, url));
}
function testRegExpFromStart(pattern, value) {
if (!pattern.global && !pattern.sticky) return pattern.test(value);
const lastIndex = pattern.lastIndex;
pattern.lastIndex = 0;
try {
return pattern.test(value);
} finally {
pattern.lastIndex = lastIndex;
}
}
//#endregion
//#region src/is-valid-hostname-part.ts
/**
* Checks whether a value is a valid ASCII hostname part: 1–63 letters, digits,
* or hyphens, without a leading or trailing hyphen.
*
* Use this before inserting a resource name, region, or location into a
* generated hostname. Rejects values such as `evil.example.com/#` and
* `user@localhost:8080/#` that could change the request destination.
*/
function isValidHostnamePart(value) {
return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.exec(value)?.[0] === value;
}
//#endregion
//#region src/load-api-key.ts
function loadApiKey({ apiKey, environmentVariableName, apiKeyParameterName = "apiKey", description }) {
if (typeof apiKey === "string") return apiKey;
if (apiKey != null) throw new LoadAPIKeyError({ message: `${description} API key must be a string.` });
if (typeof process === "undefined") throw new LoadAPIKeyError({ message: `${description} API key is missing. Pass it using the '${apiKeyParameterName}' parameter. Environment variables are not supported in this environment.` });
apiKey = process.env[environmentVariableName];
if (apiKey == null) throw new LoadAPIKeyError({ message: `${description} API key is missing. Pass it using the '${apiKeyParameterName}' parameter or the ${environmentVariableName} environment variable.` });
if (typeof apiKey !== "string") throw new LoadAPIKeyError({ message: `${description} API key must be a string. The value of the ${environmentVariableName} environment variable is not a string.` });
return apiKey;
}
//#endregion
//#region src/load-optional-setting.ts
/**
* Loads an optional `string` setting from the environment or a parameter.
*
* @param settingValue - The setting value.
* @param environmentVariableName - The environment variable name.
* @returns The setting value.
*/
function loadOptionalSetting({ settingValue, environmentVariableName }) {
if (typeof settingValue === "string") return settingValue;
if (settingValue != null || typeof process === "undefined") return;
settingValue = process.env[environmentVariableName];
if (settingValue == null || typeof settingValue !== "string") return;
return settingValue;
}
//#endregion
//#region src/load-setting.ts
/**
* Loads a `string` setting from the environment or a parameter.
*
* @param settingValue - The setting value.
* @param environmentVariableName - The environment variable name.
* @param settingName - The setting name.
* @param description - The description of the setting.
* @returns The setting value.
*/
function loadSetting({ settingValue, environmentVariableName, settingName, description }) {
if (typeof settingValue === "string") return settingValue;
if (settingValue != null) throw new LoadSettingError({ message: `${description} setting must be a string.` });
if (typeof process === "undefined") throw new LoadSettingError({ message: `${description} setting is missing. Pass it using the '${settingName}' parameter. Environment variables are not supported in this environment.` });
settingValue = process.env[environmentVariableName];
if (settingValue == null) throw new LoadSettingError({ message: `${description} setting is missing. Pass it using the '${settingName}' parameter or the ${environmentVariableName} environment variable.` });
if (typeof settingValue !== "string") throw new LoadSettingError({ message: `${description} setting must be a string. The value of the ${environmentVariableName} environment variable is not a string.` });
return settingValue;
}
//#endregion
//#region src/map-reasoning-to-provider.ts
/**
* Reasoning levels in ascending order.
*/
const REASONING_LEVELS = [
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
];
function isCustomReasoning(reasoning) {
return reasoning !== void 0 && reasoning !== "provider-default";
}
/**
* Maps a top-level reasoning value to a provider-specific effort string.
*
* A `providerOptionsEffort` (the reasoning effort set in provider options) is
* returned unchanged. Otherwise, `undefined` and `'provider-default'` return
* `undefined`, and other reasoning values are mapped with the effort map.
*
* The effort map should only contain the values that the model supports. An
* unsupported value maps to the next lower supported level, or the next higher
* one if there is no lower level. `'none'` is never used as a fallback, so an
* unsupported `'none'` maps to the lowest supported level.
*
* Pushes a compatibility warning if the effort that is sent differs from the
* requested value, or an unsupported warning if the effort map is empty.
*
* @returns The provider options effort, the mapped effort string, or
* `undefined` if no effort should be sent.
*/
function mapReasoningToProviderEffort({ reasoning, effortMap, providerOptionsEffort, warnings }) {
if (providerOptionsEffort != null) return providerOptionsEffort;
if (!isCustomReasoning(reasoning)) return;
const index = REASONING_LEVELS.indexOf(reasoning);
const level = [
reasoning,
...REASONING_LEVELS.slice(0, Math.max(index, 0)).reverse(),
...REASONING_LEVELS.slice(index + 1)
].find((candidate) => effortMap[candidate] != null);
if (level == null) {
warnings.push({
type: "unsupported",
feature: "reasoning",
details: `reasoning "${reasoning}" is not supported by this model.`
});
return;
}
const mapped = effortMap[level];
if (mapped !== reasoning) warnings.push({
type: "compatibility",
feature: "reasoning",
details: `reasoning "${reasoning}" is not directly supported by this model. mapped to effort "${mapped}".`
});
return mapped;
}
const DEFAULT_REASONING_BUDGET_PERCENTAGES = {
minimal: .02,
low: .1,
medium: .3,
high: .6,
xhigh: .9,
max: .95
};
/**
* Maps a top-level reasoning value to an absolute token budget by multiplying
* the model's max output tokens by a percentage for the level. Levels missing
* from `budgetPercentages` use the default percentages. The result is clamped
* between `minReasoningBudget` (default 1024) and `maxReasoningBudget`.
*
* Handle `'none'` before calling this function when the model can disable
* reasoning. Otherwise, `'none'` maps to `minReasoningBudget` with a
* compatibility warning.
*
* @returns The computed token budget.
*/
function mapReasoningToProviderBudget({ reasoning, maxOutputTokens, maxReasoningBudget, minReasoningBudget = 1024, budgetPercentages, warnings }) {
if (reasoning === "none") {
warnings.push({
type: "compatibility",
feature: "reasoning",
details: `reasoning "none" is not supported by this model. mapped to the minimum reasoning budget of ${minReasoningBudget} tokens.`
});
return minReasoningBudget;
}
const percentage = budgetPercentages?.[reasoning] ?? DEFAULT_REASONING_BUDGET_PERCENTAGES[reasoning];
return Math.min(maxReasoningBudget, Math.max(minReasoningBudget, Math.round(maxOutputTokens * percentage)));
}
//#endregion
//#region src/media-type-to-extension.ts
/**
* Maps a media type to its corresponding file extension.
* It was originally introduced to set a filename for audio file uploads
* in https://github.com/vercel/ai/pull/8159.
*
* @param mediaType The media type to map.
* @returns The corresponding file extension
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
*/
function mediaTypeToExtension(mediaType) {
const [_type, subtype = ""] = mediaType.toLowerCase().split("/");
return {
mpeg: "mp3",
"x-wav": "wav",
opus: "ogg",
mp4: "m4a",
"x-m4a": "m4a"
}[subtype] ?? subtype;
}
//#endregion
//#region src/normalize-batch-request-counts.ts
/**
* Normalizes complete batch request counts.
*
* Returns `undefined` when any count is missing, is not a non-negative safe
* integer, or when the item counts do not add up to the total.
*/
function normalizeBatchRequestCounts({ total, pending, completed, failed }) {
if (isNonNegativeSafeInteger(total) && isNonNegativeSafeInteger(pending) && isNonNegativeSafeInteger(completed) && isNonNegativeSafeInteger(failed) && pending + completed + failed === total) return {
total,
pending,
completed,
failed
};
}
function isNonNegativeSafeInteger(value) {
return value != null && Number.isSafeInteger(value) && value >= 0;
}
//#endregion
//#region src/secure-json-parse.ts
const suspectProtoRx = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/;
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
function _parse(text) {
const obj = JSON.parse(text);
if (obj === null || typeof obj !== "object") return obj;
if (suspectProtoRx.test(text) === false && suspectConstructorRx.test(text) === false) return obj;
return filter(obj);
}
function filter(obj) {
let next = [obj];
while (next.length) {
const nodes = next;
next = [];
for (const node of nodes) {
if (Object.prototype.hasOwnProperty.call(node, "__proto__")) throw new SyntaxError("Object contains forbidden prototype property");
if (Object.prototype.hasOwnProperty.call(node, "constructor") && node.constructor !== null && typeof node.constructor === "object" && Object.prototype.hasOwnProperty.call(node.constructor, "prototype")) throw new SyntaxError("Object contains forbidden prototype property");
for (const key in node) {
const value = node[key];
if (value && typeof value === "object") next.push(value);
}
}
}
return obj;
}
function secureJsonParse(text) {
const { stackTraceLimit } = Error;
try {
Error.stackTraceLimit = 0;
} catch {
return _parse(text);
}
try {
return _parse(text);
} finally {
Error.stackTraceLimit = stackTraceLimit;
}
}
//#endregion
//#region src/add-additional-properties-to-json-schema.ts
/**
* Recursively adds additionalProperties: false to object schemas that do not
* define a schema for their additional properties. This is necessary because
* some providers (e.g. OpenAI) do not support additionalProperties: true.
*/
function addAdditionalPropertiesToJsonSchema(jsonSchema) {
if (jsonSchema.type === "object" || Array.isArray(jsonSchema.type) && jsonSchema.type.includes("object")) {
const { additionalProperties } = jsonSchema;
jsonSchema.additionalProperties = additionalProperties != null && typeof additionalProperties !== "boolean" ? visit(additionalProperties) : false;
const { properties } = jsonSchema;
if (properties != null) for (const key of Object.keys(properties)) properties[key] = visit(properties[key]);
}
if (jsonSchema.items != null) jsonSchema.items = Array.isArray(jsonSchema.items) ? jsonSchema.items.map(visit) : visit(jsonSchema.items);
if (jsonSchema.anyOf != null) jsonSchema.anyOf = jsonSchema.anyOf.map(visit);
if (jsonSchema.allOf != null) jsonSchema.allOf = jsonSchema.allOf.map(visit);
if (jsonSchema.oneOf != null) jsonSchema.oneOf = jsonSchema.oneOf.map(visit);
const { definitions } = jsonSchema;
if (definitions != null) for (const key of Object.keys(definitions)) definitions[key] = visit(definitions[key]);
return jsonSchema;
}
function visit(def) {
if (typeof def === "boolean") return def;
return addAdditionalPropertiesToJsonSchema(def);
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/options.ts
const ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
const defaultOptions = {
name: void 0,
$refStrategy: "root",
basePath: ["#"],
effectStrategy: "input",
pipeStrategy: "all",
dateStrategy: "format:date-time",
mapStrategy: "entries",
removeAdditionalStrategy: "passthrough",
allowedAdditionalProperties: true,
rejectedAdditionalProperties: false,
definitionPath: "definitions",
strictUnions: false,
definitions: {},
errorMessages: false,
patternStrategy: "escape",
applyRegexFlags: false,
emailStrategy: "format:email",
base64Strategy: "contentEncoding:base64",
nameStrategy: "ref"
};
const getDefaultOptions = (options) => typeof options === "string" ? {
...defaultOptions,
name: options
} : {
...defaultOptions,
...options
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/any.ts
function parseAnyDef() {
return {};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/array.ts
function parseArrayDef(def, refs) {
const res = { type: "array" };
if (def.type?._def && def.type?._def?.typeName !== "ZodAny") res.items = parseDef(def.type._def, {
...refs,
currentPath: [...refs.currentPath, "items"]
});
if (def.minLength) res.minItems = def.minLength.value;
if (def.maxLength) res.maxItems = def.maxLength.value;
if (def.exactLength) {
res.minItems = def.exactLength.value;
res.maxItems = def.exactLength.value;
}
return res;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts
function parseBigintDef(def) {
const res = {
type: "integer",
format: "int64"
};
if (!def.checks) return res;
for (const check of def.checks) switch (check.kind) {
case "min":
if (check.inclusive) res.minimum = check.value;
else res.exclusiveMinimum = check.value;
break;
case "max":
if (check.inclusive) res.maximum = check.value;
else res.exclusiveMaximum = check.value;
break;
case "multipleOf": res.multipleOf = check.value;
}
return res;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/boolean.ts
function parseBooleanDef() {
return { type: "boolean" };
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/branded.ts
function parseBrandedDef(_def, refs) {
return parseDef(_def.type._def, refs);
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/catch.ts
const parseCatchDef = (def, refs) => {
return parseDef(def.innerType._def, refs);
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/date.ts
function parseDateDef(def, refs, overrideDateStrategy) {
const strategy = overrideDateStrategy ?? refs.dateStrategy;
if (Array.isArray(strategy)) return { anyOf: strategy.map((item) => parseDateDef(def, refs, item)) };
switch (strategy) {
case "string":
case "format:date-time": return {
type: "string",
format: "date-time"
};
case "format:date": return {
type: "string",
format: "date"
};
case "integer": return integerDateParser(def);
}
}
const integerDateParser = (def) => {
const res = {
type: "integer",
format: "unix-time"
};
for (const check of def.checks) switch (check.kind) {
case "min":
res.minimum = check.value;
break;
case "max": res.maximum = check.value;
}
return res;
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/default.ts
function parseDefaultDef(_def, refs) {
return {
...parseDef(_def.innerType._def, refs),
default: _def.defaultValue()
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/effects.ts
function parseEffectsDef(_def, refs) {
return refs.effectStrategy === "input" ? parseDef(_def.schema._def, refs) : parseAnyDef();
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/enum.ts
function parseEnumDef(def) {
return {
type: "string",
enum: Array.from(def.values)
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts
const isJsonSchema7AllOfType = (type) => {
if ("type" in type && type.type === "string") return false;
return "allOf" in type;
};
function parseIntersectionDef(def, refs) {
const allOf = [parseDef(def.left._def, {
...refs,
currentPath: [
...refs.currentPath,
"allOf",
"0"
]
}), parseDef(def.right._def, {
...refs,
currentPath: [
...refs.currentPath,
"allOf",
"1"
]
})].filter((x) => !!x);
const mergedAllOf = [];
allOf.forEach((schema) => {
if (isJsonSchema7AllOfType(schema)) mergedAllOf.push(...schema.allOf);
else {
let nestedSchema = schema;
if ("additionalProperties" in schema && schema.additionalProperties === false) {
const { additionalProperties: _additionalProperties, ...rest } = schema;
nestedSchema = rest;
}
mergedAllOf.push(nestedSchema);
}
});
return mergedAllOf.length ? { allOf: mergedAllOf } : void 0;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/literal.ts
function parseLiteralDef(def) {
const parsedType = typeof def.value;
if (parsedType !== "bigint" && parsedType !== "number" && parsedType !== "boolean" && parsedType !== "string") return { type: Array.isArray(def.value) ? "array" : "object" };
return {
type: parsedType === "bigint" ? "integer" : parsedType,
const: def.value
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/string.ts
let emojiRegex = void 0;
/**
* Generated from the regular expressions found here as of 2024-05-22:
* https://github.com/colinhacks/zod/blob/master/src/types.ts.
*
* Expressions with /i flag have been changed accordingly.
*/
const zodPatterns = {
/**
* `c` was changed to `[cC]` to replicate /i flag
*/
cuid: /^[cC][^\s-]{8,}$/,
cuid2: /^[0-9a-z]+$/,
ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
/**
* `a-z` was added to replicate /i flag
*/
email: /^(?!\.)(?!.*\.\.)([a-zA-Z0-9_'+\-.]*)[a-zA-Z0-9_+-]@([a-zA-Z0-9][a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$/,
/**
* Constructed a valid Unicode RegExp
*
* Lazily instantiate since this type of regex isn't supported
* in all envs (e.g. React Native).
*
* See:
* https://github.com/colinhacks/zod/issues/2433
* Fix in Zod:
* https://github.com/colinhacks/zod/commit/9340fd51e48576a75adc919bff65dbc4a5d4c99b
*/
emoji: () => {
if (emojiRegex === void 0) emojiRegex = RegExp("^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$", "u");
return emojiRegex;
},
/**
* Unused
*/
uuid: /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
/**
* Unused
*/
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,
ipv4Cidr: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,
/**
* Unused
*/
ipv6: /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/,
ipv6Cidr: /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,
base64: /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,
base64url: /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,
nanoid: /^[a-zA-Z0-9_-]{21}$/,
jwt: /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/
};
function parseStringDef(def, refs) {
const res = { type: "string" };
if (def.checks) for (const check of def.checks) switch (check.kind) {
case "min":
res.minLength = typeof res.minLength === "number" ? Math.max(res.minLength, check.value) : check.value;
break;
case "max":
res.maxLength = typeof res.maxLength === "number" ? Math.min(res.maxLength, check.value) : check.value;
break;
case "email":
switch (refs.emailStrategy) {
case "format:email":
addFormat(res, "email", check.message, refs);
break;
case "format:idn-email":
addFormat(res, "idn-email", check.message, refs);
break;
case "pattern:zod": addPattern(res, zodPatterns.email, check.message, refs);
}
break;
case "url":
addFormat(res, "uri", check.message, refs);
break;
case "uuid":
addFormat(res, "uuid", check.message, refs);
break;
case "regex":
addPattern(res, check.regex, check.message, refs);
break;
case "cuid":
addPattern(res, zodPatterns.cuid, check.message, refs);
break;
case "cuid2":
addPattern(res, zodPatterns.cuid2, check.message, refs);
break;
case "startsWith":
addPattern(res, RegExp(`^${escapeLiteralCheckValue(check.value, refs)}`), check.message, refs);
break;
case "endsWith":
addPattern(res, RegExp(`${escapeLiteralCheckValue(check.value, refs)}$`), check.message, refs);
break;
case "datetime":
addFormat(res, "date-time", check.message, refs);
break;
case "date":
addFormat(res, "date", check.message, refs);
break;
case "time":
addFormat(res, "time", check.message, refs);
break;
case "duration":
addFormat(res, "duration", check.message, refs);
break;
case "length":
res.minLength = typeof res.minLength === "number" ? Math.max(res.minLength, check.value) : check.value;
res.maxLength = typeof res.maxLength === "number" ? Math.min(res.maxLength, check.value) : check.value;
break;
case "includes":
addPattern(res, RegExp(escapeLiteralCheckValue(check.value, refs)), check.message, refs);
break;
case "ip":
if (check.version !== "v6") addFormat(res, "ipv4", check.message, refs);
if (check.version !== "v4") addFormat(res, "ipv6", check.message, refs);
break;
case "base64url":
addPattern(res, zodPatterns.base64url, check.message, refs);
break;
case "jwt":
addPattern(res, zodPatterns.jwt, check.message, refs);
break;
case "cidr":
if (check.version !== "v6") addPattern(res, zodPatterns.ipv4Cidr, check.message, refs);
if (check.version !== "v4") addPattern(res, zodPatterns.ipv6Cidr, check.message, refs);
break;
case "emoji":
addPattern(res, zodPatterns.emoji(), check.message, refs);
break;
case "ulid":
addPattern(res, zodPatterns.ulid, check.message, refs);
break;
case "base64":
switch (refs.base64Strategy) {
case "format:binary":
addFormat(res, "binary", check.message, refs);
break;
case "contentEncoding:base64":
res.contentEncoding = "base64";
break;
case "pattern:zod": addPattern(res, zodPatterns.base64, check.message, refs);
}
break;
case "nanoid": addPattern(res, zodPatterns.nanoid, check.message, refs);
}
return res;
}
function escapeLiteralCheckValue(literal, refs) {
return refs.patternStrategy === "escape" ? escapeNonAlphaNumeric(literal) : literal;
}
const ALPHA_NUMERIC = /* @__PURE__ */ new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
function escapeNonAlphaNumeric(source) {
let result = "";
for (let i = 0; i < source.length; i++) {
if (!ALPHA_NUMERIC.has(source[i])) result += "\\";
result += source[i];
}
return result;
}
function addFormat(schema, value, message, refs) {
if (schema.format || schema.anyOf?.some((x) => x.format)) {
if (!schema.anyOf) schema.anyOf = [];
if (schema.format) {
schema.anyOf.push({ format: schema.format });
delete schema.format;
}
schema.anyOf.push({
format: value,
...message && refs.errorMessages && { errorMessage: { format: message } }
});
} else schema.format = value;
}
function addPattern(schema, regex, message, refs) {
if (schema.pattern || schema.allOf?.some((x) => x.pattern)) {
if (!schema.allOf) schema.allOf = [];
if (schema.pattern) {
schema.allOf.push({ pattern: schema.pattern });
delete schema.pattern;
}
schema.allOf.push({
pattern: stringifyRegExpWithFlags(regex, refs),
...message && refs.errorMessages && { errorMessage: { pattern: message } }
});
} else schema.pattern = stringifyRegExpWithFlags(regex, refs);
}
function stringifyRegExpWithFlags(regex, refs) {
if (!refs.applyRegexFlags || !regex.flags) return regex.source;
const flags = {
i: regex.flags.includes("i"),
m: regex.flags.includes("m"),
s: regex.flags.includes("s")
};
const source = flags.i ? regex.source.toLowerCase() : regex.source;
let pattern = "";
let isEscaped = false;
let inCharGroup = false;
let inCharRange = false;
for (let i = 0; i < source.length; i++) {
if (isEscaped) {
pattern += source[i];
isEscaped = false;
continue;
}
if (flags.i) {
if (inCharGroup) {
if (source[i].match(/[a-z]/)) {
if (inCharRange) {
pattern += source[i];
pattern += `${source[i - 2]}-${source[i]}`.toUpperCase();
inCharRange = false;
} else if (source[i + 1] === "-" && source[i + 2]?.match(/[a-z]/)) {
pattern += source[i];
inCharRange = true;
} else pattern += `${source[i]}${source[i].toUpperCase()}`;
continue;
}
} else if (source[i].match(/[a-z]/)) {
pattern += `[${source[i]}${source[i].toUpperCase()}]`;
continue;
}
}
if (flags.m) {
if (source[i] === "^") {
pattern += `(^|(?<=[\r\n]))`;
continue;
} else if (source[i] === "$") {
pattern += `($|(?=[\r\n]))`;
continue;
}
}
if (flags.s && source[i] === ".") {
pattern += inCharGroup ? `${source[i]}\r\n` : `[${source[i]}\r\n]`;
continue;
}
pattern += source[i];
if (source[i] === "\\") isEscaped = true;
else if (inCharGroup && source[i] === "]") inCharGroup = false;
else if (!inCharGroup && source[i] === "[") inCharGroup = true;
}
try {
new RegExp(pattern);
} catch {
console.warn(`Could not convert regex pattern at ${refs.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`);
return regex.source;
}
return pattern;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/record.ts
function parseRecordDef(def, refs) {
const schema = {
type: "object",
additionalProperties: parseDef(def.valueType._def, {
...refs,
currentPath: [...refs.currentPath, "additionalProperties"]
}) ?? refs.allowedAdditionalProperties
};
if (def.keyType?._def.typeName === "ZodString" && def.keyType._def.checks?.length) {
const { type: _type, ...keyType } = parseStringDef(def.keyType._def, refs);
return {
...schema,
propertyNames: keyType
};
} else if (def.keyType?._def.typeName === "ZodEnum") return {
...schema,
propertyNames: { enum: def.keyType._def.values }
};
else if (def.keyType?._def.typeName === "ZodBranded" && def.keyType._def.type._def.typeName === "ZodString" && def.keyType._def.type._def.checks?.length) {
const { type: _type, ...keyType } = parseBrandedDef(def.keyType._def, refs);
return {
...schema,
propertyNames: keyType
};
}
return schema;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/map.ts
function parseMapDef(def, refs) {
if (refs.mapStrategy === "record") return parseRecordDef(def, refs);
return {
type: "array",
maxItems: 125,
items: {
type: "array",
items: [parseDef(def.keyType._def, {
...refs,
currentPath: [
...refs.currentPath,
"items",
"items",
"0"
]
}) || parseAnyDef(), parseDef(def.valueType._def, {
...refs,
currentPath: [
...refs.currentPath,
"items",
"items",
"1"
]
}) || parseAnyDef()],
minItems: 2,
maxItems: 2
}
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts
function parseNativeEnumDef(def) {
const object = def.values;
const actualValues = Object.keys(def.values).filter((key) => {
return typeof object[object[key]] !== "number";
}).map((key) => object[key]);
const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
return {
type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
enum: actualValues
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/never.ts
function parseNeverDef() {
return { not: parseAnyDef() };
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/null.ts
function parseNullDef() {
return { type: "null" };
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/union.ts
const primitiveMappings = {
ZodString: "string",
ZodNumber: "number",
ZodBigInt: "integer",
ZodBoolean: "boolean",
ZodNull: "null"
};
function parseUnionDef(def, refs) {
const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options;
if (options.every((x) => x._def.typeName in primitiveMappings && (!x._def.checks || !x._def.checks.length))) {
const types = options.reduce((types, x) => {
const type = primitiveMappings[x._def.typeName];
return type && !types.includes(type) ? [...types, type] : types;
}, []);
return { type: types.length > 1 ? types : types[0] };
} else if (options.every((x) => x._def.typeName === "ZodLiteral" && !x.description)) {
const types = options.reduce((acc, x) => {
const type = typeof x._def.value;
switch (type) {
case "string":
case "number":
case "boolean": return [...acc, type];
case "bigint": return [...acc, "integer"];
case "object": if (x._def.value === null) return [...acc, "null"];
default: return acc;
}
}, []);
if (types.length === options.length) {
const uniqueTypes = types.filter((x, i, a) => a.indexOf(x) === i);
return {
type: uniqueTypes.length > 1 ? uniqueTypes : uniqueTypes[0],
enum: options.reduce((acc, x) => {
return acc.includes(x._def.value) ? acc : [...acc, x._def.value];
}, [])
};
}
} else if (options.every((x) => x._def.typeName === "ZodEnum")) return {
type: "string",
enum: options.reduce((acc, x) => [...acc, ...x._def.values.filter((x) => !acc.includes(x))], [])
};
return asAnyOf(def, refs);
}
const asAnyOf = (def, refs) => {
const anyOf = (def.options instanceof Map ? Array.from(def.options.values()) : def.options).map((x, i) => parseDef(x._def, {
...refs,
currentPath: [
...refs.currentPath,
"anyOf",
`${i}`
]
})).filter((x) => !!x && (!refs.strictUnions || typeof x === "object" && Object.keys(x).length > 0));
return anyOf.length ? { anyOf } : void 0;
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts
function parseNullableDef(def, refs) {
if ([
"ZodString",
"ZodNumber",
"ZodBigInt",
"ZodBoolean",
"ZodNull"
].includes(def.innerType._def.typeName) && (!def.innerType._def.checks || !def.innerType._def.checks.length)) return { type: [primitiveMappings[def.innerType._def.typeName], "null"] };
const base = parseDef(def.innerType._def, {
...refs,
currentPath: [
...refs.currentPath,
"anyOf",
"0"
]
});
return base && { anyOf: [base, { type: "null" }] };
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/number.ts
function parseNumberDef(def) {
const res = { type: "number" };
if (!def.checks) return res;
for (const check of def.checks) switch (check.kind) {
case "int":
res.type = "integer";
break;
case "min":
if (check.inclusive) res.minimum = check.value;
else res.exclusiveMinimum = check.value;
break;
case "max":
if (check.inclusive) res.maximum = check.value;
else res.exclusiveMaximum = check.value;
break;
case "multipleOf": res.multipleOf = check.value;
}
return res;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/object.ts
function parseObjectDef(def, refs) {
const result = {
type: "object",
properties: {}
};
const required = [];
const shape = def.shape();
for (const propName in shape) {
let propDef = shape[propName];
if (propDef === void 0 || propDef._def === void 0) continue;
const propOptional = safeIsOptional(propDef);
const parsedDef = parseDef(propDef._def, {
...refs,
currentPath: [
...refs.currentPath,
"properties",
propName
],
propertyPath: [
...refs.currentPath,
"properties",
propName
]
});
if (parsedDef === void 0) continue;
result.properties[propName] = parsedDef;
if (!propOptional) required.push(propName);
}
if (required.length) result.required = required;
const additionalProperties = decideAdditionalProperties(def, refs);
if (additionalProperties !== void 0) result.additionalProperties = additionalProperties;
return result;
}
function decideAdditionalProperties(def, refs) {
if (def.catchall._def.typeName !== "ZodNever") return parseDef(def.catchall._def, {
...refs,
currentPath: [...refs.currentPath, "additionalProperties"]
});
switch (def.unknownKeys) {
case "passthrough": return refs.allowedAdditionalProperties;
case "strict": return refs.rejectedAdditionalProperties;
case "strip": return refs.removeAdditionalStrategy === "strict" ? refs.allowedAdditionalProperties : refs.rejectedAdditionalProperties;
}
}
function safeIsOptional(schema) {
try {
return schema.isOptional();
} catch {
return true;
}
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/optional.ts
const parseOptionalDef = (def, refs) => {
if (refs.currentPath.toString() === refs.propertyPath?.toString()) return parseDef(def.innerType._def, refs);
const innerSchema = parseDef(def.innerType._def, {
...refs,
currentPath: [
...refs.currentPath,
"anyOf",
"1"
]
});
return innerSchema ? { anyOf: [{ not: parseAnyDef() }, innerSchema] } : parseAnyDef();
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts
const parsePipelineDef = (def, refs) => {
if (refs.pipeStrategy === "input") return parseDef(def.in._def, refs);
else if (refs.pipeStrategy === "output") return parseDef(def.out._def, refs);
const inputSchema = parseDef(def.in._def, {
...refs,
currentPath: [
...refs.currentPath,
"allOf",
"0"
]
});
return { allOf: [inputSchema, parseDef(def.out._def, {
...refs,
currentPath: [
...refs.currentPath,
"allOf",
inputSchema ? "1" : "0"
]
})].filter((schema) => schema !== void 0) };
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/promise.ts
function parsePromiseDef(def, refs) {
return parseDef(def.type._def, refs);
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/set.ts
function parseSetDef(def, refs) {
const schema = {
type: "array",
uniqueItems: true,
items: parseDef(def.valueType._def, {
...refs,
currentPath: [...refs.currentPath, "items"]
})
};
if (def.minSize) schema.minItems = def.minSize.value;
if (def.maxSize) schema.maxItems = def.maxSize.value;
return schema;
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts
function parseTupleDef(def, refs) {
if (def.rest) return {
type: "array",
minItems: def.items.length,
items: def.items.map((x, i) => parseDef(x._def, {
...refs,
currentPath: [
...refs.currentPath,
"items",
`${i}`
]
})).reduce((acc, x) => x === void 0 ? acc : [...acc, x], []),
additionalItems: parseDef(def.rest._def, {
...refs,
currentPath: [...refs.currentPath, "additionalItems"]
})
};
else return {
type: "array",
minItems: def.items.length,
maxItems: def.items.length,
items: def.items.map((x, i) => parseDef(x._def, {
...refs,
currentPath: [
...refs.currentPath,
"items",
`${i}`
]
})).reduce((acc, x) => x === void 0 ? acc : [...acc, x], [])
};
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts
function parseUndefinedDef() {
return { not: parseAnyDef() };
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts
function parseUnknownDef() {
return parseAnyDef();
}
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts
const parseReadonlyDef = (def, refs) => {
return parseDef(def.innerType._def, refs);
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/select-parser.ts
const selectParser = (def, typeName, refs) => {
switch (typeName) {
case "ZodString": return parseStringDef(def, refs);
case "ZodNumber": return parseNumberDef(def);
case "ZodObject": return parseObjectDef(def, refs);
case "ZodBigInt": return parseBigintDef(def);
case "ZodBoolean": return parseBooleanDef();
case "ZodDate": return parseDateDef(def, refs);
case "ZodUndefined": return parseUndefinedDef();
case "ZodNull": return parseNullDef();
case "ZodArray": return parseArrayDef(def, refs);
case "ZodUnion":
case "ZodDiscriminatedUnion": return parseUnionDef(def, refs);
case "ZodIntersection": return parseIntersectionDef(def, refs);
case "ZodTuple": return parseTupleDef(def, refs);
case "ZodRecord": return parseRecordDef(def, refs);
case "ZodLiteral": return parseLiteralDef(def);
case "ZodEnum": return parseEnumDef(def);
case "ZodNativeEnum": return parseNativeEnumDef(def);
case "ZodNullable": return parseNullableDef(def, refs);
case "ZodOptional": return parseOptionalDef(def, refs);
case "ZodMap": return parseMapDef(def, refs);
case "ZodSet": return parseSetDef(def, refs);
case "ZodLazy": return () => def.getter()._def;
case "ZodPromise": return parsePromiseDef(def, refs);
case "ZodNaN":
case "ZodNever": return parseNeverDef();
case "ZodEffects": return parseEffectsDef(def, refs);
case "ZodAny": return parseAnyDef();
case "ZodUnknown": return parseUnknownDef();
case "ZodDefault": return parseDefaultDef(def, refs);
case "ZodBranded": return parseBrandedDef(def, refs);
case "ZodReadonly": return parseReadonlyDef(def, refs);
case "ZodCatch": return parseCatchDef(def, refs);
case "ZodPipeline": return parsePipelineDef(def, refs);
case "ZodFunction":
case "ZodVoid":
case "ZodSymbol": return;
default:
/* c8 ignore next */
return ((_) => void 0)(typeName);
}
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/get-relative-path.ts
const getRelativePath = (pathA, pathB) => {
let i = 0;
for (; i < pathA.length && i < pathB.length; i++) if (pathA[i] !== pathB[i]) break;
return [(pathA.length - i).toString(), ...pathB.slice(i)].join("/");
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/parse-def.ts
function parseDef(def, refs, forceResolution = false) {
const seenItem = refs.seen.get(def);
if (refs.override) {
const overrideResult = refs.override?.(def, refs, seenItem, forceResolution);
if (overrideResult !== ignoreOverride) return overrideResult;
}
if (seenItem && !forceResolution) {
const seenSchema = get$ref(seenItem, refs);
if (seenSchema !== void 0) return seenSchema;
}
const newItem = {
def,
path: refs.currentPath,
jsonSchema: void 0
};
refs.seen.set(def, newItem);
const jsonSchemaOrGetter = selectParser(def, def.typeName, refs);
const jsonSchema = typeof jsonSchemaOrGetter === "function" ? parseDef(jsonSchemaOrGetter(), refs) : jsonSchemaOrGetter;
if (jsonSchema) addMeta(def, refs, jsonSchema);
if (refs.postProcess) {
const postProcessResult = refs.postProcess(jsonSchema, def, refs);
newItem.jsonSchema = jsonSchema;
return postProcessResult;
}
newItem.jsonSchema = jsonSchema;
return jsonSchema;
}
const get$ref = (item, refs) => {
switch (refs.$refStrategy) {
case "root": return { $ref: item.path.join("/") };
case "relative": return { $ref: getRelativePath(refs.currentPath, item.path) };
case "none":
case "seen":
if (item.path.length < refs.currentPath.length && item.path.every((value, index) => refs.currentPath[index] === value)) {
console.warn(`Recursive reference detected at ${refs.currentPath.join("/")}! Defaulting to any`);
return parseAnyDef();
}
return refs.$refStrategy === "seen" ? parseAnyDef() : void 0;
}
};
const addMeta = (def, refs, jsonSchema) => {
if (def.description) jsonSchema.description = def.description;
return jsonSchema;
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/refs.ts
const getRefs = (options) => {
const _options = getDefaultOptions(options);
const currentPath = _options.name !== void 0 ? [
..._options.basePath,
_options.definitionPath,
_options.name
] : _options.basePath;
return {
..._options,
currentPath,
propertyPath: void 0,
seen: new Map(Object.entries(_options.definitions).map(([name, def]) => [def._def, {
def: def._def,
path: [
..._options.basePath,
_options.definitionPath,
name
],
jsonSchema: void 0
}]))
};
};
//#endregion
//#region src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts
const zod3ToJsonSchema = (schema, options) => {
const refs = getRefs(options);
let definitions = typeof options === "object" && options.definitions ? Object.entries(options.definitions).reduce((acc, [name, schema]) => ({
...acc,
[name]: parseDef(schema._def, {
...refs,
currentPath: [
...refs.basePath,
refs.definitionPath,
name
]
}, true) ?? parseAnyDef()
}), {}) : void 0;
const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
const main = parseDef(schema._def, name === void 0 ? refs : {
...refs,
currentPath: [
...refs.basePath,
refs.definitionPath,
name
]
}, false) ?? parseAnyDef();
const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
if (title !== void 0) main.title = title;
const combined = name === void 0 ? definitions ? {
...main,
[refs.definitionPath]: definitions
} : main : {
$ref: [
...refs.$refStrategy === "relative" ? [] : refs.basePath,
refs.definitionPath,
name
].join("/"),
[refs.definitionPath]: {
...definitions,
[name]: main
}
};
combined.$schema = "http://json-schema.org/draft-07/schema#";
return combined;
};
//#endregion
//#region src/schema.ts
/**
* Used to mark schemas so we can support both Zod and custom schemas.
*/
const schemaSymbol = Symbol.for("vercel.ai.schema");
/**
* Creates a schema with deferred creation.
* This is important to reduce the startup time of the library
* and to avoid initializing unused validators.
*
* @param createValidator A function that creates a schema.
* @returns A function that returns a schema.
*/
function lazySchema(createSchema) {
let schema;
return () => {
if (schema == null) schema = createSchema();
return schema;
};
}
/**
* Create a schema using a JSON Schema.
*
* @param jsonSchema The JSON Schema for the schema.
* @param options.validate Optional. A validation function for the schema.
*/
function jsonSchema(jsonSchema, { validate } = {}) {
return {
[schemaSymbol]: true,
_type: void 0,
get jsonSchema() {
if (typeof jsonSchema === "function") jsonSchema = jsonSchema();
return jsonSchema;
},
validate
};
}
function isSchema(value) {
return typeof value === "object" && value !== null && schemaSymbol in value && value[schemaSymbol] === true && "jsonSchema" in value && "validate" in value;
}
function asSchema(schema) {
return schema == null ? jsonSchema({
type: "object",
properties: {},
additionalProperties: false
}) : isSchema(schema) ? schema : "~standard" in schema ? schema["~standard"].vendor === "zod" ? zodSchema(schema) : standardSchema(schema) : schema();
}
function standardSchema(standardSchema) {
return jsonSchema(() => {
if (!hasStandardJsonSchema(standardSchema)) throw new Error(`Standard schema vendor '${standardSchema["~standard"].vendor}' does not support JSON Schema conversion.`);
return addAdditionalPropertiesToJsonSchema(standardSchema["~standard"].jsonSchema.input({ target: "draft-07" }));
}, { validate: async (value) => {
const result = await standardSchema["~standard"].validate(value);
return "value" in result ? {
success: true,
value: result.value
} : {
success: false,
error: new TypeValidationError({
value,
cause: result.issues
})
};
} });
}
function hasStandardJsonSchema(schema) {
return schema["~standard"].jsonSchema != null;
}
function zod3Schema(zodSchema, options) {
const useReferences = options?.useReferences ?? false;
return jsonSchema(() => zod3ToJsonSchema(zodSchema, { $refStrategy: useReferences ? "root" : "none" }), { validate: async (value) => {
const result = await zodSchema.safeParseAsync(value);
return result.success ? {
success: true,
value: result.data
} : {
success: false,
error: result.error
};
} });
}
function zod4Schema(zodSchema, options) {
if (!config().localeError) config(en());
const useReferences = options?.useReferences ?? false;
return jsonSchema(() => addAdditionalPropertiesToJsonSchema(toJSONSchema(zodSchema, {
target: "draft-7",
io: "input",
reused: useReferences ? "ref" : "inline"
})), { validate: async (value) => {
const result = await safeParseAsync(zodSchema, value);
return result.success ? {
success: true,
value: result.data
} : {
success: false,
error: result.error
};
} });
}
function isZod4Schema(zodSchema) {
return "_zod" in zodSchema;
}
function zodSchema(zodSchema, options) {
if (isZod4Schema(zodSchema)) return zod4Schema(zodSchema, options);
else return zod3Schema(zodSchema, options);
}
//#endregion
//#region src/validate-types.ts
/**
* Validates the types of an unknown object using a schema and
* return a strongly-typed object.
*
* @template T - The type of the object to validate.
* @param {string} options.value - The object to validate.
* @param {Validator<T>} options.schema - The schema to use for validating the JSON.
* @param {TypeValidationContext} options.context - Optional context about what is being validated.
* @returns {Promise<T>} - The typed object.
*/
async function validateTypes({ value, schema, context }) {
const result = await safeValidateTypes({
value,
schema,
context
});
if (!result.success) throw TypeValidationError.wrap({
value,
cause: result.error,
context
});
return result.value;
}
/**
* Safely validates the types of an unknown object using a schema and
* return a strongly-typed object.
*
* @template T - The type of the object to validate.
* @param {string} options.value - The JSON object to validate.
* @param {Validator<T>} options.schema - The schema to use for validating the JSON.
* @param {TypeValidationContext} options.context - Optional context about what is being validated.
* @returns An object with either a `success` flag and the parsed and typed data, or a `success` flag and an error object.
*/
async function safeValidateTypes({ value, schema, context }) {
const actualSchema = asSchema(schema);
try {
if (actualSchema.validate == null) return {
success: true,
value,
rawValue: value
};
const result = await actualSchema.validate(value);
if (result.success) return {
success: true,
value: result.value,
rawValue: value
};
return {
success: false,
error: TypeValidationError.wrap({
value,
cause: result.error,
context
}),
rawValue: value
};
} catch (error) {
return {
success: false,
error: TypeValidationError.wrap({
value,
cause: error,
context
}),
rawValue: value
};
}
}
//#endregion
//#region src/parse-json.ts
async function parseJSON({ text, schema }) {
try {
const value = secureJsonParse(text);
if (schema == null) return value;
return await validateTypes({
value,
schema
});
} catch (error) {
if (JSONParseError.isInstance(error) || TypeValidationError.isInstance(error)) throw error;
throw new JSONParseError({
text,
cause: error
});
}
}
async function safeParseJSON({ text, schema }) {
try {
const value = secureJsonParse(text);
if (schema == null) return {
success: true,
value,
rawValue: value
};
return await safeValidateTypes({
value,
schema
});
} catch (error) {
return {
success: false,
error: JSONParseError.isInstance(error) ? error : new JSONParseError({
text,
cause: error
}),
rawValue: void 0
};
}
}
function isParsableJson(input) {
try {
secureJsonParse(input);
return true;
} catch {
return false;
}
}
//#endregion
//#region src/parse-json-event-stream.ts
/**
* Parses a JSON event stream into a stream of parsed JSON objects.
*/
function parseJsonEventStream({ stream, schema }) {
return stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream$1()).pipeThrough(new TransformStream({ async transform({ data }, controller) {
if (data === "[DONE]") return;
controller.enqueue(await safeParseJSON({
text: data,
schema
}));
} }));
}
//#endregion
//#region src/parse-provider-options.ts
async function parseProviderOptions({ provider, providerOptions, schema }) {
if (providerOptions?.[provider] == null) return;
const parsedProviderOptions = await safeValidateTypes({
value: providerOptions[provider],
schema
});
if (!parsedProviderOptions.success) throw new InvalidArgumentError({
argument: "providerOptions",
message: `invalid ${provider} provider options`,
cause: parsedProviderOptions.error
});
return parsedProviderOptions.value;
}
//#endregion
//#region src/resolve-provider-options.ts
/**
* Parses provider options that a model accepts under more than one
* `providerOptions` namespace, e.g. a canonical key plus a provider-specific
* or legacy alias.
*
* Each present namespace is validated against `schema` exactly like
* `parseProviderOptions` does, so an invalid namespace throws.
*
* @returns the resolved options (`undefined` when no namespace is present)
* and the namespaces that contributed to them, in resolution order.
*/
async function resolveProviderOptions({ providerOptions, namespaces, resolution, schema }) {
const parsed = [];
const matched = [];
for (const namespace of new Set(namespaces)) {
const options = await parseProviderOptions({
provider: namespace,
providerOptions,
schema
});
if (options == null) continue;
if (resolution === "first") return {
options,
namespaces: [namespace]
};
parsed.push(options);
matched.push(namespace);
}
return {
options: parsed.length === 0 ? void 0 : Object.assign({}, ...parsed),
namespaces: matched
};
}
//#endregion
//#region src/post-multipart-stream-to-api.ts
const getOriginalFetch$1 = () => globalThis.fetch;
function escapeMultipartHeaderValue(value) {
return value.replace(/[\r\n]/g, "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
}
function createMultipartBody(parts, boundary) {
const encoder = new TextEncoder();
let disposed = false;
let activeReader;
const enteredStreams = /* @__PURE__ */ new Set();
async function* emitParts() {
for (const part of parts) {
if (disposed) return;
const disposition = `--${boundary}\r\nContent-Disposition: form-data; name="${escapeMultipartHeaderValue(part.name)}"`;
if (part.type === "field") {
yield encoder.encode(`${disposition}\r\n\r\n${part.value}\r\n`);
continue;
}
const filenameParameter = `; filename="${escapeMultipartHeaderValue(part.filename ?? "blob")}"`;
const mediaType = (part.mediaType ?? "application/octet-stream").replace(/[\r\n]/g, "");
yield encoder.encode(`${disposition}${filenameParameter}\r\nContent-Type: ${mediaType}\r\n\r\n`);
if (part.content instanceof Uint8Array) yield part.content;
else {
const reader = part.content.getReader();
activeReader = reader;
enteredStreams.add(part.content);
let finished = false;
try {
while (true) {
const { done, value } = await reader.read();
if (done || disposed) {
finished = done;
break;
}
yield value;
}
} finally {
activeReader = void 0;
if (!finished) await reader.cancel().catch(() => {});
reader.releaseLock();
}
if (disposed) return;
}
yield encoder.encode("\r\n");
}
yield encoder.encode(`--${boundary}--\r\n`);
}
return {
stream: convertAsyncIteratorToReadableStream(emitParts()),
async dispose(reason) {
if (disposed) return;
disposed = true;
const reader = activeReader;
if (reader != null) await reader.cancel(reason).catch(() => {});
for (const part of parts) if (part.type === "file" && !(part.content instanceof Uint8Array) && !enteredStreams.has(part.content)) await part.content.cancel(reason).catch(() => {});
}
};
}
/**
* POSTs a multipart/form-data body as a request stream, so file parts backed
* by a `ReadableStream` are sent without buffering the full file in memory.
*
* Requires a fetch implementation that supports streaming request bodies
* (`duplex: 'half'`). Callers with fully buffered payloads can keep using
* `postFormDataToApi`.
*/
const postMultipartStreamToApi = async ({ url, headers = {}, parts, failedResponseHandler, successfulResponseHandler, abortSignal, fetch = getOriginalFetch$1() }) => {
const boundary = `ai-sdk-multipart-${generateId()}`;
const requestBodyValues = Object.fromEntries(parts.map((part) => [part.name, part.type === "field" ? part.value : `<file:${part.filename ?? part.name}>`]));
const body = createMultipartBody(parts, boundary);
try {
const response = await fetch(url, {
method: "POST",
headers: withUserAgentSuffix({
...headers,
"Content-Type": `multipart/form-data; boundary=${boundary}`
}, `ai-sdk-provider-utils/${VERSION}`, getRuntimeEnvironmentUserAgent()),
body: body.stream,
duplex: "half",
signal: abortSignal
});
const responseHeaders = extractResponseHeaders(response);
if (!response.ok) {
let errorInformation;
try {
errorInformation = await failedResponseHandler({
response,
url,
requestBodyValues
});
} catch (error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
throw new APICallError({
message: "Failed to process error response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues
});
}
throw errorInformation.value;
}
try {
return await successfulResponseHandler({
response,
url,
requestBodyValues
});
} catch (error) {
if (error instanceof Error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
}
throw new APICallError({
message: "Failed to process successful response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues
});
}
} catch (error) {
await body.dispose(error);
throw handleFetchError({
error,
url,
requestBodyValues
});
}
};
//#endregion
//#region src/post-to-api.ts
const getOriginalFetch = () => globalThis.fetch;
const postJsonToApi = async ({ url, headers, body, failedResponseHandler, successfulResponseHandler, abortSignal, fetch }) => await postToApi({
url,
headers: {
"Content-Type": "application/json",
...headers
},
body: {
content: JSON.stringify(body),
values: body
},
failedResponseHandler,
successfulResponseHandler,
abortSignal,
fetch
});
const postFormDataToApi = async ({ url, headers, formData, failedResponseHandler, successfulResponseHandler, abortSignal, fetch }) => await postToApi({
url,
headers,
body: {
content: formData,
values: Object.fromEntries(formData.entries())
},
failedResponseHandler,
successfulResponseHandler,
abortSignal,
fetch
});
const postToApi = async ({ url, headers = {}, body, successfulResponseHandler, failedResponseHandler, abortSignal, fetch = getOriginalFetch() }) => {
try {
const response = await fetch(url, {
method: "POST",
headers: withUserAgentSuffix(headers, `ai-sdk-provider-utils/${VERSION}`, getRuntimeEnvironmentUserAgent()),
body: body.content,
signal: abortSignal
});
const responseHeaders = extractResponseHeaders(response);
if (!response.ok) {
let errorInformation;
try {
errorInformation = await failedResponseHandler({
response,
url,
requestBodyValues: body.values
});
} catch (error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
throw new APICallError({
message: "Failed to process error response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: body.values
});
}
throw errorInformation.value;
}
try {
return await successfulResponseHandler({
response,
url,
requestBodyValues: body.values
});
} catch (error) {
if (error instanceof Error) {
if (isAbortError(error) || APICallError.isInstance(error)) throw error;
}
throw new APICallError({
message: "Failed to process successful response",
cause: error,
statusCode: response.status,
url,
responseHeaders,
requestBodyValues: body.values
});
}
} catch (error) {
throw handleFetchError({
error,
url,
requestBodyValues: body.values
});
}
};
//#endregion
//#region src/types/tool.ts
function tool(tool) {
return tool;
}
/**
* Define a dynamic tool.
*/
function dynamicTool(tool) {
return {
...tool,
type: "dynamic"
};
}
//#endregion
//#region src/telemetry-redaction.ts
const telemetryRedaction = Symbol.for("vercel.ai.telemetry.redact");
/**
* Attach a provider-owned telemetry view without changing the original value.
* The annotation is not serialized or included in enumerable application data.
* Keep the annotated object when forwarding tool arguments or provider metadata.
*/
function withTelemetryRedaction(value, redact) {
return Object.create(Object.getPrototypeOf(value), {
...Object.getOwnPropertyDescriptors(value),
[telemetryRedaction]: {
value: redact,
configurable: true
}
});
}
/** Apply the provider's telemetry view, leaving unannotated values unchanged. */
function redactTelemetryValue(value) {
if (value == null || typeof value !== "object") return value;
const redact = Object.getOwnPropertyDescriptor(value, telemetryRedaction)?.value;
if (typeof redact !== "function") return value;
try {
return redact(value);
} catch {
return;
}
}
//#endregion
//#region src/provider-defined-tool-factory.ts
function createProviderDefinedToolFactory({ id, inputSchema, redactArgs }) {
return ({ execute, outputSchema, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable, ...args }) => tool({
type: "provider",
isProviderExecuted: false,
id,
args: redactArgs == null ? args : withTelemetryRedaction(args, redactArgs),
inputSchema,
outputSchema,
execute,
needsApproval,
toModelOutput,
onInputStart,
onInputDelta,
onInputAvailable
});
}
function createProviderDefinedToolFactoryWithOutputSchema({ id, inputSchema, outputSchema, redactArgs }) {
return ({ execute, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable, ...args }) => tool({
type: "provider",
isProviderExecuted: false,
id,
args: redactArgs == null ? args : withTelemetryRedaction(args, redactArgs),
inputSchema,
outputSchema,
execute,
needsApproval,
toModelOutput,
onInputStart,
onInputDelta,
onInputAvailable
});
}
//#endregion
//#region src/provider-executed-tool-factory.ts
function createProviderExecutedToolFactory({ id, inputSchema, outputSchema, supportsDeferredResults, redactArgs }) {
return ({ onInputStart, onInputDelta, onInputAvailable, ...args }) => tool({
type: "provider",
isProviderExecuted: true,
id,
args: redactArgs == null ? args : withTelemetryRedaction(args, redactArgs),
inputSchema,
outputSchema,
onInputStart,
onInputDelta,
onInputAvailable,
supportsDeferredResults
});
}
//#endregion
//#region src/resolve.ts
/**
* Resolves a value that could be a raw value, a Promise, a function returning a value,
* or a function returning a Promise.
*/
async function resolve(value) {
if (typeof value === "function") value = value();
return value;
}
/**
* Resolves and transforms a value, preserving synchronous results. This allows
* synchronous credentials to remain usable by workflow serialization while
* asynchronous credentials are awaited by request handlers.
*/
function resolveWith(value, transform) {
const resolved = typeof value === "function" ? value() : value;
if (resolved != null && (typeof resolved === "object" || typeof resolved === "function") && typeof resolved.then === "function") return Promise.resolve(resolved).then(transform);
return transform(resolved);
}
//#endregion
//#region src/resolve-full-media-type.ts
/**
* Resolves a file part's media type to a full `type/subtype` form required by
* providers whose API demands the full IANA media type.
*
* - If `part.mediaType` is already a full media type (e.g. `image/png`), it is
* returned as-is.
* - Otherwise, when inline bytes are available (`part.data.type === 'data'`),
* the subtype is sniffed from the bytes using the signature table that
* corresponds to the top-level segment.
* - When neither applies (e.g. top-level-only with a URL source, or bytes that
* cannot be detected), an `UnsupportedFunctionalityError` is thrown.
*/
function resolveFullMediaType({ part }) {
if (isFullMediaType(part.mediaType)) return part.mediaType;
if (part.data.type === "data") {
const detected = detectMediaType({
data: part.data.data,
topLevelType: getTopLevelMediaType(part.mediaType)
});
if (detected) return detected;
throw new UnsupportedFunctionalityError({ functionality: `file of media type "${part.mediaType}" must specify subtype since it could not be auto-detected` });
}
throw new UnsupportedFunctionalityError({ functionality: `file of media type "${part.mediaType}" must specify subtype since it is not passed as inline bytes` });
}
//#endregion
//#region src/resolve-provider-reference.ts
/**
* Resolves a provider reference to the provider-specific identifier for the
* given provider. Throws `NoSuchProviderReferenceError` if the provider is not
* found in the reference mapping.
*/
function resolveProviderReference({ reference, provider }) {
const id = reference[provider];
if (id != null) return id;
throw new NoSuchProviderReferenceError({
provider,
reference
});
}
//#endregion
//#region src/retry-with-exponential-backoff.ts
/**
* Retries a failed operation with exponential backoff.
*/
const retryWithExponentialBackoff = ({ maxRetries = 2, initialDelayInMs = 2e3, backoffFactor = 2, abortSignal, shouldRetry, getDelayInMs = ({ exponentialBackoffDelay }) => exponentialBackoffDelay, createRetryError = ({ message }) => new Error(message) }) => async (f) => retryWithExponentialBackoffInternal(f, {
maxRetries,
delayInMs: initialDelayInMs,
backoffFactor,
abortSignal,
shouldRetry,
getDelayInMs,
createRetryError
});
async function retryWithExponentialBackoffInternal(f, { maxRetries, delayInMs, backoffFactor, abortSignal, shouldRetry, getDelayInMs, createRetryError }, errors = []) {
try {
return await f();
} catch (error) {
if (isAbortError(error)) throw error;
if (maxRetries === 0) throw error;
const errorMessage = getErrorMessage(error);
const newErrors = [...errors, error];
const tryNumber = newErrors.length;
if (tryNumber > maxRetries) throw createRetryError({
message: `Failed after ${tryNumber} attempts. Last error: ${errorMessage}`,
reason: "maxRetriesExceeded",
errors: newErrors
});
if (await shouldRetry(error) && tryNumber <= maxRetries) {
await delay(getDelayInMs({
error,
exponentialBackoffDelay: delayInMs
}), { abortSignal });
return retryWithExponentialBackoffInternal(f, {
maxRetries,
delayInMs: backoffFactor * delayInMs,
backoffFactor,
abortSignal,
shouldRetry,
getDelayInMs,
createRetryError
}, newErrors);
}
if (tryNumber === 1) throw error;
throw createRetryError({
message: `Failed after ${tryNumber} attempts with non-retryable error: '${errorMessage}'`,
reason: "errorNotRetryable",
errors: newErrors
});
}
}
//#endregion
//#region src/json-binary-field-scanner.ts
/**
* Streaming extraction of large base64 strings from a JSON response body.
*
* A JSON response that carries an image or a file is mostly one very long
* base64 string. The regular pipeline (collect chunks, concatenate, decode to
* text, `JSON.parse`, decode base64) holds about four copies of that string at
* once. The scanner reads the body chunk by chunk instead: strings at the
* configured paths are decoded to bytes while they stream in, and the text that
* is handed to `JSON.parse` (the "skeleton") holds a short placeholder in their
* place.
*
* - The skeleton is the original body except for the extracted strings, so
* `secureJsonParse` still sees every key (a base64 string cannot contain a
* quote, so it cannot hide one).
* - A string is extracted only if it is canonical standard-alphabet base64
* (length divisible by 4, `=` only as the final padding, zero trailing bits,
* no escape sequences or whitespace). The bytes are then exactly what `atob`
* gives for it. Everything else stays a string. What was decoded before a
* string turned out not to qualify is encoded back, which gives the original
* characters.
*/
/** Strings that decode to fewer bytes stay strings. */
const DEFAULT_BINARY_FIELD_MIN_BYTES = 65536;
const QUOTE = 34;
const BACKSLASH = 92;
const EQUALS = 61;
const COMMA = 44;
const COLON = 58;
const LEFT_BRACE = 123;
const RIGHT_BRACE = 125;
const LEFT_BRACKET = 91;
const RIGHT_BRACKET = 93;
const OUTSIDE = 0;
const KEY = 1;
const SKIP = 2;
const BLOB = 3;
/**
* Compiles paths such as `images[*]` or `content[*].data.data`: plain object
* keys (letters, digits, `_` and `-`) joined by `.`, and `[*]` for every
* element of an array. Keys are compared with the raw bytes of the key in the
* body, so a key written with an escape sequence never matches.
*/
function compileBinaryFieldPaths(paths) {
const root = /* @__PURE__ */ new Map();
for (const path of paths) {
if (!/^(?:[\w-]+|\[\*])(?:\.[\w-]+|\[\*])*$/.test(path)) throw new InvalidArgumentError({
argument: "experimental_binaryFields",
message: `Invalid binary field path "${path}"`
});
let node = root;
for (const segment of path.match(/\[\*]|[\w-]+/g)) {
const edges = segment === "[*]" ? [null] : new TextEncoder().encode(`${segment}"`);
for (const edge of edges) {
let next = node.get(edge);
if (next === void 0) node.set(edge, next = /* @__PURE__ */ new Map());
node = next;
}
}
node.set(true, node);
}
return root;
}
function join(parts, length) {
const bytes = new Uint8Array(length);
let at = 0;
for (const part of parts) {
bytes.set(part, at);
at += part.length;
}
return bytes;
}
function createJsonBinaryFieldScanner(paths, minBytes = DEFAULT_BINARY_FIELD_MIN_BYTES) {
const sextet = (/* @__PURE__ */ new Uint8Array(256)).fill(64);
for (let index = 0; index < 64; index++) sextet["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charCodeAt(index)] = index;
const encoder = new TextEncoder();
const pieces = [];
let piece = /* @__PURE__ */ new Uint8Array(65536);
let pieceLength = 0;
let size = 0;
const binaries = /* @__PURE__ */ new Map();
const frames = [];
let skipped = 0;
let mode = OUTSIDE;
let escaped = false;
let last = 0;
let key;
let segments = [];
let segment = /* @__PURE__ */ new Uint8Array(0);
let at = 0;
let total = 0;
let bits = 0;
let chars = 0;
let padding = 0;
const add = (bytes) => {
for (let offset = 0; offset < bytes.length;) {
const length = Math.min(piece.length - pieceLength, bytes.length - offset);
piece.set(bytes.subarray(offset, offset + length), pieceLength);
pieceLength += length;
offset += length;
size += length;
if (pieceLength === piece.length) {
pieces.push(piece);
piece = /* @__PURE__ */ new Uint8Array(65536);
pieceLength = 0;
}
}
};
const emit = (chunk, start, end) => add(chunk.subarray(start, end));
const reset = () => {
segments = [];
segment = /* @__PURE__ */ new Uint8Array(0);
at = 0;
total = 0;
bits = 0;
chars = 0;
padding = 0;
};
const grow = () => {
if (at > 0) segments.push(segment.subarray(0, at));
segment = new Uint8Array(Math.min(Math.max(total, 4096), 4 << 20));
at = 0;
};
const rest = () => bits << (4 - chars) * 6;
const restore = () => {
for (const part of [...segments, segment.subarray(0, at)]) add(encoder.encode(convertUint8ArrayToBase64(part)));
const group = rest();
add(encoder.encode(convertUint8ArrayToBase64(Uint8Array.of(group >> 16, group >> 8, group)).slice(0, chars) + "=".repeat(padding)));
reset();
};
const closeBlob = () => {
mode = OUTSIDE;
const tail = chars && chars - 1;
if (padding === (4 - chars) % 4 && !(bits & (1 << (4 - chars) * 2) - 1) && total + tail >= minBytes) {
const group = rest();
const bytes = join([...segments, segment.subarray(0, at)], total + tail);
for (let index = 0; index < tail; index++) bytes[total + index] = group >> 16 - 8 * index;
const placeholder = `[binary data: ${bytes.length} bytes; ref ${crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(2)).join("-")}]`;
binaries.set(placeholder, bytes);
add(encoder.encode(placeholder));
reset();
} else restore();
};
const push = (chunk) => {
let from = 0;
const { length } = chunk;
for (let index = 0; index < length; index++) {
let byte = chunk[index];
if (mode === BLOB) {
if (padding === 0) {
let pending = bits;
let count = chars;
for (let value; index < length && (value = sextet[chunk[index]]) < 64; index++) {
pending = pending << 6 | value;
if (++count === 4) {
if (at + 3 > segment.length) grow();
segment[at++] = pending >> 16;
segment[at++] = pending >> 8;
segment[at++] = pending;
total += 3;
pending = 0;
count = 0;
}
}
bits = pending;
chars = count;
}
if (index >= length) break;
byte = chunk[index];
if (byte === QUOTE) {
closeBlob();
from = index;
} else if (byte === EQUALS && chars > 1 && padding < 4 - chars) padding++;
else {
restore();
mode = SKIP;
from = index--;
}
} else if (mode !== OUTSIDE) {
if (mode === KEY) key = key?.get(byte);
if (escaped) escaped = false;
else if (byte === BACKSLASH) escaped = true;
else if (byte === QUOTE) {
if (mode === KEY) frames.at(-1).next = key;
mode = OUTSIDE;
}
} else if (byte === QUOTE) {
const frame = frames.at(-1);
if (skipped > 0) mode = SKIP;
else if (frame?.keys && (last === LEFT_BRACE || last === COMMA)) {
mode = KEY;
key = frame.keys;
} else if ((frame ? frame.next : paths)?.get(true)) {
mode = BLOB;
emit(chunk, from, index + 1);
} else mode = SKIP;
} else if (byte === LEFT_BRACE || byte === LEFT_BRACKET) {
last = byte;
const frame = frames.at(-1);
const node = skipped > 0 ? void 0 : frame ? frame.next : paths;
const items = node?.get(null);
if (byte === LEFT_BRACE && node) frames.push({ keys: node });
else if (byte === LEFT_BRACKET && items) frames.push({
keys: null,
next: items
});
else skipped++;
} else if (byte === RIGHT_BRACE || byte === RIGHT_BRACKET) {
if (skipped > 0) skipped--;
else frames.pop();
} else if (byte === COMMA || byte === COLON) last = byte;
}
if (mode !== BLOB) emit(chunk, from, length);
};
const finish = () => {
if (mode === BLOB) restore();
return {
text: new TextDecoder().decode(join([...pieces, piece.subarray(0, pieceLength)], size)),
binaries
};
};
return {
push,
finish
};
}
/**
* Replaces the placeholders in a parsed value with the bytes they stand for.
* Objects and arrays are copied, the parsed value is not modified. A
* placeholder holds a random number, so a string that equals one is ours.
*/
function replaceBinaryFieldPlaceholders(value, binaries) {
if (typeof value === "string") return binaries.get(value) ?? value;
if (Array.isArray(value)) return value.map((item) => replaceBinaryFieldPlaceholders(item, binaries));
if (value != null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([name, item]) => [name, replaceBinaryFieldPlaceholders(item, binaries)]));
return value;
}
/**
* Makes `JSON.stringify` give the placeholder for each of the arrays, instead
* of `{"0":…,"1":…}` with an entry for every byte: 24 MiB of bytes would
* become a string of more than 300 MB. That is what a validator, or the code
* that reports its issues, does with a value it puts into an error message.
* The property is not enumerable, so the arrays look the same otherwise.
* Returns the function that removes it again.
*/
function maskBinariesInJson(binaries) {
for (const [placeholder, bytes] of binaries) Object.defineProperty(bytes, "toJSON", {
value: () => placeholder,
configurable: true
});
return () => {
for (const bytes of binaries.values()) Reflect.deleteProperty(bytes, "toJSON");
};
}
/**
* Wraps a schema so that it validates the extracted bytes in place of their
* placeholders. The value parsed from the skeleton, and the value of the
* validation error of the response, keep the placeholders: reporting an error
* never prints the bytes.
*
* The errors of the schema itself may hold the bytes: a Standard Schema
* validator wraps the value it was given into an error, and the issues it
* reports may carry the offending input. So that their messages do not grow
* with the image, the arrays stringify as their placeholders, and keep doing
* so as long as an error can refer to them. When the validation succeeds the
* arrays are plain again, for whoever receives them.
*/
function withBinaryFields(schema, binaries) {
if (binaries.size === 0) return schema;
const actualSchema = asSchema(schema);
return jsonSchema({}, { validate: async (value) => {
const replaced = replaceBinaryFieldPlaceholders(value, binaries);
const unmask = maskBinariesInJson(binaries);
const result = actualSchema.validate ? await actualSchema.validate(replaced) : {
success: true,
value: replaced
};
if (result.success) unmask();
return result;
} });
}
//#endregion
//#region src/response-handler.ts
const textDecoder = new TextDecoder();
const DEFAULT_MAX_JSON_LINE_BYTES = 67108864;
function wrapResponseBodyStream({ stream, url, requestBodyValues, statusCode, responseHeaders }) {
const reader = stream.getReader();
let readerReleased = false;
const releaseReader = () => {
if (!readerReleased) {
reader.releaseLock();
readerReleased = true;
}
};
return new ReadableStream({
async pull(controller) {
try {
const { done, value } = await reader.read();
if (done) {
releaseReader();
controller.close();
} else controller.enqueue(value);
} catch (error) {
releaseReader();
if (isAbortError(error)) {
controller.error(error);
return;
}
controller.error(handleFetchError({
error: new APICallError({
message: "Failed to process successful response",
cause: error,
statusCode,
url,
responseHeaders,
requestBodyValues
}),
url,
requestBodyValues
}));
}
},
async cancel(reason) {
try {
await reader.cancel(reason);
} finally {
releaseReader();
}
}
});
}
async function readResponseBodyAsText({ response, url }) {
return textDecoder.decode(await readResponseWithSizeLimit({
response,
url
}));
}
const createJsonErrorResponseHandler = ({ errorSchema, errorToMessage, isRetryable }) => async ({ response, url, requestBodyValues }) => {
const responseBody = await readResponseBodyAsText({
response,
url
});
const responseHeaders = extractResponseHeaders(response);
if (responseBody.trim() === "") return {
responseHeaders,
value: new APICallError({
message: response.statusText,
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody,
isRetryable: isRetryable?.(response)
})
};
try {
const parsedError = await parseJSON({
text: responseBody,
schema: errorSchema
});
return {
responseHeaders,
value: new APICallError({
message: errorToMessage(parsedError),
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody,
data: parsedError,
isRetryable: isRetryable?.(response, parsedError)
})
};
} catch {
return {
responseHeaders,
value: new APICallError({
message: response.statusText,
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody,
isRetryable: isRetryable?.(response)
})
};
}
};
const createEventSourceResponseHandler = (chunkSchema) => async ({ response, url, requestBodyValues }) => {
const responseHeaders = extractResponseHeaders(response);
if (response.body == null) throw new EmptyResponseBodyError({});
return {
responseHeaders,
value: parseJsonEventStream({
stream: wrapResponseBodyStream({
stream: response.body,
url,
requestBodyValues,
statusCode: response.status,
responseHeaders
}),
schema: chunkSchema
})
};
};
const createJsonResponseHandler = (responseSchema, { experimental_binaryFields } = {}) => {
const binaryFieldPaths = experimental_binaryFields?.length ? compileBinaryFieldPaths(experimental_binaryFields) : void 0;
return async ({ response, url, requestBodyValues }) => {
let responseBody;
let schema = responseSchema;
if (binaryFieldPaths == null) responseBody = await readResponseBodyAsText({
response,
url
});
else {
const scanner = createJsonBinaryFieldScanner(binaryFieldPaths);
await readResponseWithSizeLimit({
response,
url,
onChunk: scanner.push
});
const scanned = scanner.finish();
responseBody = scanned.text;
schema = withBinaryFields(responseSchema, scanned.binaries);
}
const parsedResult = await safeParseJSON({
text: responseBody,
schema
});
const responseHeaders = extractResponseHeaders(response);
if (!parsedResult.success) throw new APICallError({
message: "Invalid JSON response",
cause: parsedResult.error,
statusCode: response.status,
responseHeaders,
responseBody,
url,
requestBodyValues
});
return {
responseHeaders,
value: parsedResult.value,
rawValue: parsedResult.rawValue
};
};
};
const createJsonLinesResponseHandler = (responseSchema, { maxLineBytes = DEFAULT_MAX_JSON_LINE_BYTES } = {}) => {
if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes <= 0) throw new InvalidArgumentError({
argument: "maxLineBytes",
message: "maxLineBytes must be a positive safe integer."
});
return async ({ response, url }) => {
const responseHeaders = extractResponseHeaders(response);
if (response.body == null) throw new EmptyResponseBodyError({});
return {
responseHeaders,
value: parseJsonLines({
stream: response.body,
schema: responseSchema,
url,
maxLineBytes
})
};
};
};
async function* parseJsonLines({ stream, schema, url, maxLineBytes }) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let buffer = "";
let lineBytes = 0;
let finished = false;
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
finished = true;
buffer += decoder.decode();
break;
}
let offset = 0;
while (offset < value.length) {
const lineEnd = value.indexOf(10, offset);
const segmentEnd = lineEnd === -1 ? value.length : lineEnd;
lineBytes += segmentEnd - offset;
if (lineBytes > maxLineBytes) throw new DownloadError({
message: `JSON Lines response exceeded maximum line size of ${maxLineBytes} bytes.`,
url
});
const decodeEnd = lineEnd === -1 ? segmentEnd : lineEnd + 1;
buffer += decoder.decode(value.subarray(offset, decodeEnd), { stream: true });
if (lineEnd === -1) break;
const line = buffer.slice(0, -1).replace(/\r$/, "");
buffer = "";
lineBytes = 0;
offset = decodeEnd;
if (line.trim().length > 0) yield await parseJSON({
text: line,
schema
});
}
}
const finalLine = buffer.replace(/\r$/, "");
if (finalLine.trim().length > 0) yield await parseJSON({
text: finalLine,
schema
});
} finally {
if (!finished) await reader.cancel().catch(() => {});
reader.releaseLock();
}
}
const createBinaryResponseHandler = () => async ({ response, url, requestBodyValues }) => {
const responseHeaders = extractResponseHeaders(response);
if (!response.body) throw new APICallError({
message: "Response body is empty",
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody: void 0
});
try {
const buffer = await response.arrayBuffer();
return {
responseHeaders,
value: new Uint8Array(buffer)
};
} catch (error) {
throw new APICallError({
message: "Failed to read response as array buffer",
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody: void 0,
cause: error
});
}
};
/**
* Passes the response body through as a `ReadableStream<Uint8Array>` without
* buffering it (unlike `createBinaryResponseHandler`). The consumer is
* responsible for draining or cancelling the stream.
*/
const createBinaryStreamResponseHandler = () => async ({ response, url, requestBodyValues }) => {
const responseHeaders = extractResponseHeaders(response);
if (response.body == null) throw new EmptyResponseBodyError({});
return {
responseHeaders,
value: wrapResponseBodyStream({
stream: response.body,
url,
requestBodyValues,
statusCode: response.status,
responseHeaders
})
};
};
const createStatusCodeErrorResponseHandler = () => async ({ response, url, requestBodyValues }) => {
const responseHeaders = extractResponseHeaders(response);
const responseBody = await readResponseBodyAsText({
response,
url
});
return {
responseHeaders,
value: new APICallError({
message: response.statusText,
url,
requestBodyValues,
statusCode: response.status,
responseHeaders,
responseBody
})
};
};
//#endregion
//#region src/is-json-serializable.ts
/**
* Checks whether a value can cross a workflow serialization boundary.
*
* The check accepts JSON-like primitives, arrays, and plain objects whose
* nested values are also serializable. It rejects functions, symbols,
* bigints, and non-plain objects such as class instances, dates, and regexes.
*/
function isJSONSerializable(value) {
if (value === null || value === void 0) return true;
const type = typeof value;
if (type === "string" || type === "number" || type === "boolean") return true;
if (type === "function" || type === "symbol" || type === "bigint") return false;
if (Array.isArray(value)) return value.every(isJSONSerializable);
if (Object.getPrototypeOf(value) === Object.prototype) return Object.values(value).every(isJSONSerializable);
return false;
}
//#endregion
//#region src/serialization-error.ts
const name = "AI_SerializationError";
const marker = `vercel.ai.error.${name}`;
const symbol = Symbol.for(marker);
var SerializationError = class extends AISDKError {
constructor({ message = "Failed to serialize value.", cause } = {}) {
super({
name,
message,
cause
});
this[symbol] = true;
}
static isInstance(error) {
return AISDKError.hasMarker(error, marker);
}
};
//#endregion
//#region src/serialize-model-options.ts
/**
* Serializes a model instance for workflow step boundaries.
* Returns the `modelId` plus the JSON-serializable config properties.
*
* Non-serializable values are omitted. As a special case, a
* function-valued `headers` property is resolved during serialization
* and included if the returned value is JSON-serializable.
*
* Used as the body of `static [WORKFLOW_SERIALIZE]` in provider models.
*
* @example
* ```ts
* static [WORKFLOW_SERIALIZE](model: MyLanguageModel) {
* return serializeModelOptions({
* modelId: model.modelId,
* config: model.config,
* });
* }
* ```
*/
function serializeModelOptions(options) {
const serializableConfig = {};
for (const [key, value] of Object.entries(options.config)) if (key === "headers") {
const resolvedHeaders = resolveSync(value);
if (isJSONSerializable(resolvedHeaders)) serializableConfig[key] = resolvedHeaders;
} else if (isJSONSerializable(value)) serializableConfig[key] = value;
return {
modelId: options.modelId,
config: serializableConfig
};
}
function resolveSync(value) {
let next = value;
if (typeof value === "function") next = value();
if (next instanceof Promise) throw new SerializationError({ message: "Cannot serialize asynchronous model options." });
return next;
}
//#endregion
//#region src/speech-stream-envelope.ts
/**
* Experimental speech-stream WebSocket envelope (v1): the standard
* serialization of `Experimental_StreamingSpeechModelV4.doStream` over a WebSocket. Clients (e.g.
* the `@ai-sdk/gateway` provider) encode with this module and servers (e.g.
* AI Gateway) decode with it, so the two sides cannot drift.
*
* Envelope rules:
*
* 1. The client sends exactly one `speech-stream.start` TEXT frame first.
* 2. Input text rides `speech-stream.text-delta` TEXT frames, in order. A
* frame may carry its text part's `voice` and `providerOptions`. A long
* part may span several frames: `voice` is repeated on each, and
* `providerOptions` appear on the first only, so they must describe an
* event at the start of the part (e.g. a turn break), not a setting. An
* empty `delta` is valid and carries only part metadata; it does not end
* the input.
* 3. The client signals the end of the input text with the
* `speech-stream.text-done` TEXT frame; a plain close without it is an
* abort.
* 4. Every server→client TEXT frame is one JSON-serialized
* `SpeechModelV4StreamPart` (flattened, no wrapper). `audio` payloads are
* base64 strings. `Date` values (`response-metadata.timestamp`) serialize
* to ISO 8601 strings and are revived by `parseSpeechStreamPart`. `Error`
* payloads in `error` parts serialize as `{ name, message }`; servers may
* instead send `{ type, message }`, where `type` names an error class the
* client can map to a typed error (e.g. `invalid_request_error`).
* 5. The server closes with code 1000 after the `finish` part. Over this
* envelope an `error` part is terminal: the server sends it on failure and
* then closes non-1000. A close without a prior `finish` is an error.
* 6. Unknown frame/part types are ignored in both directions (forward
* compatibility).
* 7. Servers may enforce a maximum frame size (the AI Gateway rejects frames
* over 256 KiB); clients should keep text-delta frames under 64 KiB.
* 8. Connection establishment (URL, auth) is transport-specific and out of
* scope.
*
* The envelope validates frame shape only; server policy layers on top. Both
* parsers use `secureJsonParse`, so frames carrying `__proto__` /
* `constructor.prototype` keys are rejected rather than parsed.
*/
/** Type of the first client TEXT frame. */
const SPEECH_STREAM_START_FRAME_TYPE = "speech-stream.start";
/** Type of the client TEXT frame that carries one input text delta. */
const SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE = "speech-stream.text-delta";
/** Type of the client TEXT frame that signals the end of the input text. */
const SPEECH_STREAM_TEXT_DONE_FRAME_TYPE = "speech-stream.text-done";
const optionalStartFrameFields = {
instructions: "string",
language: "string",
speed: "number",
voice: "string",
includeRawChunks: "boolean"
};
/**
* Server-side: parse a client TEXT frame. Validates envelope shape only and
* rejects prototype-pollution payloads. Never throws.
*/
function parseSpeechStreamClientFrame(text) {
let value;
try {
value = secureJsonParse(text);
} catch {
return {
type: "invalid",
message: "malformed JSON"
};
}
if (!isRecord(value)) return {
type: "invalid",
message: "frame must be a JSON object"
};
if (typeof value.type !== "string") return {
type: "invalid",
message: "frame type must be a string"
};
switch (value.type) {
case SPEECH_STREAM_START_FRAME_TYPE: {
for (const [field, type] of Object.entries(optionalStartFrameFields)) if (value[field] !== void 0 && typeof value[field] !== type) return {
type: "invalid",
message: `${field} must be a ${type} when present`
};
if (value.outputFormat !== void 0 && !isAudioFormat(value.outputFormat)) return {
type: "invalid",
message: "outputFormat must be an audio format object ({ type, rate? }) when present"
};
if (value.providerOptions !== void 0 && !isProviderOptions(value.providerOptions)) return {
type: "invalid",
message: "providerOptions must be an object of provider objects when present"
};
const frame = { type: SPEECH_STREAM_START_FRAME_TYPE };
for (const field of Object.keys(optionalStartFrameFields)) if (value[field] !== void 0) frame[field] = value[field];
if (value.outputFormat !== void 0) {
const { type, rate } = value.outputFormat;
frame.outputFormat = {
type,
...rate !== void 0 && { rate }
};
}
if (value.providerOptions !== void 0) frame.providerOptions = value.providerOptions;
return {
type: "start",
frame
};
}
case SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE:
if (typeof value.delta !== "string") return {
type: "invalid",
message: "text-delta frame must have a string delta"
};
if (value.voice !== void 0 && typeof value.voice !== "string") return {
type: "invalid",
message: "text-delta voice must be a string when present"
};
if (value.providerOptions !== void 0 && !isProviderOptions(value.providerOptions)) return {
type: "invalid",
message: "text-delta providerOptions must be an object of provider objects when present"
};
return {
type: "text-delta",
delta: value.delta,
...value.voice !== void 0 && { voice: value.voice },
...value.providerOptions !== void 0 && { providerOptions: value.providerOptions }
};
case SPEECH_STREAM_TEXT_DONE_FRAME_TYPE: return { type: "text-done" };
default: return { type: "unknown" };
}
}
/**
* Server-side: serialize a speech stream part as one TEXT frame. Binary audio
* is base64-encoded and `Error` payloads serialize as `{ name, message }`.
* Returns `undefined` for payloads that are not JSON-serializable; callers
* drop the frame.
*/
function serializeSpeechStreamPart(part) {
try {
if (part.type === "audio" && part.audio instanceof Uint8Array) return JSON.stringify({
...part,
audio: convertUint8ArrayToBase64(part.audio)
});
if (part.type === "error" && isError$1(part.error)) return JSON.stringify({
...part,
error: {
name: part.error.name,
message: part.error.message
}
});
return JSON.stringify(part);
} catch {
return;
}
}
function isError$1(value) {
return value instanceof Error || Object.prototype.toString.call(value) === "[object Error]";
}
/**
* Client-side: parse a server TEXT frame into a speech stream part. Returns
* `undefined` for malformed or unsafe JSON, unknown part types, and known
* part types whose fields are missing or mistyped. Revives
* `response-metadata.timestamp` to a `Date`.
*/
function parseSpeechStreamPart(text) {
let value;
try {
value = secureJsonParse(text);
} catch {
return;
}
if (!isRecord(value)) return;
const part = value;
switch (part.type) {
case "stream-start": {
if (!Array.isArray(part.warnings)) return;
const warnings = part.warnings.filter((warning) => !isUnknownWarningType(warning));
return warnings.every(isWarning$1) ? {
...part,
warnings
} : void 0;
}
case "audio": return isString$1(part.audio) && isAudioFormat(part.audioFormat) && isOptional$1(part.id, isString$1) && isOptional$1(part.providerMetadata, isRecord) ? part : void 0;
case "finish": return isOptional$1(part.usage, isUsage) && isOptional$1(part.providerMetadata, isRecord) ? part : void 0;
case "response-metadata": {
if (!(isOptional$1(part.modelId, isString$1) && isOptional$1(part.headers, isRecord))) return;
const timestamp = part.timestamp;
if (timestamp == null) return {
...part,
timestamp: void 0
};
if (typeof timestamp !== "string") return;
const revived = new Date(timestamp);
return Number.isNaN(revived.getTime()) ? void 0 : {
...part,
timestamp: revived
};
}
case "raw": return "rawValue" in part ? part : void 0;
case "error": return "error" in part ? part : void 0;
default: return;
}
}
function isString$1(value) {
return typeof value === "string";
}
function isOptional$1(value, check) {
return value === void 0 || check(value);
}
const knownWarningTypes = /* @__PURE__ */ new Set([
"compatibility",
"deprecated",
"other",
"unsupported"
]);
function isUnknownWarningType(value) {
return isRecord(value) && isString$1(value.type) && !knownWarningTypes.has(value.type);
}
function isWarning$1(value) {
if (!isRecord(value)) return false;
switch (value.type) {
case "compatibility":
case "unsupported": return isString$1(value.feature) && isOptional$1(value.details, isString$1);
case "deprecated": return isString$1(value.setting) && isString$1(value.message);
case "other": return isString$1(value.message);
default: return false;
}
}
function isProviderOptions(value) {
return isRecord(value) && Object.values(value).every(isRecord);
}
function isAudioFormat(value) {
return isRecord(value) && isString$1(value.type) && value.type.length > 0 && isOptional$1(value.rate, (rate) => typeof rate === "number" && Number.isFinite(rate) && rate > 0);
}
function isUsage(value) {
return isRecord(value) && [
"inputCharacters",
"inputTokens",
"outputTokens"
].every((key) => isOptional$1(value[key], (count) => typeof count === "number"));
}
//#endregion
//#region src/streaming-tool-call-argument-state.ts
function startsWithStructuredValue(value) {
if (typeof value !== "string") return false;
const firstCharacter = value.trimStart()[0];
return firstCharacter === "{" || firstCharacter === "[";
}
/**
* Incrementally tracks whether streamed tool-call arguments contain a complete
* structured JSON value. This is intentionally structural rather than a JSON
* parse: a currently parsable scalar can still be the prefix of a later value.
*/
var StreamingToolCallArgumentState = class {
constructor(initialValue = "") {
this.structure = { kind: "undetermined" };
this.append(initialValue);
}
get hasCompleteStructuredValue() {
return this.structure.kind === "structured" && this.structure.complete === true;
}
append(delta) {
let nextStructure = this.structure;
for (const character of delta) {
if (nextStructure.kind === "undetermined") {
if (/\s/.test(character)) continue;
if (character !== "{" && character !== "[") {
nextStructure = { kind: "other" };
continue;
}
nextStructure = {
kind: "structured",
stack: [character],
inString: false,
escaped: false,
complete: false
};
continue;
}
if (nextStructure.kind !== "structured" || nextStructure.complete) continue;
if (nextStructure.inString) {
if (nextStructure.escaped) nextStructure.escaped = false;
else if (character === "\\") nextStructure.escaped = true;
else if (character === "\"") nextStructure.inString = false;
continue;
}
if (character === "\"") nextStructure.inString = true;
else if (character === "{" || character === "[") nextStructure.stack.push(character);
else if (character === "}" || character === "]") {
const expectedOpening = character === "}" ? "{" : "[";
if (nextStructure.stack.at(-1) !== expectedOpening) {
nextStructure = { kind: "other" };
continue;
}
nextStructure.stack.pop();
if (nextStructure.stack.length === 0) nextStructure.complete = true;
}
}
this.structure = nextStructure;
}
};
//#endregion
//#region src/streaming-tool-call-tracker.ts
/**
* Tracks streaming tool call state across multiple deltas from an
* OpenAI-compatible chat completion stream. Handles argument accumulation,
* emits tool-input-start/delta/end and tool-call events, and finalizes
* unfinished tool calls on flush.
*
* Used by openai, openai-compatible, groq, deepseek, alibaba, mistral, and
* moonshotai providers.
*/
var StreamingToolCallTracker = class {
constructor(controller, options = {}) {
this.toolCalls = [];
this.toolCallsById = /* @__PURE__ */ new Map();
this.toolCallsByIndex = /* @__PURE__ */ new Map();
this.usedToolCallIds = /* @__PURE__ */ new Set();
this.nextGeneratedIdSuffixes = /* @__PURE__ */ new Map();
this.controller = controller;
this._generateId = options.generateId ?? generateId;
this.typeValidation = options.typeValidation ?? "none";
this.extractMetadata = options.extractMetadata;
this.buildToolCallProviderMetadata = options.buildToolCallProviderMetadata;
}
/**
* Process a tool call delta from a streaming response chunk.
* Emits tool-input-start, tool-input-delta, tool-input-end, and tool-call
* events as appropriate.
*/
processDelta(toolCallDelta) {
const wireName = toolCallDelta.function?.name;
const hasBlankName = typeof wireName === "string" && wireName.trim().length === 0;
const wireId = this.getNonBlankString(toolCallDelta.id);
const name = this.getNonBlankString(wireName);
const argumentsDelta = toolCallDelta.function?.arguments;
const { index } = toolCallDelta;
const resolution = this.resolveToolCall({
wireId,
index,
name,
hasExplicitCallStart: name != null && startsWithStructuredValue(argumentsDelta),
hasEmptyArguments: argumentsDelta == null || argumentsDelta.trim().length === 0
});
if (resolution.kind === "ambiguous") return;
let toolCall;
if (resolution.kind === "new") {
if (hasBlankName) return;
toolCall = this.processNewToolCall(toolCallDelta, {
wireId,
index,
name
});
} else {
toolCall = resolution.toolCall;
if (wireId != null) this.associateWireId(toolCall, wireId);
this.processExistingToolCall(toolCall, toolCallDelta);
}
if (index != null) this.associateIndex(toolCall, index);
}
/**
* Finalize any unfinished tool calls. Should be called during the stream's
* flush handler to ensure all tool calls are properly completed.
*/
flush() {
const toolCalls = this.toolCalls.every((toolCall) => toolCall.index != null) ? [...this.toolCalls].sort((a, b) => a.index - b.index || a.sequence - b.sequence) : this.toolCalls;
for (const toolCall of toolCalls) if (!toolCall.hasFinished) this.finishToolCall(toolCall);
}
/**
* Correlation precedence for streamed deltas:
*
* | ID evidence | index/name evidence | start evidence | resolution |
* | --- | --- | --- | --- |
* | known | matching | any | matching call, new call, or ambiguity |
* | known | conflicting | named | new call |
* | unseen | matching | structured start | new call |
* | unseen | matching | named empty arguments | incomplete call, new call, or ambiguity |
* | unseen | matching | continuation | matching call or ambiguity |
* | absent | matching | any | matching call, new call, or ambiguity |
* | absent | absent | named | new call |
* | absent | absent | unnamed | sole unfinished call, new call, or ambiguity |
*/
resolveToolCall({ wireId, index, name, hasExplicitCallStart, hasEmptyArguments }) {
const indexedToolCalls = index != null ? this.toolCallsByIndex.get(index) : void 0;
const matchingIndexedToolCalls = this.filterToolCallsByName(indexedToolCalls, name);
if (wireId != null) {
const toolCallsWithId = this.toolCallsById.get(wireId);
if (toolCallsWithId != null) {
if (index != null) {
const matchingToolCalls = matchingIndexedToolCalls.filter((toolCall) => toolCallsWithId.has(toolCall));
const matchingToolCall = this.resolveMatchingToolCall(matchingToolCalls, hasExplicitCallStart);
if (matchingToolCall.kind !== "new") return matchingToolCall;
if (name != null) return { kind: "new" };
if (indexedToolCalls != null) return { kind: "ambiguous" };
return this.resolveMatchingToolCall([...toolCallsWithId], false);
}
if (name != null) {
const matchingToolCalls = [...toolCallsWithId].filter((toolCall) => toolCall.function.name === name);
return this.resolveMatchingToolCall(matchingToolCalls, hasExplicitCallStart);
}
return this.resolveMatchingToolCall([...toolCallsWithId], false);
}
if (matchingIndexedToolCalls.length > 0) return hasExplicitCallStart ? { kind: "new" } : this.resolveMatchingToolCall(matchingIndexedToolCalls, name != null && hasEmptyArguments);
return { kind: "new" };
}
if (indexedToolCalls != null) return this.resolveMatchingToolCall(matchingIndexedToolCalls, hasExplicitCallStart);
if (name != null) return { kind: "new" };
const unfinishedToolCalls = this.toolCalls.filter((toolCall) => !toolCall.hasFinished);
return this.resolveMatchingToolCall(unfinishedToolCalls, false);
}
filterToolCallsByName(toolCalls, name) {
if (toolCalls == null) return [];
return [...toolCalls].filter((toolCall) => name == null || toolCall.function.name === name);
}
resolveMatchingToolCall(toolCalls, hasExplicitCallStart) {
if (toolCalls.length === 0) return { kind: "new" };
const continuableToolCalls = toolCalls.filter((toolCall) => !toolCall.argumentState.hasCompleteStructuredValue);
if (!hasExplicitCallStart && toolCalls.length === 1) return {
kind: "existing",
toolCall: toolCalls[0]
};
if (continuableToolCalls.length === 1) return {
kind: "existing",
toolCall: continuableToolCalls[0]
};
if (continuableToolCalls.length > 1 || !hasExplicitCallStart) return { kind: "ambiguous" };
return { kind: "new" };
}
processNewToolCall(toolCallDelta, { wireId, index, name }) {
if (this.typeValidation === "required") {
if (toolCallDelta.type !== "function") throw new InvalidResponseDataError({
data: toolCallDelta,
message: `Expected 'function' type.`
});
} else if (this.typeValidation === "if-present") {
if (toolCallDelta.type != null && toolCallDelta.type !== "function") throw new InvalidResponseDataError({
data: toolCallDelta,
message: `Expected 'function' type.`
});
}
if (name == null) throw new InvalidResponseDataError({
data: toolCallDelta,
message: `Expected 'function.name' to be a string.`
});
const id = this.createToolCallId(wireId);
this.controller.enqueue({
type: "tool-input-start",
id,
toolName: name
});
const metadata = this.extractMetadata?.(toolCallDelta);
const initialArguments = toolCallDelta.function?.arguments ?? "";
const toolCall = {
id,
index: index ?? void 0,
sequence: this.toolCalls.length,
type: "function",
function: {
name,
arguments: initialArguments
},
argumentState: new StreamingToolCallArgumentState(initialArguments),
hasFinished: false,
metadata
};
this.toolCalls.push(toolCall);
if (wireId != null) this.associateWireId(toolCall, wireId);
if (toolCall.function.arguments.length > 0) this.controller.enqueue({
type: "tool-input-delta",
id: toolCall.id,
delta: toolCall.function.arguments
});
return toolCall;
}
associateWireId(toolCall, wireId) {
let toolCallsWithId = this.toolCallsById.get(wireId);
if (toolCallsWithId == null) {
toolCallsWithId = /* @__PURE__ */ new Set();
this.toolCallsById.set(wireId, toolCallsWithId);
}
toolCallsWithId.add(toolCall);
}
associateIndex(toolCall, index) {
let toolCallsWithIndex = this.toolCallsByIndex.get(index);
if (toolCallsWithIndex == null) {
toolCallsWithIndex = /* @__PURE__ */ new Set();
this.toolCallsByIndex.set(index, toolCallsWithIndex);
}
toolCallsWithIndex.add(toolCall);
}
createToolCallId(wireId) {
if (wireId != null && !this.usedToolCallIds.has(wireId)) {
this.usedToolCallIds.add(wireId);
return wireId;
}
const generatedId = this.getNonBlankString(this._generateId()) ?? "tool-call";
if (!this.usedToolCallIds.has(generatedId)) {
this.usedToolCallIds.add(generatedId);
return generatedId;
}
const initialSuffix = this.nextGeneratedIdSuffixes.get(generatedId) ?? 1;
const maximumSuffix = initialSuffix + this.usedToolCallIds.size;
for (let suffix = initialSuffix; suffix <= maximumSuffix; suffix++) {
const suffixedId = `${generatedId}-${suffix}`;
if (!this.usedToolCallIds.has(suffixedId)) {
this.usedToolCallIds.add(suffixedId);
this.nextGeneratedIdSuffixes.set(generatedId, suffix + 1);
return suffixedId;
}
}
throw new Error("Failed to create a unique tool call ID.");
}
getNonBlankString(value) {
return value != null && value.trim().length > 0 ? value : void 0;
}
processExistingToolCall(toolCall, toolCallDelta) {
if (toolCall.hasFinished) return;
if (toolCallDelta.function?.arguments != null) {
toolCall.argumentState.append(toolCallDelta.function.arguments);
toolCall.function.arguments += toolCallDelta.function.arguments;
this.controller.enqueue({
type: "tool-input-delta",
id: toolCall.id,
delta: toolCallDelta.function.arguments
});
}
}
finishToolCall(toolCall) {
this.controller.enqueue({
type: "tool-input-end",
id: toolCall.id
});
const providerMetadata = this.buildToolCallProviderMetadata?.(toolCall.metadata);
this.controller.enqueue({
type: "tool-call",
toolCallId: toolCall.id,
toolName: toolCall.function.name,
input: toolCall.function.arguments,
...providerMetadata ? { providerMetadata } : {}
});
toolCall.hasFinished = true;
}
};
//#endregion
//#region src/strip-file-extension.ts
/**
* Strips file extension segments from a filename.
*
* Examples:
* - "report.pdf" -> "report"
* - "archive.tar.gz" -> "archive"
* - "filename" -> "filename"
*/
function stripFileExtension(filename) {
const firstDotIndex = filename.indexOf(".");
return firstDotIndex === -1 ? filename : filename.slice(0, firstDotIndex);
}
//#endregion
//#region src/transcription-stream-envelope.ts
/**
* Experimental transcription-stream WebSocket envelope (v1): the standard
* serialization of `TranscriptionModelV4.doStream` over a WebSocket. Clients
* (e.g. the `@ai-sdk/gateway` provider) encode with this module and servers
* (e.g. AI Gateway) decode with it, so the two sides cannot drift.
*
* Envelope rules:
*
* 1. The client sends exactly one `transcription-stream.start` TEXT frame
* first.
* 2. Audio rides BINARY frames containing raw bytes in the declared
* `inputAudioFormat` (base64 string chunks are decoded before sending).
* 3. The client signals end of audio with the
* `transcription-stream.audio-done` TEXT frame; a plain close without it
* is an abort.
* 4. Every server→client TEXT frame is one JSON-serialized
* `TranscriptionModelV4StreamPart` (flattened, no wrapper). Payloads must
* be JSON-serializable. `Date` values (`response-metadata.timestamp`)
* serialize to ISO 8601 strings and are revived by
* `parseTranscriptionStreamPart`. `Error` payloads in `error` parts
* serialize as `{ name, message }`.
* 5. The server closes with code 1000 after the `finish` part; on failure it
* sends an `error` part and closes non-1000. A close without a prior
* `finish` is an error.
* 6. Unknown frame/part types are ignored in both directions (forward
* compatibility).
* 7. Servers may enforce a maximum frame size (the AI Gateway rejects frames
* over 256 KiB); clients should split audio into frames of at most
* 64 KiB.
* 8. Connection establishment (URL, auth) is transport-specific and out of
* scope.
*
* The envelope validates frame shape only; server policy (accepted audio
* formats, required `rate`, size limits) layers on top. Both parsers use
* `secureJsonParse`, so frames carrying `__proto__` / `constructor.prototype`
* keys are rejected (prototype-pollution protection) rather than parsed.
*/
/** Type of the first client TEXT frame. */
const TRANSCRIPTION_STREAM_START_FRAME_TYPE = "transcription-stream.start";
/** Type of the client TEXT frame that signals the end of the audio input. */
const TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE = "transcription-stream.audio-done";
/**
* Server-side: parse a client TEXT frame. Validates envelope shape only and
* rejects prototype-pollution payloads (parsed with `secureJsonParse`). Never
* throws.
*/
function parseTranscriptionStreamClientFrame(text) {
let value;
try {
value = secureJsonParse(text);
} catch {
return {
type: "invalid",
message: "malformed JSON"
};
}
if (value == null || typeof value !== "object" || Array.isArray(value)) return {
type: "invalid",
message: "frame must be a JSON object"
};
const frame = value;
if (typeof frame.type !== "string") return {
type: "invalid",
message: "frame type must be a string"
};
switch (frame.type) {
case TRANSCRIPTION_STREAM_START_FRAME_TYPE: {
const inputAudioFormat = frame.inputAudioFormat;
if (inputAudioFormat == null || typeof inputAudioFormat !== "object" || Array.isArray(inputAudioFormat) || typeof inputAudioFormat.type !== "string") return {
type: "invalid",
message: "start frame must have an inputAudioFormat object with a string type"
};
if (inputAudioFormat.rate !== void 0 && typeof inputAudioFormat.rate !== "number") return {
type: "invalid",
message: "inputAudioFormat.rate must be a number when present"
};
if (frame.providerOptions !== void 0 && (frame.providerOptions == null || typeof frame.providerOptions !== "object" || Array.isArray(frame.providerOptions))) return {
type: "invalid",
message: "providerOptions must be an object when present"
};
if (frame.includeRawChunks !== void 0 && typeof frame.includeRawChunks !== "boolean") return {
type: "invalid",
message: "includeRawChunks must be a boolean when present"
};
return {
type: "start",
frame
};
}
case TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE: return { type: "audio-done" };
default: return { type: "unknown" };
}
}
/**
* Server-side: serialize a transcription stream part as one TEXT frame.
* `Error` payloads in `error` parts serialize as `{ name, message }` —
* `Error` properties are non-enumerable, so a plain `JSON.stringify` would
* serialize them to `{}` and lose the message end-to-end. Returns
* `undefined` for payloads that are not JSON-serializable (envelope rule 4,
* e.g. bigint or cyclic values); callers drop the frame.
*/
function serializeTranscriptionStreamPart(part) {
try {
if (part.type === "error" && isError(part.error)) return JSON.stringify({
...part,
error: {
name: part.error.name,
message: part.error.message
}
});
return JSON.stringify(part);
} catch {
return;
}
}
function isError(value) {
return value instanceof Error || Object.prototype.toString.call(value) === "[object Error]";
}
/**
* Client-side: parse a server TEXT frame into a transcription stream part.
* Returns `undefined` for malformed or unsafe (prototype-polluting) JSON
* (parsed with `secureJsonParse`), unknown part types, and known part types
* whose required or optional fields are missing or mistyped (including
* warning and segment elements) — downstream SDK code dereferences those
* fields, so a drifted server must not crash or pollute the stream.
* Revives `response-metadata.timestamp` to a `Date`.
*/
function parseTranscriptionStreamPart(text) {
let value;
try {
value = secureJsonParse(text);
} catch {
return;
}
if (value == null || typeof value !== "object" || Array.isArray(value)) return;
const part = value;
switch (part.type) {
case "stream-start": return Array.isArray(part.warnings) && part.warnings.every(isWarning) ? part : void 0;
case "transcript-delta": return isString(part.delta) && isOptional(part.id, isString) && isOptional(part.providerMetadata, isRecord) ? part : void 0;
case "transcript-partial": return isString(part.text) && isOptional(part.id, isString) && isOptional(part.startSecond, isNumber) && isOptional(part.durationInSeconds, isNumber) && isOptional(part.channelIndex, isNumber) && isOptional(part.providerMetadata, isRecord) ? part : void 0;
case "transcript-final": return isString(part.text) && isOptional(part.id, isString) && isOptional(part.startSecond, isNumber) && isOptional(part.endSecond, isNumber) && isOptional(part.channelIndex, isNumber) && isOptional(part.providerMetadata, isRecord) ? part : void 0;
case "finish": return isString(part.text) && Array.isArray(part.segments) && part.segments.every(isSegment) && isOptional(part.language, isString) && isOptional(part.durationInSeconds, isNumber) && isOptional(part.usage, isRecord) && isOptional(part.providerMetadata, isRecord) ? part : void 0;
case "response-metadata": {
if (!(isOptional(part.modelId, isString) && isOptional(part.headers, isRecord))) return;
const timestamp = part.timestamp;
if (timestamp == null) return {
...part,
timestamp: void 0
};
if (typeof timestamp !== "string") return;
const revived = new Date(timestamp);
return Number.isNaN(revived.getTime()) ? void 0 : {
...part,
timestamp: revived
};
}
case "raw": return "rawValue" in part ? part : void 0;
case "error": return "error" in part ? part : void 0;
default: return;
}
}
function isString(value) {
return typeof value === "string";
}
function isNumber(value) {
return typeof value === "number";
}
function isOptional(value, check) {
return value === void 0 || check(value);
}
function isWarning(value) {
return isRecord(value) && isString(value.type);
}
function isSegment(value) {
return isRecord(value) && isString(value.text) && isNumber(value.startSecond) && isNumber(value.endSecond);
}
//#endregion
//#region src/validate-base-url.ts
function validateBaseURL(baseURL) {
if (baseURL?.trim() === "") throw new InvalidArgumentError({
argument: "baseURL",
message: "baseURL must be a non-empty string."
});
return baseURL;
}
//#endregion
//#region src/without-trailing-slash.ts
function withoutTrailingSlash(url) {
return url?.replace(/\/$/, "");
}
//#endregion
//#region src/types/executable-tool.ts
/**
* Checks whether a tool exposes an execute function.
*/
function isExecutableTool(tool) {
return tool != null && typeof tool.execute === "function";
}
//#endregion
//#region src/is-async-iterable.ts
function isAsyncIterable(obj) {
return obj != null && typeof obj[Symbol.asyncIterator] === "function";
}
//#endregion
//#region src/types/execute-tool.ts
/**
* Executes a tool function and normalizes its results into a stream of outputs.
*
* - If the tool's `execute` function returns an `AsyncIterable`, each yielded value is emitted as
* `{ type: "preliminary", output }`. After iteration completes, the last yielded value is emitted
* again as `{ type: "final", output }`.
* - If the tool returns a direct value or Promise, a single `{ type: "final", output }` is yielded.
*
* @param params.tool The tool whose `execute` function should be invoked.
* @param params.input The input value to pass to the tool.
* @param params.options Additional options for tool execution.
* @yields A preliminary output for each streamed value, followed by a final output, or a single final
* output for non-streaming tools.
*/
async function* executeTool({ tool, input, options }) {
const result = tool.execute(input, options);
if (isAsyncIterable(result)) {
let lastOutput;
for await (const output of result) {
lastOutput = output;
yield {
type: "preliminary",
output
};
}
yield {
type: "final",
output: lastOutput
};
} else yield {
type: "final",
output: await result
};
}
//#endregion
//#region src/types/tool-caller.ts
function toolCaller(tool, definition) {
return Object.defineProperty({ ...tool }, "experimental_toolCaller", { value: definition });
}
function getToolCaller(tool) {
return tool?.experimental_toolCaller;
}
//#endregion
//#region src/stringify-with-bigint-and-cycles.ts
/**
* Serializes a value as JSON, representing BigInt values as decimal strings and
* ancestor cycles as "[Circular]". Reused objects, toJSON methods, and other JSON
* serialization behavior are preserved. Getters and toJSON methods may throw.
*/
function stringifyWithBigIntAndCycles(value) {
const ancestors = [];
return JSON.stringify(value, function(_key, value) {
if (typeof value === "bigint") return value.toString();
if (typeof value !== "object" || value === null) return value;
while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop();
if (ancestors.includes(value)) return "[Circular]";
ancestors.push(value);
return value;
});
}
//#endregion
export { DEFAULT_MAX_DOWNLOAD_SIZE, DelayedPromise, DownloadError, EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL as EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL, EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER as EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER, SPEECH_STREAM_START_FRAME_TYPE as EXPERIMENTAL_SPEECH_STREAM_START_FRAME_TYPE, SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE as EXPERIMENTAL_SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE, SPEECH_STREAM_TEXT_DONE_FRAME_TYPE as EXPERIMENTAL_SPEECH_STREAM_TEXT_DONE_FRAME_TYPE, TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE as EXPERIMENTAL_TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE, TRANSCRIPTION_STREAM_START_FRAME_TYPE as EXPERIMENTAL_TRANSCRIPTION_STREAM_START_FRAME_TYPE, EventSourceParserStream, SerializationError, StreamingToolCallTracker, VERSION, WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, asArray, asSchema, cancelResponseBody, combineHeaders, connectToWebSocket, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertImageModelFileToDataUri, convertInlineFileDataToUint8Array, convertJsonResponseToolStream, convertToBase64, convertToFormData, convertUint8ArrayToBase64, createBinaryResponseHandler, createBinaryStreamResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonLinesResponseHandler, createJsonResponseHandler, createLanguageModelResponseMetadata, createNullLanguageModelUsage, createProviderDefinedToolFactory, createProviderDefinedToolFactoryWithOutputSchema, createProviderExecutedToolFactory, createProviderStreamError, createStatusCodeErrorResponseHandler, createToolNameMapping, delay, deleteFromApi, detectMediaType, downloadBlob, dynamicTool, encodePathSegment, executeTool, getToolCaller as experimental_getToolCaller, parseSpeechStreamClientFrame as experimental_parseSpeechStreamClientFrame, parseSpeechStreamPart as experimental_parseSpeechStreamPart, parseTranscriptionStreamClientFrame as experimental_parseTranscriptionStreamClientFrame, parseTranscriptionStreamPart as experimental_parseTranscriptionStreamPart, serializeSpeechStreamPart as experimental_serializeSpeechStreamPart, serializeTranscriptionStreamPart as experimental_serializeTranscriptionStreamPart, toolCaller as experimental_toolCaller, extractLines, extractResponseHeaders, fetchUntrustedUrl, fetchWithValidatedEndpoint, fetchWithValidatedRedirects, filterNullable, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, getTopLevelMediaType, getWebSocketConstructor, injectJsonInstructionIntoMessages, isAbortError, isArrayBuffer, isBrowserRuntime, isBuffer, isCustomReasoning, isExecutableTool, isFullMediaType, isNonNullable, isParsableJson, isProviderReference, isProviderStreamError, isRecord, isSameOrigin, isUint8Array, isUrlSupported, isValidHostnamePart, jsonSchema, lazySchema, loadApiKey, loadOptionalSetting, loadSetting, mapReasoningToProviderBudget, mapReasoningToProviderEffort, mediaTypeToExtension, normalizeBatchRequestCounts, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postMultipartStreamToApi, postToApi, readResponseWithSizeLimit, readWebSocketMessageText, redactTelemetryValue, removeUndefinedEntries, resolve, resolveFullMediaType, resolveProviderOptions, resolveProviderReference, resolveWith, retryWithExponentialBackoff, safeParseJSON, safeValidateTypes, secureJsonParse, serializeModelOptions, stringifyWithBigIntAndCycles, stripFileExtension, toWebSocketUrl, tool, validateBaseURL, validateDownloadUrl, validateTypes, waitForWebSocketBufferDrain, withTelemetryRedaction, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
//# sourceMappingURL=index.js.map