UNPKG

ai

Version:

Vercel AI SDK - The AI Toolkit for TypeScript and JavaScript

1 lines • 158 kB
{"version":3,"sources":["../ai-state.tsx","../../util/create-resolvable-promise.ts","../../util/is-function.ts","../constants.ts","../create-suspended-chunk.tsx","../streamable.tsx","../render.ts","../../util/retry-with-exponential-backoff.ts","../../util/delay.ts","../../util/retry-error.ts","../../core/prompt/convert-to-language-model-prompt.ts","../../util/download-error.ts","../../util/download.ts","../../core/util/detect-image-mimetype.ts","../../core/prompt/data-content.ts","../../core/prompt/invalid-data-content-error.ts","../../core/prompt/invalid-message-role-error.ts","../../core/prompt/get-validated-prompt.ts","../../errors/invalid-argument-error.ts","../../core/prompt/prepare-call-settings.ts","../../core/types/token-usage.ts","../../core/util/schema.ts","../../core/util/is-non-empty-object.ts","../../core/prompt/prepare-tools-and-tool-choice.ts","../../errors/invalid-tool-arguments-error.ts","../../errors/no-such-tool-error.ts","../../streams/ai-stream.ts","../../streams/stream-data.ts","../../streams/openai-stream.ts","../../util/consume-stream.ts","../stream-ui/stream-ui.tsx","../../util/is-async-generator.ts","../../util/is-generator.ts","../provider.tsx"],"sourcesContent":["import * as jsondiffpatch from 'jsondiffpatch';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { createResolvablePromise } from '../util/create-resolvable-promise';\nimport { isFunction } from '../util/is-function';\nimport type {\n AIProvider,\n InferAIState,\n InternalAIStateStorageOptions,\n MutableAIState,\n ValueOrUpdater,\n} from './types';\n\n// It is possible that multiple AI requests get in concurrently, for different\n// AI instances. So ALS is necessary here for a simpler API.\nconst asyncAIStateStorage = new AsyncLocalStorage<{\n currentState: any;\n originalState: any;\n sealed: boolean;\n options: InternalAIStateStorageOptions;\n mutationDeltaPromise?: Promise<any>;\n mutationDeltaResolve?: (v: any) => void;\n}>();\n\nfunction getAIStateStoreOrThrow(message: string) {\n const store = asyncAIStateStorage.getStore();\n if (!store) {\n throw new Error(message);\n }\n return store;\n}\n\nexport function withAIState<S, T>(\n { state, options }: { state: S; options: InternalAIStateStorageOptions },\n fn: () => T,\n): T {\n return asyncAIStateStorage.run(\n {\n currentState: state,\n originalState: state,\n sealed: false,\n options,\n },\n fn,\n );\n}\n\nexport function getAIStateDeltaPromise() {\n const store = getAIStateStoreOrThrow('Internal error occurred.');\n return store.mutationDeltaPromise;\n}\n\n// Internal method. This will be called after the AI Action has been returned\n// and you can no longer call `getMutableAIState()` inside any async callbacks\n// created by that Action.\nexport function sealMutableAIState() {\n const store = getAIStateStoreOrThrow('Internal error occurred.');\n store.sealed = true;\n}\n\n/**\n * Get the current AI state.\n * If `key` is provided, it will return the value of the specified key in the\n * AI state, if it's an object. If it's not an object, it will throw an error.\n *\n * @example const state = getAIState() // Get the entire AI state\n * @example const field = getAIState('key') // Get the value of the key\n */\nfunction getAIState<AI extends AIProvider = any>(): Readonly<\n InferAIState<AI, any>\n>;\nfunction getAIState<AI extends AIProvider = any>(\n key: keyof InferAIState<AI, any>,\n): Readonly<InferAIState<AI, any>[typeof key]>;\nfunction getAIState<AI extends AIProvider = any>(\n ...args: [] | [key: keyof InferAIState<AI, any>]\n) {\n const store = getAIStateStoreOrThrow(\n '`getAIState` must be called within an AI Action.',\n );\n\n if (args.length > 0) {\n const key = args[0];\n if (typeof store.currentState !== 'object') {\n throw new Error(\n `You can't get the \"${String(\n key,\n )}\" field from the AI state because it's not an object.`,\n );\n }\n return store.currentState[key as keyof typeof store.currentState];\n }\n\n return store.currentState;\n}\n\n/**\n * Get the mutable AI state. Note that you must call `.done()` when finishing\n * updating the AI state.\n *\n * @example\n * ```tsx\n * const state = getMutableAIState()\n * state.update({ ...state.get(), key: 'value' })\n * state.update((currentState) => ({ ...currentState, key: 'value' }))\n * state.done()\n * ```\n *\n * @example\n * ```tsx\n * const state = getMutableAIState()\n * state.done({ ...state.get(), key: 'value' }) // Done with a new state\n * ```\n */\nfunction getMutableAIState<AI extends AIProvider = any>(): MutableAIState<\n InferAIState<AI, any>\n>;\nfunction getMutableAIState<AI extends AIProvider = any>(\n key: keyof InferAIState<AI, any>,\n): MutableAIState<InferAIState<AI, any>[typeof key]>;\nfunction getMutableAIState<AI extends AIProvider = any>(\n ...args: [] | [key: keyof InferAIState<AI, any>]\n) {\n type AIState = InferAIState<AI, any>;\n type AIStateWithKey = typeof args extends [key: keyof AIState]\n ? AIState[(typeof args)[0]]\n : AIState;\n type NewStateOrUpdater = ValueOrUpdater<AIStateWithKey>;\n\n const store = getAIStateStoreOrThrow(\n '`getMutableAIState` must be called within an AI Action.',\n );\n\n if (store.sealed) {\n throw new Error(\n \"`getMutableAIState` must be called before returning from an AI Action. Please move it to the top level of the Action's function body.\",\n );\n }\n\n if (!store.mutationDeltaPromise) {\n const { promise, resolve } = createResolvablePromise();\n store.mutationDeltaPromise = promise;\n store.mutationDeltaResolve = resolve;\n }\n\n function doUpdate(newState: NewStateOrUpdater, done: boolean) {\n if (args.length > 0) {\n if (typeof store.currentState !== 'object') {\n const key = args[0];\n throw new Error(\n `You can't modify the \"${String(\n key,\n )}\" field of the AI state because it's not an object.`,\n );\n }\n }\n\n if (isFunction(newState)) {\n if (args.length > 0) {\n store.currentState[args[0]] = newState(store.currentState[args[0]]);\n } else {\n store.currentState = newState(store.currentState);\n }\n } else {\n if (args.length > 0) {\n store.currentState[args[0]] = newState;\n } else {\n store.currentState = newState;\n }\n }\n\n store.options.onSetAIState?.({\n key: args.length > 0 ? args[0] : undefined,\n state: store.currentState,\n done,\n });\n }\n\n const mutableState = {\n get: () => {\n if (args.length > 0) {\n const key = args[0];\n if (typeof store.currentState !== 'object') {\n throw new Error(\n `You can't get the \"${String(\n key,\n )}\" field from the AI state because it's not an object.`,\n );\n }\n return store.currentState[key] as Readonly<AIStateWithKey>;\n }\n\n return store.currentState as Readonly<AIState>;\n },\n update: function update(newAIState: NewStateOrUpdater) {\n doUpdate(newAIState, false);\n },\n done: function done(...doneArgs: [] | [NewStateOrUpdater]) {\n if (doneArgs.length > 0) {\n doUpdate(doneArgs[0] as NewStateOrUpdater, true);\n }\n\n const delta = jsondiffpatch.diff(store.originalState, store.currentState);\n store.mutationDeltaResolve!(delta);\n },\n };\n\n return mutableState;\n}\n\nexport { getAIState, getMutableAIState };\n","/**\n * Creates a Promise with externally accessible resolve and reject functions.\n *\n * @template T - The type of the value that the Promise will resolve to.\n * @returns An object containing:\n * - promise: A Promise that can be resolved or rejected externally.\n * - resolve: A function to resolve the Promise with a value of type T.\n * - reject: A function to reject the Promise with an error.\n */\nexport function createResolvablePromise<T = any>(): {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (error: unknown) => void;\n} {\n let resolve: (value: T) => void;\n let reject: (error: unknown) => void;\n\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n return {\n promise,\n resolve: resolve!,\n reject: reject!,\n };\n}\n","/**\n * Checks if the given value is a function.\n *\n * @param {unknown} value - The value to check.\n * @returns {boolean} True if the value is a function, false otherwise.\n */\nexport const isFunction = (value: unknown): value is Function =>\n typeof value === 'function';\n","export const STREAMABLE_VALUE_TYPE = Symbol.for('ui.streamable.value');\nexport const DEV_DEFAULT_STREAMABLE_WARNING_TIME = 15 * 1000;\n","import React, { Suspense } from 'react';\nimport { createResolvablePromise } from '../util/create-resolvable-promise';\n\n// Recursive type for the chunk.\ntype ChunkType =\n | {\n done: false;\n value: React.ReactNode;\n next: Promise<ChunkType>;\n append?: boolean;\n }\n | {\n done: true;\n value: React.ReactNode;\n };\n\n// Use single letter names for the variables to reduce the size of the RSC payload.\n// `R` for `Row`, `c` for `current`, `n` for `next`.\n// Note: Array construction is needed to access the name R.\nconst R = [\n (async ({\n c: current,\n n: next,\n }: {\n c: React.ReactNode;\n n: Promise<ChunkType>;\n }) => {\n const chunk = await next;\n\n if (chunk.done) {\n return chunk.value;\n }\n\n if (chunk.append) {\n return (\n <>\n {current}\n <Suspense fallback={chunk.value}>\n <R c={chunk.value} n={chunk.next} />\n </Suspense>\n </>\n );\n }\n\n return (\n <Suspense fallback={chunk.value}>\n <R c={chunk.value} n={chunk.next} />\n </Suspense>\n );\n }) as unknown as React.FC<{\n c: React.ReactNode;\n n: Promise<ChunkType>;\n }>,\n][0];\n\n/**\n * Creates a suspended chunk for React Server Components.\n *\n * This function generates a suspenseful React component that can be dynamically updated.\n * It's useful for streaming updates to the client in a React Server Components context.\n *\n * @param {React.ReactNode} initialValue - The initial value to render while the promise is pending.\n * @returns {Object} An object containing:\n * - row: A React node that renders the suspenseful content.\n * - resolve: A function to resolve the promise with a new value.\n * - reject: A function to reject the promise with an error.\n */\nexport function createSuspendedChunk(initialValue: React.ReactNode): {\n row: React.ReactNode;\n resolve: (value: ChunkType) => void;\n reject: (error: unknown) => void;\n} {\n const { promise, resolve, reject } = createResolvablePromise<ChunkType>();\n\n return {\n row: (\n <Suspense fallback={initialValue}>\n <R c={initialValue} n={promise} />\n </Suspense>\n ),\n resolve,\n reject,\n };\n}\n","import { createResolvablePromise } from '../util/create-resolvable-promise';\nimport {\n DEV_DEFAULT_STREAMABLE_WARNING_TIME,\n STREAMABLE_VALUE_TYPE,\n} from './constants';\nimport { createSuspendedChunk } from './create-suspended-chunk';\nimport type { StreamablePatch, StreamableValue } from './types';\n\n// It's necessary to define the type manually here, otherwise TypeScript compiler\n// will not be able to infer the correct return type as it's circular.\ntype StreamableUIWrapper = {\n /**\n * The value of the streamable UI. This can be returned from a Server Action and received by the client.\n */\n readonly value: React.ReactNode;\n\n /**\n * This method updates the current UI node. It takes a new UI node and replaces the old one.\n */\n update(value: React.ReactNode): StreamableUIWrapper;\n\n /**\n * This method is used to append a new UI node to the end of the old one.\n * Once appended a new UI node, the previous UI node cannot be updated anymore.\n *\n * @example\n * ```jsx\n * const ui = createStreamableUI(<div>hello</div>)\n * ui.append(<div>world</div>)\n *\n * // The UI node will be:\n * // <>\n * // <div>hello</div>\n * // <div>world</div>\n * // </>\n * ```\n */\n append(value: React.ReactNode): StreamableUIWrapper;\n\n /**\n * This method is used to signal that there is an error in the UI stream.\n * It will be thrown on the client side and caught by the nearest error boundary component.\n */\n error(error: any): StreamableUIWrapper;\n\n /**\n * This method marks the UI node as finalized. You can either call it without any parameters or with a new UI node as the final state.\n * Once called, the UI node cannot be updated or appended anymore.\n *\n * This method is always **required** to be called, otherwise the response will be stuck in a loading state.\n */\n done(...args: [React.ReactNode] | []): StreamableUIWrapper;\n};\n\n/**\n * Create a piece of changeable UI that can be streamed to the client.\n * On the client side, it can be rendered as a normal React node.\n */\nfunction createStreamableUI(initialValue?: React.ReactNode) {\n let currentValue = initialValue;\n let closed = false;\n let { row, resolve, reject } = createSuspendedChunk(initialValue);\n\n function assertStream(method: string) {\n if (closed) {\n throw new Error(method + ': UI stream is already closed.');\n }\n }\n\n let warningTimeout: NodeJS.Timeout | undefined;\n function warnUnclosedStream() {\n if (process.env.NODE_ENV === 'development') {\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n warningTimeout = setTimeout(() => {\n console.warn(\n 'The streamable UI has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.',\n );\n }, DEV_DEFAULT_STREAMABLE_WARNING_TIME);\n }\n }\n warnUnclosedStream();\n\n const streamable: StreamableUIWrapper = {\n value: row,\n update(value: React.ReactNode) {\n assertStream('.update()');\n\n // There is no need to update the value if it's referentially equal.\n if (value === currentValue) {\n warnUnclosedStream();\n return streamable;\n }\n\n const resolvable = createResolvablePromise();\n currentValue = value;\n\n resolve({ value: currentValue, done: false, next: resolvable.promise });\n resolve = resolvable.resolve;\n reject = resolvable.reject;\n\n warnUnclosedStream();\n\n return streamable;\n },\n append(value: React.ReactNode) {\n assertStream('.append()');\n\n const resolvable = createResolvablePromise();\n currentValue = value;\n\n resolve({ value, done: false, append: true, next: resolvable.promise });\n resolve = resolvable.resolve;\n reject = resolvable.reject;\n\n warnUnclosedStream();\n\n return streamable;\n },\n error(error: any) {\n assertStream('.error()');\n\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n closed = true;\n reject(error);\n\n return streamable;\n },\n done(...args: [] | [React.ReactNode]) {\n assertStream('.done()');\n\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n closed = true;\n if (args.length) {\n resolve({ value: args[0], done: true });\n return streamable;\n }\n resolve({ value: currentValue, done: true });\n\n return streamable;\n },\n };\n\n return streamable;\n}\n\nconst STREAMABLE_VALUE_INTERNAL_LOCK = Symbol('streamable.value.lock');\n\n/**\n * Create a wrapped, changeable value that can be streamed to the client.\n * On the client side, the value can be accessed via the readStreamableValue() API.\n */\nfunction createStreamableValue<T = any, E = any>(\n initialValue?: T | ReadableStream<T>,\n) {\n const isReadableStream =\n initialValue instanceof ReadableStream ||\n (typeof initialValue === 'object' &&\n initialValue !== null &&\n 'getReader' in initialValue &&\n typeof initialValue.getReader === 'function' &&\n 'locked' in initialValue &&\n typeof initialValue.locked === 'boolean');\n\n if (!isReadableStream) {\n return createStreamableValueImpl<T, E>(initialValue);\n }\n\n const streamableValue = createStreamableValueImpl<T, E>();\n\n // Since the streamable value will be from a readable stream, it's not allowed\n // to update the value manually as that introduces race conditions and\n // unexpected behavior.\n // We lock the value to prevent any updates from the user.\n streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = true;\n\n (async () => {\n try {\n // Consume the readable stream and update the value.\n const reader = initialValue.getReader();\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n\n // Unlock the value to allow updates.\n streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;\n if (typeof value === 'string') {\n streamableValue.append(value);\n } else {\n streamableValue.update(value);\n }\n // Lock the value again.\n streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = true;\n }\n\n streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;\n streamableValue.done();\n } catch (e) {\n streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;\n streamableValue.error(e);\n }\n })();\n\n return streamableValue;\n}\n\n// It's necessary to define the type manually here, otherwise TypeScript compiler\n// will not be able to infer the correct return type as it's circular.\ntype StreamableValueWrapper<T, E> = {\n /**\n * The value of the streamable. This can be returned from a Server Action and\n * received by the client. To read the streamed values, use the\n * `readStreamableValue` or `useStreamableValue` APIs.\n */\n readonly value: StreamableValue<T, E>;\n\n /**\n * This method updates the current value with a new one.\n */\n update(value: T): StreamableValueWrapper<T, E>;\n\n /**\n * This method is used to append a delta string to the current value. It\n * requires the current value of the streamable to be a string.\n *\n * @example\n * ```jsx\n * const streamable = createStreamableValue('hello');\n * streamable.append(' world');\n *\n * // The value will be 'hello world'\n * ```\n */\n append(value: T): StreamableValueWrapper<T, E>;\n\n /**\n * This method is used to signal that there is an error in the value stream.\n * It will be thrown on the client side when consumed via\n * `readStreamableValue` or `useStreamableValue`.\n */\n error(error: any): StreamableValueWrapper<T, E>;\n\n /**\n * This method marks the value as finalized. You can either call it without\n * any parameters or with a new value as the final state.\n * Once called, the value cannot be updated or appended anymore.\n *\n * This method is always **required** to be called, otherwise the response\n * will be stuck in a loading state.\n */\n done(...args: [T] | []): StreamableValueWrapper<T, E>;\n\n /**\n * @internal This is an internal lock to prevent the value from being\n * updated by the user.\n */\n [STREAMABLE_VALUE_INTERNAL_LOCK]: boolean;\n};\n\nfunction createStreamableValueImpl<T = any, E = any>(initialValue?: T) {\n let closed = false;\n let locked = false;\n let resolvable = createResolvablePromise<StreamableValue<T, E>>();\n\n let currentValue = initialValue;\n let currentError: E | undefined;\n let currentPromise: typeof resolvable.promise | undefined =\n resolvable.promise;\n let currentPatchValue: StreamablePatch;\n\n function assertStream(method: string) {\n if (closed) {\n throw new Error(method + ': Value stream is already closed.');\n }\n if (locked) {\n throw new Error(\n method + ': Value stream is locked and cannot be updated.',\n );\n }\n }\n\n let warningTimeout: NodeJS.Timeout | undefined;\n function warnUnclosedStream() {\n if (process.env.NODE_ENV === 'development') {\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n warningTimeout = setTimeout(() => {\n console.warn(\n 'The streamable value has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.',\n );\n }, DEV_DEFAULT_STREAMABLE_WARNING_TIME);\n }\n }\n warnUnclosedStream();\n\n function createWrapped(initialChunk?: boolean): StreamableValue<T, E> {\n // This makes the payload much smaller if there're mutative updates before the first read.\n let init: Partial<StreamableValue<T, E>>;\n\n if (currentError !== undefined) {\n init = { error: currentError };\n } else {\n if (currentPatchValue && !initialChunk) {\n init = { diff: currentPatchValue };\n } else {\n init = { curr: currentValue };\n }\n }\n\n if (currentPromise) {\n init.next = currentPromise;\n }\n\n if (initialChunk) {\n init.type = STREAMABLE_VALUE_TYPE;\n }\n\n return init;\n }\n\n // Update the internal `currentValue` and `currentPatchValue` if needed.\n function updateValueStates(value: T) {\n // If we can only send a patch over the wire, it's better to do so.\n currentPatchValue = undefined;\n if (typeof value === 'string') {\n if (typeof currentValue === 'string') {\n if (value.startsWith(currentValue)) {\n currentPatchValue = [0, value.slice(currentValue.length)];\n }\n }\n }\n\n currentValue = value;\n }\n\n const streamable: StreamableValueWrapper<T, E> = {\n set [STREAMABLE_VALUE_INTERNAL_LOCK](state: boolean) {\n locked = state;\n },\n get value() {\n return createWrapped(true);\n },\n update(value: T) {\n assertStream('.update()');\n\n const resolvePrevious = resolvable.resolve;\n resolvable = createResolvablePromise();\n\n updateValueStates(value);\n currentPromise = resolvable.promise;\n resolvePrevious(createWrapped());\n\n warnUnclosedStream();\n\n return streamable;\n },\n append(value: T) {\n assertStream('.append()');\n\n if (\n typeof currentValue !== 'string' &&\n typeof currentValue !== 'undefined'\n ) {\n throw new Error(\n `.append(): The current value is not a string. Received: ${typeof currentValue}`,\n );\n }\n if (typeof value !== 'string') {\n throw new Error(\n `.append(): The value is not a string. Received: ${typeof value}`,\n );\n }\n\n const resolvePrevious = resolvable.resolve;\n resolvable = createResolvablePromise();\n\n if (typeof currentValue === 'string') {\n currentPatchValue = [0, value];\n (currentValue as string) = currentValue + value;\n } else {\n currentPatchValue = undefined;\n currentValue = value;\n }\n\n currentPromise = resolvable.promise;\n resolvePrevious(createWrapped());\n\n warnUnclosedStream();\n\n return streamable;\n },\n error(error: any) {\n assertStream('.error()');\n\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n closed = true;\n currentError = error;\n currentPromise = undefined;\n\n resolvable.resolve({ error });\n\n return streamable;\n },\n done(...args: [] | [T]) {\n assertStream('.done()');\n\n if (warningTimeout) {\n clearTimeout(warningTimeout);\n }\n closed = true;\n currentPromise = undefined;\n\n if (args.length) {\n updateValueStates(args[0]);\n resolvable.resolve(createWrapped());\n return streamable;\n }\n\n resolvable.resolve({});\n\n return streamable;\n },\n };\n\n return streamable;\n}\n\nexport { createStreamableUI, createStreamableValue };\n","import type OpenAI from 'openai';\nimport type { ReactNode } from 'react';\nimport { z } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport { OpenAIStream } from '../streams';\nimport { consumeStream } from '../util/consume-stream';\nimport { createResolvablePromise } from '../util/create-resolvable-promise';\nimport { createStreamableUI } from './streamable';\n\ntype Streamable = ReactNode | Promise<ReactNode>;\ntype Renderer<T> = (\n props: T,\n) =>\n | Streamable\n | Generator<Streamable, Streamable, void>\n | AsyncGenerator<Streamable, Streamable, void>;\n\n/**\n * `render` is a helper function to create a streamable UI from some LLMs.\n * This API only supports OpenAI's GPT models with Function Calling and Assistants Tools,\n * please use `streamUI` for compatibility with other providers.\n *\n * @deprecated It's recommended to use the `streamUI` API for compatibility with AI SDK Core APIs\n * and future features. This API will be removed in a future release.\n */\nexport function render<\n TS extends {\n [name: string]: z.Schema;\n } = {},\n FS extends {\n [name: string]: z.Schema;\n } = {},\n>(options: {\n /**\n * The model name to use. Must be OpenAI SDK compatible. Tools and Functions are only supported\n * GPT models (3.5/4), OpenAI Assistants, Mistral small and large, and Fireworks firefunction-v1.\n *\n * @example \"gpt-3.5-turbo\"\n */\n model: string;\n /**\n * The provider instance to use. Currently the only provider available is OpenAI.\n * This needs to match the model name.\n */\n provider: OpenAI;\n messages: Parameters<\n typeof OpenAI.prototype.chat.completions.create\n >[0]['messages'];\n text?: Renderer<{\n /**\n * The full text content from the model so far.\n */\n content: string;\n /**\n * The new appended text content from the model since the last `text` call.\n */\n delta: string;\n /**\n * Whether the model is done generating text.\n * If `true`, the `content` will be the final output and this call will be the last.\n */\n done: boolean;\n }>;\n tools?: {\n [name in keyof TS]: {\n description?: string;\n parameters: TS[name];\n render: Renderer<z.infer<TS[name]>>;\n };\n };\n functions?: {\n [name in keyof FS]: {\n description?: string;\n parameters: FS[name];\n render: Renderer<z.infer<FS[name]>>;\n };\n };\n initial?: ReactNode;\n temperature?: number;\n}): ReactNode {\n const ui = createStreamableUI(options.initial);\n\n // The default text renderer just returns the content as string.\n const text = options.text\n ? options.text\n : ({ content }: { content: string }) => content;\n\n const functions = options.functions\n ? Object.entries(options.functions).map(\n ([name, { description, parameters }]) => {\n return {\n name,\n description,\n parameters: zodToJsonSchema(parameters) as Record<string, unknown>,\n };\n },\n )\n : undefined;\n\n const tools = options.tools\n ? Object.entries(options.tools).map(\n ([name, { description, parameters }]) => {\n return {\n type: 'function' as const,\n function: {\n name,\n description,\n parameters: zodToJsonSchema(parameters) as Record<\n string,\n unknown\n >,\n },\n };\n },\n )\n : undefined;\n\n if (functions && tools) {\n throw new Error(\n \"You can't have both functions and tools defined. Please choose one or the other.\",\n );\n }\n\n let finished: Promise<void> | undefined;\n\n async function handleRender(\n args: any,\n renderer: undefined | Renderer<any>,\n res: ReturnType<typeof createStreamableUI>,\n ) {\n if (!renderer) return;\n\n const resolvable = createResolvablePromise<void>();\n\n if (finished) {\n finished = finished.then(() => resolvable.promise);\n } else {\n finished = resolvable.promise;\n }\n\n const value = renderer(args);\n if (\n value instanceof Promise ||\n (value &&\n typeof value === 'object' &&\n 'then' in value &&\n typeof value.then === 'function')\n ) {\n const node = await (value as Promise<React.ReactNode>);\n res.update(node);\n resolvable.resolve(void 0);\n } else if (\n value &&\n typeof value === 'object' &&\n Symbol.asyncIterator in value\n ) {\n const it = value as AsyncGenerator<\n React.ReactNode,\n React.ReactNode,\n void\n >;\n while (true) {\n const { done, value } = await it.next();\n res.update(value);\n if (done) break;\n }\n resolvable.resolve(void 0);\n } else if (value && typeof value === 'object' && Symbol.iterator in value) {\n const it = value as Generator<React.ReactNode, React.ReactNode, void>;\n while (true) {\n const { done, value } = it.next();\n res.update(value);\n if (done) break;\n }\n resolvable.resolve(void 0);\n } else {\n res.update(value);\n resolvable.resolve(void 0);\n }\n }\n\n (async () => {\n let hasFunction = false;\n let content = '';\n\n consumeStream(\n OpenAIStream(\n (await options.provider.chat.completions.create({\n model: options.model,\n messages: options.messages,\n temperature: options.temperature,\n stream: true,\n ...(functions\n ? {\n functions,\n }\n : {}),\n ...(tools\n ? {\n tools,\n }\n : {}),\n })) as any,\n {\n ...(functions\n ? {\n async experimental_onFunctionCall(functionCallPayload) {\n hasFunction = true;\n handleRender(\n functionCallPayload.arguments,\n options.functions?.[functionCallPayload.name as any]\n ?.render,\n ui,\n );\n },\n }\n : {}),\n ...(tools\n ? {\n async experimental_onToolCall(toolCallPayload: any) {\n hasFunction = true;\n\n // TODO: We might need Promise.all here?\n for (const tool of toolCallPayload.tools) {\n handleRender(\n tool.func.arguments,\n options.tools?.[tool.func.name as any]?.render,\n ui,\n );\n }\n },\n }\n : {}),\n onText(chunk) {\n content += chunk;\n handleRender({ content, done: false, delta: chunk }, text, ui);\n },\n async onFinal() {\n if (hasFunction) {\n await finished;\n ui.done();\n return;\n }\n\n handleRender({ content, done: true }, text, ui);\n await finished;\n ui.done();\n },\n },\n ),\n );\n })();\n\n return ui.value;\n}\n","import { APICallError } from '@ai-sdk/provider';\nimport { getErrorMessage, isAbortError } from '@ai-sdk/provider-utils';\nimport { delay } from './delay';\nimport { RetryError } from './retry-error';\n\nexport type RetryFunction = <OUTPUT>(\n fn: () => PromiseLike<OUTPUT>,\n) => PromiseLike<OUTPUT>;\n\n/**\nThe `retryWithExponentialBackoff` strategy retries a failed API call with an exponential backoff.\nYou can configure the maximum number of retries, the initial delay, and the backoff factor.\n */\nexport const retryWithExponentialBackoff =\n ({\n maxRetries = 2,\n initialDelayInMs = 2000,\n backoffFactor = 2,\n } = {}): RetryFunction =>\n async <OUTPUT>(f: () => PromiseLike<OUTPUT>) =>\n _retryWithExponentialBackoff(f, {\n maxRetries,\n delayInMs: initialDelayInMs,\n backoffFactor,\n });\n\nasync function _retryWithExponentialBackoff<OUTPUT>(\n f: () => PromiseLike<OUTPUT>,\n {\n maxRetries,\n delayInMs,\n backoffFactor,\n }: { maxRetries: number; delayInMs: number; backoffFactor: number },\n errors: unknown[] = [],\n): Promise<OUTPUT> {\n try {\n return await f();\n } catch (error) {\n if (isAbortError(error)) {\n throw error; // don't retry when the request was aborted\n }\n\n if (maxRetries === 0) {\n throw error; // don't wrap the error when retries are disabled\n }\n\n const errorMessage = getErrorMessage(error);\n const newErrors = [...errors, error];\n const tryNumber = newErrors.length;\n\n if (tryNumber > maxRetries) {\n throw new RetryError({\n message: `Failed after ${tryNumber} attempts. Last error: ${errorMessage}`,\n reason: 'maxRetriesExceeded',\n errors: newErrors,\n });\n }\n\n if (\n error instanceof Error &&\n APICallError.isAPICallError(error) &&\n error.isRetryable === true &&\n tryNumber <= maxRetries\n ) {\n await delay(delayInMs);\n return _retryWithExponentialBackoff(\n f,\n { maxRetries, delayInMs: backoffFactor * delayInMs, backoffFactor },\n newErrors,\n );\n }\n\n if (tryNumber === 1) {\n throw error; // don't wrap the error when a non-retryable error occurs on the first try\n }\n\n throw new RetryError({\n message: `Failed after ${tryNumber} attempts with non-retryable error: '${errorMessage}'`,\n reason: 'errorNotRetryable',\n errors: newErrors,\n });\n }\n}\n","export async function delay(delayInMs: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, delayInMs));\n}\n","import { AISDKError } from '@ai-sdk/provider';\n\nconst name = 'AI_RetryError';\nconst marker = `vercel.ai.error.${name}`;\nconst symbol = Symbol.for(marker);\n\nexport type RetryErrorReason =\n | 'maxRetriesExceeded'\n | 'errorNotRetryable'\n | 'abort';\n\nexport class RetryError extends AISDKError {\n private readonly [symbol] = true; // used in isInstance\n\n // note: property order determines debugging output\n readonly reason: RetryErrorReason;\n readonly lastError: unknown;\n readonly errors: Array<unknown>;\n\n constructor({\n message,\n reason,\n errors,\n }: {\n message: string;\n reason: RetryErrorReason;\n errors: Array<unknown>;\n }) {\n super({ name, message });\n\n this.reason = reason;\n this.errors = errors;\n\n // separate our last error to make debugging via log easier:\n this.lastError = errors[errors.length - 1];\n }\n\n static isInstance(error: unknown): error is RetryError {\n return AISDKError.hasMarker(error, marker);\n }\n\n /**\n * @deprecated use `isInstance` instead\n */\n static isRetryError(error: unknown): error is RetryError {\n return (\n error instanceof Error &&\n error.name === name &&\n typeof (error as RetryError).reason === 'string' &&\n Array.isArray((error as RetryError).errors)\n );\n }\n\n /**\n * @deprecated Do not use this method. It will be removed in the next major version.\n */\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n reason: this.reason,\n lastError: this.lastError,\n errors: this.errors,\n };\n }\n}\n","import {\n LanguageModelV1ImagePart,\n LanguageModelV1Message,\n LanguageModelV1Prompt,\n LanguageModelV1TextPart,\n} from '@ai-sdk/provider';\nimport { getErrorMessage } from '@ai-sdk/provider-utils';\nimport { download } from '../../util/download';\nimport { CoreMessage } from '../prompt/message';\nimport { detectImageMimeType } from '../util/detect-image-mimetype';\nimport { ImagePart, TextPart } from './content-part';\nimport { convertDataContentToUint8Array } from './data-content';\nimport { ValidatedPrompt } from './get-validated-prompt';\nimport { InvalidMessageRoleError } from './invalid-message-role-error';\n\nexport async function convertToLanguageModelPrompt({\n prompt,\n modelSupportsImageUrls = true,\n downloadImplementation = download,\n}: {\n prompt: ValidatedPrompt;\n modelSupportsImageUrls: boolean | undefined;\n downloadImplementation?: typeof download;\n}): Promise<LanguageModelV1Prompt> {\n const languageModelMessages: LanguageModelV1Prompt = [];\n\n if (prompt.system != null) {\n languageModelMessages.push({ role: 'system', content: prompt.system });\n }\n\n const downloadedImages =\n modelSupportsImageUrls || prompt.messages == null\n ? null\n : await downloadImages(prompt.messages, downloadImplementation);\n\n const promptType = prompt.type;\n switch (promptType) {\n case 'prompt': {\n languageModelMessages.push({\n role: 'user',\n content: [{ type: 'text', text: prompt.prompt }],\n });\n break;\n }\n\n case 'messages': {\n languageModelMessages.push(\n ...prompt.messages.map(\n (message): LanguageModelV1Message =>\n convertToLanguageModelMessage(message, downloadedImages),\n ),\n );\n break;\n }\n\n default: {\n const _exhaustiveCheck: never = promptType;\n throw new Error(`Unsupported prompt type: ${_exhaustiveCheck}`);\n }\n }\n\n return languageModelMessages;\n}\n\n/**\n * Convert a CoreMessage to a LanguageModelV1Message.\n *\n * @param message The CoreMessage to convert.\n * @param downloadedImages A map of image URLs to their downloaded data. Only\n * available if the model does not support image URLs, null otherwise.\n */\nexport function convertToLanguageModelMessage(\n message: CoreMessage,\n downloadedImages: Record<\n string,\n { mimeType: string | undefined; data: Uint8Array }\n > | null,\n): LanguageModelV1Message {\n const role = message.role;\n switch (role) {\n case 'system': {\n return { role: 'system', content: message.content };\n }\n\n case 'user': {\n if (typeof message.content === 'string') {\n return {\n role: 'user',\n content: [{ type: 'text', text: message.content }],\n };\n }\n\n return {\n role: 'user',\n content: message.content.map(\n (part): LanguageModelV1TextPart | LanguageModelV1ImagePart => {\n switch (part.type) {\n case 'text': {\n return part;\n }\n\n case 'image': {\n if (part.image instanceof URL) {\n if (downloadedImages == null) {\n return {\n type: 'image',\n image: part.image,\n mimeType: part.mimeType,\n };\n } else {\n const downloadedImage =\n downloadedImages[part.image.toString()];\n return {\n type: 'image',\n image: downloadedImage.data,\n mimeType: part.mimeType ?? downloadedImage.mimeType,\n };\n }\n }\n\n // try to convert string image parts to urls\n if (typeof part.image === 'string') {\n try {\n const url = new URL(part.image);\n\n switch (url.protocol) {\n case 'http:':\n case 'https:': {\n if (downloadedImages == null) {\n return {\n type: 'image',\n image: url,\n mimeType: part.mimeType,\n };\n } else {\n const downloadedImage = downloadedImages[part.image];\n return {\n type: 'image',\n image: downloadedImage.data,\n mimeType: part.mimeType ?? downloadedImage.mimeType,\n };\n }\n }\n case 'data:': {\n try {\n const [header, base64Content] = part.image.split(',');\n const mimeType = header.split(';')[0].split(':')[1];\n\n if (mimeType == null || base64Content == null) {\n throw new Error('Invalid data URL format');\n }\n\n return {\n type: 'image',\n image:\n convertDataContentToUint8Array(base64Content),\n mimeType,\n };\n } catch (error) {\n throw new Error(\n `Error processing data URL: ${getErrorMessage(\n message,\n )}`,\n );\n }\n }\n default: {\n throw new Error(\n `Unsupported URL protocol: ${url.protocol}`,\n );\n }\n }\n } catch (_ignored) {\n // not a URL\n }\n }\n\n const imageUint8 = convertDataContentToUint8Array(part.image);\n\n return {\n type: 'image',\n image: imageUint8,\n mimeType: part.mimeType ?? detectImageMimeType(imageUint8),\n };\n }\n }\n },\n ),\n };\n }\n\n case 'assistant': {\n if (typeof message.content === 'string') {\n return {\n role: 'assistant',\n content: [{ type: 'text', text: message.content }],\n };\n }\n\n return {\n role: 'assistant',\n content: message.content.filter(\n // remove empty text parts:\n part => part.type !== 'text' || part.text !== '',\n ),\n };\n }\n\n case 'tool': {\n return message;\n }\n\n default: {\n const _exhaustiveCheck: never = role;\n throw new InvalidMessageRoleError({ role: _exhaustiveCheck });\n }\n }\n}\n\nasync function downloadImages(\n messages: CoreMessage[],\n downloadImplementation: typeof download,\n): Promise<Record<string, { mimeType: string | undefined; data: Uint8Array }>> {\n const urls = messages\n .filter(message => message.role === 'user')\n .map(message => message.content)\n .filter((content): content is Array<TextPart | ImagePart> =>\n Array.isArray(content),\n )\n .flat()\n .filter((part): part is ImagePart => part.type === 'image')\n .map(part => part.image)\n .map(part =>\n // support string urls in image parts:\n typeof part === 'string' &&\n (part.startsWith('http:') || part.startsWith('https:'))\n ? new URL(part)\n : part,\n )\n .filter((image): image is URL => image instanceof URL);\n\n // download images in parallel:\n const downloadedImages = await Promise.all(\n urls.map(async url => ({\n url,\n data: await downloadImplementation({ url }),\n })),\n );\n\n return Object.fromEntries(\n downloadedImages.map(({ url, data }) => [url.toString(), data]),\n );\n}\n","import { AISDKError } from '@ai-sdk/provider';\n\nconst name = 'AI_DownloadError';\nconst marker = `vercel.ai.error.${name}`;\nconst symbol = Symbol.for(marker);\n\nexport class DownloadError extends AISDKError {\n private readonly [symbol] = true; // used in isInstance\n\n readonly url: string;\n readonly statusCode?: number;\n readonly statusText?: string;\n\n constructor({\n url,\n statusCode,\n statusText,\n cause,\n message = cause == null\n ? `Failed to download ${url}: ${statusCode} ${statusText}`\n : `Failed to download ${url}: ${cause}`,\n }: {\n url: string;\n statusCode?: number;\n statusText?: string;\n message?: string;\n cause?: unknown;\n }) {\n super({ name, message, cause });\n\n this.url = url;\n this.statusCode = statusCode;\n this.statusText = statusText;\n }\n\n static isInstance(error: unknown): error is DownloadError {\n return AISDKError.hasMarker(error, marker);\n }\n\n /**\n * @deprecated use `isInstance` instead\n */\n static isDownloadError(error: unknown): error is DownloadError {\n return (\n error instanceof Error &&\n error.name === name &&\n typeof (error as DownloadError).url === 'string' &&\n ((error as DownloadError).statusCode == null ||\n typeof (error as DownloadError).statusCode === 'number') &&\n ((error as DownloadError).statusText == null ||\n typeof (error as DownloadError).statusText === 'string')\n );\n }\n\n /**\n * @deprecated Do not use this method. It will be removed in the next major version.\n */\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n url: this.url,\n statusCode: this.statusCode,\n statusText: this.statusText,\n cause: this.cause,\n };\n }\n}\n","import { DownloadError } from './download-error';\n\nexport async function download({\n url,\n fetchImplementation = fetch,\n}: {\n url: URL;\n fetchImplementation?: typeof fetch;\n}): Promise<{\n data: Uint8Array;\n mimeType: string | undefined;\n}> {\n const urlText = url.toString();\n try {\n const response = await fetchImplementation(urlText);\n\n if (!response.ok) {\n throw new DownloadError({\n url: urlText,\n statusCode: response.status,\n statusText: response.statusText,\n });\n }\n\n return {\n data: new Uint8Array(await response.arrayBuffer()),\n mimeType: response.headers.get('content-type') ?? undefined,\n };\n } catch (error) {\n if (DownloadError.isInstance(error)) {\n throw error;\n }\n\n throw new DownloadError({ url: urlText, cause: error });\n }\n}\n","const mimeTypeSignatures = [\n { mimeType: 'image/gif' as const, bytes: [0x47, 0x49, 0x46] },\n { mimeType: 'image/png' as const, bytes: [0x89, 0x50, 0x4e, 0x47] },\n { mimeType: 'image/jpeg' as const, bytes: [0xff, 0xd8] },\n { mimeType: 'image/webp' as const, bytes: [0x52, 0x49, 0x46, 0x46] },\n];\n\nexport function detectImageMimeType(\n image: Uint8Array,\n): 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | undefined {\n for (const { bytes, mimeType } of mimeTypeSignatures) {\n if (\n image.length >= bytes.length &&\n bytes.every((byte, index) => image[index] === byte)\n ) {\n return mimeType;\n }\n }\n\n return undefined;\n}\n","import {\n convertBase64ToUint8Array,\n convertUint8ArrayToBase64,\n} from '@ai-sdk/provider-utils';\nimport { InvalidDataContentError } from './invalid-data-content-error';\n\n/**\nData content. Can either be a base64-encoded string, a Uint8Array, an ArrayBuffer, or a Buffer.\n */\nexport type DataContent = string | Uint8Array | ArrayBuffer | Buffer;\n\n/**\nConverts data content to a base64-encoded string.\n\n@param content - Data content to convert.\n@returns Base64-encoded string.\n*/\nexport function convertDataContentToBase64String(content: DataContent): string {\n if (typeof content === 'string') {\n return content;\n }\n\n if (content instanceof ArrayBuffer) {\n return convertUint8ArrayToBase64(new Uint8Array(content));\n }\n\n return convertUint8ArrayToBase64(content);\n}\n\n/**\nConverts data content to a Uint8Array.\n\n@param content - Data content to convert.\n@returns Uint8Array.\n */\nexport function convertDataContentToUint8Array(\n content: DataContent,\n): Uint8Array {\n if (content instanceof Uint8Array) {\n return content;\n }\n\n if (typeof content === 'string') {\n try {\n return convertBase64ToUint8Array(content);\n } catch (error) {\n throw new InvalidDataContentError({\n message:\n 'Invalid data content. Content string is not a base64-encoded media.',\n content,\n cause: error,\n });\n }\n }\n\n if (content instanceof ArrayBuffer) {\n return new Uint8Array(content);\n }\n\n throw new InvalidDataContentError({ content });\n}\n\n/**\n * Converts a Uint8Array to a string of text.\n *\n * @param uint8Array - The Uint8Array to convert.\n * @returns The converted string.\n */\nexport function convertUint8ArrayToText(uint8Array: Uint8Array): string {\n try {\n return new TextDecoder().decode(uint8Array);\n } catch (error) {\n throw new Error('Error decoding Uint8Array to text');\n }\n}\n","import { AISDKError } from '@ai-sdk/provider';\n\nconst name = 'AI_InvalidDataContentError';\nconst marker = `vercel.ai.error.${name}`;\nconst symbol = Symbol.for(marker);\n\nexport class InvalidDataContentError extends AISDKError {\n private readonly [symbol] = true; // used in isInstance\n\n readonly content: unknown;\n\n constructor({\n content,\n cause,\n message = `Invalid data content. Expected a base64 string, Uint8Array, ArrayBuffer, or Buffer, but got ${typeof content}.`,\n }: {\n content: unknown;\n cause?: unknown;\n message?: string;\n }) {\n super({ name, message, cause });\n\n this.content = content;\n }\n\n static isInstance(error: unknown): error is InvalidDataContentError {\n return AISDKError.hasMarker(error, marker);\n }\n\n /**\n * @deprecated use `is