UNPKG

langbase

Version:

The AI SDK for building declarative and composable AI-powered LLM products.

1 lines • 117 kB
{"version":3,"sources":["../src/lib/utils/doc-to-formdata.ts","../src/data/constants.ts","../src/common/errors.ts","../src/common/stream.ts","../src/common/request.ts","../src/langbase/trace.ts","../src/langbase/workflows.ts","../src/langbase/langbase.ts","../src/pipes/pipes.ts","../src/lib/helpers/index.ts"],"sourcesContent":["/**\n * Converts various document formats to FormData.\n *\n * @param options - The conversion options\n * @param options.document - The document to convert. Can be Buffer, File, FormData or ReadableStream\n * @param options.documentName - The name of the document\n * @param options.contentType - The MIME type of the document\n *\n * @returns A Promise that resolves to FormData containing the document\n *\n * @example\n * ```ts\n * const buffer = Buffer.from('Hello World');\n * const formData = await convertDocToFormData({\n * document: buffer,\n * documentName: 'hello.txt',\n * contentType: 'text/plain'\n * });\n * ```\n */\nexport async function convertDocToFormData(options: {\n\tdocument: Buffer | File | FormData | ReadableStream;\n\tdocumentName: string;\n\tcontentType: string;\n}) {\n\tlet formData = new FormData();\n\n\tif (options.document instanceof Buffer) {\n\t\tconst documentBlob = new Blob([options.document], {\n\t\t\ttype: options.contentType,\n\t\t});\n\t\tformData.append('document', documentBlob, options.documentName);\n\t} else if (options.document instanceof File) {\n\t\tformData.append('document', options.document, options.documentName);\n\t} else if (options.document instanceof FormData) {\n\t\tformData = options.document;\n\t} else if (options.document instanceof ReadableStream) {\n\t\tconst chunks: Uint8Array[] = [];\n\t\tconst reader = options.document.getReader();\n\n\t\twhile (true) {\n\t\t\tconst {done, value} = await reader.read();\n\t\t\tif (done) break;\n\t\t\tchunks.push(value);\n\t\t}\n\n\t\tconst documentBlob = new Blob(chunks, {type: options.contentType});\n\t\tformData.append('document', documentBlob, options.documentName);\n\t}\n\n\tformData.append('documentName', options.documentName);\n\n\treturn formData;\n}\n","export const GENERATION_ENDPOINTS = [\n\t'/v1/pipes/run',\n\t'/beta/chat',\n\t'/beta/generate',\n\t'/v1/agent/run',\n];\n","import {Headers} from './../../types'; // Ensure this import is correct\n\nexport class APIError extends Error {\n\treadonly status: number | undefined;\n\treadonly headers: Headers | undefined;\n\treadonly error: Object | undefined;\n\n\treadonly code: string | null | undefined;\n\treadonly param: string | null | undefined;\n\treadonly type: string | undefined;\n\n\treadonly request_id: string | null | undefined;\n\n\tconstructor(\n\t\tstatus: number | undefined,\n\t\terror: Object | undefined,\n\t\tmessage: string | undefined,\n\t\theaders: Headers | undefined,\n\t) {\n\t\tsuper(APIError.makeMessage(status, error, message));\n\t\tthis.status = status;\n\t\tthis.headers = headers;\n\t\tthis.request_id = headers?.['lb-request-id'];\n\n\t\tconst data = error as Record<string, any>;\n\t\tthis.error = data;\n\t\tthis.code = data?.['code'];\n\t\tthis.status = data?.['status'];\n\t\t// this.param = data?.['param'];\n\t\t// this.type = data?.['type'];\n\t}\n\n\tprivate static makeMessage(\n\t\tstatus: number | undefined,\n\t\terror: any,\n\t\tmessage: string | undefined,\n\t): string {\n\t\tconst msg = error?.message\n\t\t\t? typeof error.message === 'string'\n\t\t\t\t? error.message\n\t\t\t\t: JSON.stringify(error.message)\n\t\t\t: error\n\t\t\t\t? JSON.stringify(error)\n\t\t\t\t: message;\n\n\t\tif (status && msg) {\n\t\t\treturn `${status} ${msg}`;\n\t\t}\n\t\tif (status) {\n\t\t\treturn `${status} status code (no body)`;\n\t\t}\n\t\tif (msg) {\n\t\t\treturn msg;\n\t\t}\n\t\treturn '(no status code or body)';\n\t}\n\n\tstatic generate(\n\t\tstatus: number | undefined,\n\t\terrorResponse: Object | undefined,\n\t\tmessage: string | undefined,\n\t\theaders: Headers | undefined,\n\t): APIError {\n\t\tif (!status) {\n\t\t\treturn new APIConnectionError({\n\t\t\t\tcause:\n\t\t\t\t\terrorResponse instanceof Error ? errorResponse : undefined,\n\t\t\t});\n\t\t}\n\n\t\tconst error = (errorResponse as Record<string, any>)?.['error'];\n\n\t\tswitch (status) {\n\t\t\tcase 400:\n\t\t\t\treturn new BadRequestError(status, error, message, headers);\n\t\t\tcase 401:\n\t\t\t\treturn new AuthenticationError(status, error, message, headers);\n\t\t\tcase 403:\n\t\t\t\treturn new PermissionDeniedError(\n\t\t\t\t\tstatus,\n\t\t\t\t\terror,\n\t\t\t\t\tmessage,\n\t\t\t\t\theaders,\n\t\t\t\t);\n\t\t\tcase 404:\n\t\t\t\treturn new NotFoundError(status, error, message, headers);\n\t\t\tcase 409:\n\t\t\t\treturn new ConflictError(status, error, message, headers);\n\t\t\tcase 422:\n\t\t\t\treturn new UnprocessableEntityError(\n\t\t\t\t\tstatus,\n\t\t\t\t\terror,\n\t\t\t\t\tmessage,\n\t\t\t\t\theaders,\n\t\t\t\t);\n\t\t\tcase 429:\n\t\t\t\treturn new RateLimitError(status, error, message, headers);\n\t\t\tdefault:\n\t\t\t\treturn status >= 500\n\t\t\t\t\t? new InternalServerError(status, error, message, headers)\n\t\t\t\t\t: new APIError(status, error, message, headers);\n\t\t}\n\t}\n}\n\nexport class APIConnectionError extends APIError {\n\toverride readonly status: undefined = undefined;\n\n\tconstructor({message, cause}: {message?: string; cause?: Error}) {\n\t\tsuper(undefined, undefined, message || 'Connection error.', undefined);\n\t\tif (cause) (this as Error).cause = cause;\n\t}\n}\n\nexport class APIConnectionTimeoutError extends APIConnectionError {\n\tconstructor({message}: {message?: string} = {}) {\n\t\tsuper({message: message ?? 'Request timed out.'});\n\t}\n}\n\nexport class BadRequestError extends APIError {\n\toverride readonly status: 400 = 400;\n}\n\nexport class AuthenticationError extends APIError {\n\toverride readonly status: 401 = 401;\n}\n\nexport class PermissionDeniedError extends APIError {\n\toverride readonly status: 403 = 403;\n}\n\nexport class NotFoundError extends APIError {\n\toverride readonly status: 404 = 404;\n}\n\nexport class ConflictError extends APIError {\n\toverride readonly status: 409 = 409;\n}\n\nexport class UnprocessableEntityError extends APIError {\n\toverride readonly status: 422 = 422;\n}\n\nexport class RateLimitError extends APIError {\n\toverride readonly status: 429 = 429;\n}\n\nexport class InternalServerError extends APIError {}\n","type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;\n\nexport type ServerSentEvent = {\n\tevent: string | null;\n\tdata: string;\n\traw: string[];\n};\n\nexport class Stream<Item> implements AsyncIterable<Item> {\n\tcontroller: AbortController;\n\n\tconstructor(\n\t\tprivate iterator: () => AsyncIterator<Item>,\n\t\tcontroller: AbortController,\n\t) {\n\t\tthis.controller = controller;\n\t}\n\n\t/**\n\t * Creates a stream of AsyncIterator from a Server-Sent Events (SSE) response.\n\t *\n\t * @template Item - The type of items in the stream.\n\t * @param {Response} response - The SSE response object.\n\t * @param {AbortController} controller - The abort controller used to cancel the ongoing request.\n\t * @returns {Stream<AsyncIterator<Item, any, undefined>>} - The stream created from the SSE response.\n\t * @throws {Error} - If the stream has already been consumed.\n\t */\n\tstatic fromSSEResponse<Item>(\n\t\tresponse: Response,\n\t\tcontroller: AbortController,\n\t) {\n\t\tlet consumed = false;\n\n\t\tasync function* iterator(): AsyncIterator<Item, any, undefined> {\n\t\t\tif (consumed) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Cannot iterate over a consumed stream, use `.tee()` to split the stream.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tconsumed = true;\n\t\t\tlet done = false;\n\t\t\ttry {\n\t\t\t\tfor await (const sse of _iterSSEMessages(\n\t\t\t\t\tresponse,\n\t\t\t\t\tcontroller,\n\t\t\t\t)) {\n\t\t\t\t\tif (done) continue;\n\n\t\t\t\t\tif (sse.data.startsWith('[DONE]')) {\n\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (sse.event === null) {\n\t\t\t\t\t\tlet data;\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tdata = JSON.parse(sse.data);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t`Could not parse message into JSON:`,\n\t\t\t\t\t\t\t\tsse.data,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconsole.error(`From chunk:`, sse.raw);\n\t\t\t\t\t\t\tthrow e;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (data && data.error) {\n\t\t\t\t\t\t\tthrow new Error(data.error);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tyield data;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet data;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tdata = JSON.parse(sse.data);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t`Could not parse message into JSON:`,\n\t\t\t\t\t\t\t\tsse.data,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconsole.error(`From chunk:`, sse.raw);\n\t\t\t\t\t\t\tthrow e;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// TODO: Is this where the error should be thrown?\n\t\t\t\t\t\tif (sse.event == 'error') {\n\t\t\t\t\t\t\tthrow new Error(data.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield {event: sse.event, data: data} as any;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdone = true;\n\t\t\t} catch (e) {\n\t\t\t\t// If the user calls `stream.controller.abort()`, we should exit without throwing.\n\t\t\t\tif (e instanceof Error && e.name === 'AbortError') return;\n\t\t\t\tthrow e;\n\t\t\t} finally {\n\t\t\t\t// If the user `break`s, abort the ongoing request.\n\t\t\t\tif (!done) controller.abort();\n\t\t\t}\n\t\t}\n\n\t\treturn new Stream(iterator, controller);\n\t}\n\n\t/**\n\t * Generates a Stream from a newline-separated ReadableStream\n\t * where each item is a JSON value.\n\t *\n\t * @template Item - The type of items in the stream.\n\t * @param {ReadableStream} readableStream - The readable stream to create the stream from.\n\t * @param {AbortController} controller - The abort controller to control the stream.\n\t * @returns {Stream<Item>} - The created stream.\n\t */\n\tstatic fromReadableStream<Item>(\n\t\treadableStream: ReadableStream,\n\t\tcontroller: AbortController,\n\t) {\n\t\tlet consumed = false;\n\n\t\tasync function* iterLines(): AsyncGenerator<string, void, unknown> {\n\t\t\tconst lineDecoder = new LineDecoder();\n\n\t\t\tconst iter = readableStreamAsyncIterable<Bytes>(readableStream);\n\t\t\tfor await (const chunk of iter) {\n\t\t\t\tfor (const line of lineDecoder.decode(chunk)) {\n\t\t\t\t\tyield line;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const line of lineDecoder.flush()) {\n\t\t\t\tyield line;\n\t\t\t}\n\t\t}\n\n\t\tasync function* iterator(): AsyncIterator<Item, any, undefined> {\n\t\t\tif (consumed) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Cannot iterate over a consumed stream, use `.tee()` to split the stream.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tconsumed = true;\n\t\t\tlet done = false;\n\t\t\ttry {\n\t\t\t\tfor await (const line of iterLines()) {\n\t\t\t\t\tif (done) continue;\n\t\t\t\t\tif (line) yield JSON.parse(line);\n\t\t\t\t}\n\t\t\t\tdone = true;\n\t\t\t} catch (e) {\n\t\t\t\t// If the user calls `stream.controller.abort()`, we should exit without throwing.\n\t\t\t\tif (e instanceof Error && e.name === 'AbortError') return;\n\t\t\t\tthrow e;\n\t\t\t} finally {\n\t\t\t\t// If the user `break`s, abort the ongoing request.\n\t\t\t\tif (!done) controller.abort();\n\t\t\t}\n\t\t}\n\n\t\treturn new Stream(iterator, controller);\n\t}\n\n\t[Symbol.asyncIterator](): AsyncIterator<Item> {\n\t\treturn this.iterator();\n\t}\n\n\t/**\n\t * Splits the stream into two streams which can be\n\t * independently read from at different speeds.\n\t */\n\ttee(): [Stream<Item>, Stream<Item>] {\n\t\tconst left: Array<Promise<IteratorResult<Item>>> = [];\n\t\tconst right: Array<Promise<IteratorResult<Item>>> = [];\n\t\tconst iterator = this.iterator();\n\n\t\tconst teeIterator = (\n\t\t\tqueue: Array<Promise<IteratorResult<Item>>>,\n\t\t): AsyncIterator<Item> => {\n\t\t\treturn {\n\t\t\t\tnext: () => {\n\t\t\t\t\tif (queue.length === 0) {\n\t\t\t\t\t\tconst result = iterator.next();\n\t\t\t\t\t\tleft.push(result);\n\t\t\t\t\t\tright.push(result);\n\t\t\t\t\t}\n\t\t\t\t\treturn queue.shift()!;\n\t\t\t\t},\n\t\t\t};\n\t\t};\n\n\t\treturn [\n\t\t\tnew Stream(() => teeIterator(left), this.controller),\n\t\t\tnew Stream(() => teeIterator(right), this.controller),\n\t\t];\n\t}\n\n\t/**\n\t * Converts this stream to a newline-separated ReadableStream of\n\t * JSON stringified values in the stream which can be turned back into a Stream with `Stream.fromReadableStream()`.\n\t */\n\ttoReadableStream(): ReadableStream {\n\t\tconst self = this;\n\t\tlet iter: AsyncIterator<Item>;\n\t\tconst encoder = new TextEncoder();\n\n\t\treturn new ReadableStream({\n\t\t\tasync start() {\n\t\t\t\titer = self[Symbol.asyncIterator]();\n\t\t\t},\n\t\t\tasync pull(ctrl: any) {\n\t\t\t\ttry {\n\t\t\t\t\tconst {value, done} = await iter.next();\n\t\t\t\t\tif (done) return ctrl.close();\n\n\t\t\t\t\tconst bytes = encoder.encode(JSON.stringify(value) + '\\n');\n\n\t\t\t\t\tctrl.enqueue(bytes);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctrl.error(err);\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync cancel() {\n\t\t\t\tawait iter.return?.();\n\t\t\t},\n\t\t});\n\t}\n}\n\n/**\n * Asynchronously iterates over Server-Sent Event (SSE) messages from a response body.\n *\n * @param response - The response object containing the SSE messages.\n * @param controller - The AbortController used to abort the iteration if needed.\n * @returns An async generator that yields ServerSentEvent objects.\n * @throws Error if the response has no body.\n */\nexport async function* _iterSSEMessages(\n\tresponse: Response,\n\tcontroller: AbortController,\n): AsyncGenerator<ServerSentEvent, void, unknown> {\n\tif (!response.body) {\n\t\tcontroller.abort();\n\t\tthrow new Error(`Attempted to iterate over a response with no body`);\n\t}\n\n\tconst sseDecoder = new SSEDecoder();\n\tconst lineDecoder = new LineDecoder();\n\n\tconst iter = readableStreamAsyncIterable<Bytes>(response.body);\n\tfor await (const sseChunk of iterSSEChunks(iter)) {\n\t\tfor (const line of lineDecoder.decode(sseChunk)) {\n\t\t\tconst sse = sseDecoder.decode(line);\n\t\t\tif (sse) yield sse;\n\t\t}\n\t}\n\n\tfor (const line of lineDecoder.flush()) {\n\t\tconst sse = sseDecoder.decode(line);\n\t\tif (sse) yield sse;\n\t}\n}\n\n/**\n * Asynchronously iterates over SSE (Server-Sent Events) chunks and yields the data as Uint8Array.\n *\n * Given an async iterable iterator, iterates over it and yields full\n * SSE chunks, i.e. yields when a double new-line is encountered.\n *\n * @param iterator - The async iterator that provides the SSE chunks.\n * @returns An async generator that yields Uint8Array chunks.\n */\nasync function* iterSSEChunks(\n\titerator: AsyncIterableIterator<Bytes>,\n): AsyncGenerator<Uint8Array> {\n\tlet data = new Uint8Array();\n\n\tfor await (const chunk of iterator) {\n\t\tif (chunk == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst binaryChunk =\n\t\t\tchunk instanceof ArrayBuffer\n\t\t\t\t? new Uint8Array(chunk)\n\t\t\t\t: typeof chunk === 'string'\n\t\t\t\t\t? new TextEncoder().encode(chunk)\n\t\t\t\t\t: chunk;\n\n\t\tlet newData = new Uint8Array(data.length + binaryChunk.length);\n\t\tnewData.set(data);\n\t\tnewData.set(binaryChunk, data.length);\n\t\tdata = newData;\n\n\t\tlet patternIndex;\n\t\twhile ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {\n\t\t\tyield data.slice(0, patternIndex);\n\t\t\tdata = data.slice(patternIndex);\n\t\t}\n\t}\n\n\tif (data.length > 0) {\n\t\tyield data;\n\t}\n}\n\n/**\n * Finds the index of the first occurrence of a double newline pattern (\\r\\r, \\n\\n, \\r\\n\\r\\n) in the given buffer.\n *\n * @param buffer - The buffer to search for the double newline pattern.\n * @returns The index right after the first occurrence of the double newline pattern, or -1 if not found.\n */\nfunction findDoubleNewlineIndex(buffer: Uint8Array): number {\n\t// This function searches the buffer for the end patterns (\\r\\r, \\n\\n, \\r\\n\\r\\n)\n\t// and returns the index right after the first occurrence of any pattern,\n\t// or -1 if none of the patterns are found.\n\tconst newline = 0x0a; // \\n\n\tconst carriage = 0x0d; // \\r\n\n\tfor (let i = 0; i < buffer.length - 2; i++) {\n\t\tif (buffer[i] === newline && buffer[i + 1] === newline) {\n\t\t\t// \\n\\n\n\t\t\treturn i + 2;\n\t\t}\n\t\tif (buffer[i] === carriage && buffer[i + 1] === carriage) {\n\t\t\t// \\r\\r\n\t\t\treturn i + 2;\n\t\t}\n\t\tif (\n\t\t\tbuffer[i] === carriage &&\n\t\t\tbuffer[i + 1] === newline &&\n\t\t\ti + 3 < buffer.length &&\n\t\t\tbuffer[i + 2] === carriage &&\n\t\t\tbuffer[i + 3] === newline\n\t\t) {\n\t\t\t// \\r\\n\\r\\n\n\t\t\treturn i + 4;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/**\n * Represents a Server-Sent Event (SSE) decoder.\n */\nclass SSEDecoder {\n\tprivate data: string[];\n\tprivate event: string | null;\n\tprivate chunks: string[];\n\n\tconstructor() {\n\t\tthis.event = null;\n\t\tthis.data = [];\n\t\tthis.chunks = [];\n\t}\n\n\t/**\n\t * Decodes a line of text and returns a ServerSentEvent object if a complete event is found.\n\t * @param line - The line of text to decode.\n\t * @returns A ServerSentEvent object if a complete event is found, otherwise null.\n\t */\n\tdecode(line: string) {\n\t\tif (line.endsWith('\\r')) {\n\t\t\tline = line.substring(0, line.length - 1);\n\t\t}\n\n\t\tif (!line) {\n\t\t\t// empty line and we didn't previously encounter any messages\n\t\t\tif (!this.event && !this.data.length) return null;\n\n\t\t\tconst sse: ServerSentEvent = {\n\t\t\t\tevent: this.event,\n\t\t\t\tdata: this.data.join('\\n'),\n\t\t\t\traw: this.chunks,\n\t\t\t};\n\n\t\t\tthis.event = null;\n\t\t\tthis.data = [];\n\t\t\tthis.chunks = [];\n\n\t\t\treturn sse;\n\t\t}\n\n\t\tthis.chunks.push(line);\n\n\t\tif (line.startsWith(':')) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet [fieldname, _, value] = partition(line, ':');\n\n\t\tif (value.startsWith(' ')) {\n\t\t\tvalue = value.substring(1);\n\t\t}\n\n\t\tif (fieldname === 'event') {\n\t\t\tthis.event = value;\n\t\t} else if (fieldname === 'data') {\n\t\t\tthis.data.push(value);\n\t\t}\n\n\t\treturn null;\n\t}\n}\n\n/**\n * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally\n * reading lines from text.\n *\n * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258\n */\nclass LineDecoder {\n\t// prettier-ignore\n\tstatic NEWLINE_CHARS = new Set(['\\n', '\\r']);\n\tstatic NEWLINE_REGEXP = /\\r\\n|[\\n\\r]/g;\n\n\tbuffer: string[];\n\ttrailingCR: boolean;\n\ttextDecoder: any; // TextDecoder found in browsers; not typed to avoid pulling in either \"dom\" or \"node\" types.\n\n\tconstructor() {\n\t\tthis.buffer = [];\n\t\tthis.trailingCR = false;\n\t}\n\n\tdecode(chunk: Bytes): string[] {\n\t\tlet text = this.decodeText(chunk);\n\n\t\tif (this.trailingCR) {\n\t\t\ttext = '\\r' + text;\n\t\t\tthis.trailingCR = false;\n\t\t}\n\t\tif (text.endsWith('\\r')) {\n\t\t\tthis.trailingCR = true;\n\t\t\ttext = text.slice(0, -1);\n\t\t}\n\n\t\tif (!text) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst trailingNewline = LineDecoder.NEWLINE_CHARS.has(\n\t\t\ttext[text.length - 1] || '',\n\t\t);\n\t\tlet lines = text.split(LineDecoder.NEWLINE_REGEXP);\n\n\t\t// if there is a trailing new line then the last entry will be an empty\n\t\t// string which we don't care about\n\t\tif (trailingNewline) {\n\t\t\tlines.pop();\n\t\t}\n\n\t\tif (lines.length === 1 && !trailingNewline) {\n\t\t\tthis.buffer.push(lines[0]!);\n\t\t\treturn [];\n\t\t}\n\n\t\tif (this.buffer.length > 0) {\n\t\t\tlines = [this.buffer.join('') + lines[0], ...lines.slice(1)];\n\t\t\tthis.buffer = [];\n\t\t}\n\n\t\tif (!trailingNewline) {\n\t\t\tthis.buffer = [lines.pop() || ''];\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tdecodeText(bytes: Bytes): string {\n\t\tif (bytes == null) return '';\n\t\tif (typeof bytes === 'string') return bytes;\n\n\t\t// Node:\n\t\tif (typeof Buffer !== 'undefined') {\n\t\t\tif (bytes instanceof Buffer) {\n\t\t\t\treturn bytes.toString();\n\t\t\t}\n\t\t\tif (bytes instanceof Uint8Array) {\n\t\t\t\treturn Buffer.from(bytes).toString();\n\t\t\t}\n\n\t\t\tthrow new Error(\n\t\t\t\t`Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global \"Buffer\" defined, which this library assumes to be Node. Please report this error.`,\n\t\t\t);\n\t\t}\n\n\t\t// Browser\n\t\tif (typeof TextDecoder !== 'undefined') {\n\t\t\tif (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) {\n\t\t\t\tthis.textDecoder ??= new TextDecoder('utf8');\n\t\t\t\treturn this.textDecoder.decode(bytes);\n\t\t\t}\n\n\t\t\tthrow new Error(\n\t\t\t\t`Unexpected: received non-Uint8Array/ArrayBuffer (${\n\t\t\t\t\t(bytes as any).constructor.name\n\t\t\t\t}) in a web platform. Please report this error.`,\n\t\t\t);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`,\n\t\t);\n\t}\n\n\tflush(): string[] {\n\t\tif (!this.buffer.length && !this.trailingCR) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lines = [this.buffer.join('')];\n\t\tthis.buffer = [];\n\t\tthis.trailingCR = false;\n\t\treturn lines;\n\t}\n}\n\n/**\n * Decodes an array of chunks into an array of lines.\n *\n * This is an internal helper function that's just used for testing\n *\n * @param chunks - An array of chunks to decode.\n * @returns An array of decoded lines.\n */\nexport function _decodeChunks(chunks: string[]): string[] {\n\tconst decoder = new LineDecoder();\n\tconst lines: string[] = [];\n\tfor (const chunk of chunks) {\n\t\tlines.push(...decoder.decode(chunk));\n\t}\n\n\treturn lines;\n}\n\nfunction partition(str: string, delimiter: string): [string, string, string] {\n\tconst index = str.indexOf(delimiter);\n\tif (index !== -1) {\n\t\treturn [\n\t\t\tstr.substring(0, index),\n\t\t\tdelimiter,\n\t\t\tstr.substring(index + delimiter.length),\n\t\t];\n\t}\n\n\treturn [str, '', ''];\n}\n\n/**\n * Most browsers don't yet have async iterable support for ReadableStream,\n * and Node has a very different way of reading bytes from its \"ReadableStream\".\n *\n * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport function readableStreamAsyncIterable<T>(\n\tstream: any,\n): AsyncIterableIterator<T> {\n\tif (stream[Symbol.asyncIterator]) return stream;\n\n\tconst reader = stream.getReader();\n\treturn {\n\t\tasync next() {\n\t\t\ttry {\n\t\t\t\tconst result = await reader.read();\n\t\t\t\tif (result?.done) reader.releaseLock(); // release lock when stream becomes closed\n\t\t\t\treturn result;\n\t\t\t} catch (e) {\n\t\t\t\treader.releaseLock(); // release lock when stream becomes errored\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t},\n\t\tasync return() {\n\t\t\tconst cancelPromise = reader.cancel();\n\t\t\treader.releaseLock();\n\t\t\tawait cancelPromise;\n\t\t\treturn {done: true, value: undefined};\n\t\t},\n\t\t[Symbol.asyncIterator]() {\n\t\t\treturn this;\n\t\t},\n\t};\n}\n","import {GENERATION_ENDPOINTS} from '@/data/constants';\nimport {Headers} from './../../types'; // Ensure this import is correct\nimport {APIConnectionError, APIError} from './errors';\nimport {Stream} from './stream';\n\ninterface RequestOptions {\n\tendpoint: string;\n\tmethod: string;\n\theaders?: Headers;\n\tbody?: any;\n\tstream?: boolean;\n}\n\ninterface RequestConfig {\n\tapiKey: string;\n\tbaseUrl: string;\n\ttimeout?: number;\n}\n\ninterface SendOptions extends RequestOptions {\n\tendpoint: string;\n}\n\ninterface MakeRequestParams {\n\turl: string;\n\toptions: RequestOptions;\n\theaders: Record<string, string>;\n}\n\ninterface HandleGenerateResponseParams {\n\tresponse: Response;\n\tthreadId: string | null;\n\trawResponse: boolean;\n\tendpoint?: string;\n}\n\nexport class Request {\n\tprivate config: RequestConfig;\n\n\tconstructor(config: RequestConfig) {\n\t\tthis.config = config;\n\t}\n\n\t// Main send function\n\tprivate async send<T>({endpoint, ...options}: SendOptions): Promise<T> {\n\t\tconst url = this.buildUrl({endpoint});\n\t\tconst headers = this.buildHeaders({headers: options.headers});\n\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await this.makeRequest({url, options, headers});\n\t\t} catch (error) {\n\t\t\tthrow new APIConnectionError({\n\t\t\t\tcause: error instanceof Error ? error : undefined,\n\t\t\t});\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tawait this.handleErrorResponse({response});\n\t\t}\n\n\t\tconst isLllmGenerationEndpoint =\n\t\t\tGENERATION_ENDPOINTS.includes(endpoint);\n\n\t\t// All endpoints should return headers if rawResponse is true\n\t\tif (!isLllmGenerationEndpoint && options.body?.rawResponse) {\n\t\t\tconst responseData = await response.json();\n\t\t\t// For array responses, attach rawResponse as a hidden property to preserve response type as array\n\t\t\t// while still providing access to response headers when needed\n\t\t\tif (Array.isArray(responseData)) {\n\t\t\t\tObject.defineProperty(responseData, 'rawResponse', {\n\t\t\t\t\tvalue: {\n\t\t\t\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\t\t\t},\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\twritable: true,\n\t\t\t\t});\n\t\t\t\treturn responseData as T;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...responseData,\n\t\t\t\trawResponse: {\n\t\t\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\t\t},\n\t\t\t} as T;\n\t\t}\n\n\t\tif (isLllmGenerationEndpoint) {\n\t\t\tconst threadId = response.headers.get('lb-thread-id');\n\n\t\t\tif (!options.body) {\n\t\t\t\treturn this.handleRunResponse({\n\t\t\t\t\tresponse,\n\t\t\t\t\tthreadId: null,\n\t\t\t\t\trawResponse: options.body?.rawResponse ?? false,\n\t\t\t\t\tendpoint,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (options.body?.stream && url.includes('run')) {\n\t\t\t\treturn this.handleRunResponseStream({\n\t\t\t\t\tresponse,\n\t\t\t\t\trawResponse: options.body.rawResponse,\n\t\t\t\t}) as T;\n\t\t\t}\n\n\t\t\tif (options.body.stream) {\n\t\t\t\treturn this.handleStreamResponse({response}) as T;\n\t\t\t}\n\n\t\t\treturn this.handleRunResponse({\n\t\t\t\tresponse,\n\t\t\t\tthreadId,\n\t\t\t\trawResponse: options.body?.rawResponse ?? false,\n\t\t\t\tendpoint,\n\t\t\t});\n\t\t} else {\n\t\t\tconst res = response.json();\n\t\t\treturn res as T;\n\t\t}\n\t}\n\n\tprivate buildUrl({endpoint}: {endpoint: string}): string {\n\t\treturn `${this.config.baseUrl}${endpoint}`;\n\t}\n\n\tprivate buildHeaders({\n\t\theaders,\n\t}: {\n\t\theaders?: Record<string, string>;\n\t}): Record<string, string> {\n\t\treturn {\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t...headers,\n\t\t};\n\t}\n\n\tprivate async makeRequest({\n\t\turl,\n\t\toptions,\n\t\theaders,\n\t}: MakeRequestParams): Promise<Response> {\n\t\treturn fetch(url, {\n\t\t\tmethod: options.method,\n\t\t\theaders,\n\t\t\tbody: JSON.stringify(options.body),\n\t\t\t// signal: AbortSignal.timeout(this.config.timeout || 30000),\n\t\t});\n\t}\n\n\tprivate async handleErrorResponse({\n\t\tresponse,\n\t}: {\n\t\tresponse: Response;\n\t}): Promise<never> {\n\t\tlet errorBody;\n\t\ttry {\n\t\t\terrorBody = await response.json();\n\t\t} catch {\n\t\t\terrorBody = await response.text();\n\t\t}\n\t\tthrow APIError.generate(\n\t\t\tresponse.status,\n\t\t\terrorBody,\n\t\t\tresponse.statusText,\n\t\t\tresponse.headers as unknown as Headers,\n\t\t);\n\t}\n\n\tprivate handleStreamResponse({response}: {response: Response}): {\n\t\tstream: any;\n\t\tthreadId: string | null;\n\t} {\n\t\tconst controller = new AbortController();\n\t\tconst stream = Stream.fromSSEResponse(response, controller);\n\t\treturn {stream, threadId: response.headers.get('lb-thread-id')};\n\t}\n\n\tprivate handleRunResponseStream({\n\t\tresponse,\n\t\trawResponse,\n\t}: {\n\t\tresponse: Response;\n\t\trawResponse?: boolean;\n\t}): {\n\t\tstream: any;\n\t\tthreadId: string | null;\n\t\trawResponse?: {\n\t\t\theaders: Record<string, string>;\n\t\t};\n\t} {\n\t\tconst controller = new AbortController();\n\t\tconst streamSSE = Stream.fromSSEResponse(response, controller);\n\t\tconst stream = streamSSE.toReadableStream();\n\n\t\tconst result: {\n\t\t\tstream: ReadableStream<any>;\n\t\t\tthreadId: string | null;\n\t\t\trawResponse?: {\n\t\t\t\theaders: Record<string, string>;\n\t\t\t};\n\t\t} = {\n\t\t\tstream,\n\t\t\tthreadId: response.headers.get('lb-thread-id'),\n\t\t};\n\t\tif (rawResponse) {\n\t\t\tresult.rawResponse = {\n\t\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\t};\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate async handleRunResponse({\n\t\tresponse,\n\t\tthreadId,\n\t\trawResponse,\n\t\tendpoint,\n\t}: HandleGenerateResponseParams): Promise<any> {\n\t\tlet isAgentRun = false;\n\t\tif (endpoint === '/v1/agent/run') isAgentRun = true;\n\n\t\tconst generateResponse = await response.json();\n\n\t\tconst buildResponse = generateResponse.raw\n\t\t\t? isAgentRun\n\t\t\t\t? {\n\t\t\t\t\t\toutput: generateResponse.output,\n\t\t\t\t\t\t...generateResponse.raw,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tcompletion: generateResponse.completion,\n\t\t\t\t\t\t...generateResponse.raw,\n\t\t\t\t\t}\n\t\t\t: generateResponse;\n\n\t\tconst result: any = {\n\t\t\t...buildResponse,\n\t\t};\n\n\t\tif (threadId) {\n\t\t\tresult.threadId = threadId;\n\t\t}\n\n\t\tif (rawResponse) {\n\t\t\tresult.rawResponse = {\n\t\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\t};\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync post<T>(options: Omit<RequestOptions, 'method'>): Promise<T> {\n\t\treturn this.send<T>({...options, method: 'POST'});\n\t}\n\n\tasync get<T>(options: Omit<RequestOptions, 'method' | 'body'>): Promise<T> {\n\t\treturn this.send<T>({...options, method: 'GET'});\n\t}\n\n\tasync put<T>(options: Omit<RequestOptions, 'method'>): Promise<T> {\n\t\treturn this.send<T>({...options, method: 'PUT'});\n\t}\n\n\tasync delete<T>(\n\t\toptions: Omit<RequestOptions, 'method' | 'body'>,\n\t): Promise<T> {\n\t\treturn this.send<T>({...options, method: 'DELETE'});\n\t}\n}\n","export interface Trace {\n\tname: string;\n\tstartTime: number;\n\tendTime?: number;\n\tduration?: number;\n\tsteps: StepTrace[];\n\terror?: string;\n}\n\nexport interface StepTrace {\n\tname: string;\n\toutput: any;\n\terror?: string;\n\ttraces: string[] | null;\n\tduration: number;\n\tstartTime: number;\n\tendTime: number;\n}\n\nexport type TraceType =\n\t| 'workflow'\n\t| 'agent'\n\t| 'chunk'\n\t| 'memory'\n\t| 'parse'\n\t| 'embed';\n\nexport type PrimitiveTrace =\n\t| {chunk: any}\n\t| {agent: any}\n\t| {memory: any}\n\t| {parse: any}\n\t| {embed: any}\n\t| {workflow: WorkflowTrace; entityAuthId: string};\n\ntype WorkflowTrace = {\n\tcreatedAt: string;\n\tid: string;\n\tagentWorkflowId: string;\n\tname: string;\n\tstartTime: number;\n\tendTime?: number;\n\tduration?: number;\n\tsteps: StepTrace[];\n\terror?: string;\n};\n\nexport class TraceManager {\n\tprivate traces: Map<string, PrimitiveTrace> = new Map();\n\n\tcreateTrace(type: TraceType, traceData: any = {}): string {\n\t\tconst traceId = crypto.randomUUID();\n\t\tlet trace: PrimitiveTrace;\n\t\tconst createdAt = new Date().toISOString();\n\t\tconst agentWorkflowId =\n\t\t\ttypeof process !== 'undefined' && process.env?.LANGBASE_AGENT_ID\n\t\t\t\t? process.env.LANGBASE_AGENT_ID\n\t\t\t\t: '';\n\n\t\tif (type === 'workflow') {\n\t\t\ttrace = {\n\t\t\t\tworkflow: {\n\t\t\t\t\tcreatedAt,\n\t\t\t\t\tid: traceId,\n\t\t\t\t\tagentWorkflowId,\n\t\t\t\t\tname: traceData.name || '',\n\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t\tsteps: [],\n\t\t\t\t},\n\t\t\t\tentityAuthId: '',\n\t\t\t};\n\t\t} else if (type === 'agent') {\n\t\t\ttrace = {agent: {...traceData, createdAt, id: traceId}};\n\t\t} else if (type === 'chunk') {\n\t\t\ttrace = {chunk: {...traceData, createdAt, id: traceId}};\n\t\t} else if (type === 'memory') {\n\t\t\ttrace = {memory: {...traceData, createdAt, id: traceId}};\n\t\t} else if (type === 'parse') {\n\t\t\ttrace = {parse: {...traceData, createdAt, id: traceId}};\n\t\t} else if (type === 'embed') {\n\t\t\ttrace = {embed: {...traceData, createdAt, id: traceId}};\n\t\t} else {\n\t\t\tthrow new Error('Unknown trace type');\n\t\t}\n\t\tthis.traces.set(traceId, trace);\n\t\treturn traceId;\n\t}\n\n\taddStep(traceId: string, step: StepTrace) {\n\t\tconst trace = this.traces.get(traceId);\n\t\tif (trace && 'workflow' in trace) {\n\t\t\ttrace.workflow.steps.push(step);\n\t\t}\n\t}\n\n\tendTrace(traceId: string) {\n\t\tconst trace = this.traces.get(traceId);\n\t\tif (trace && 'workflow' in trace) {\n\t\t\ttrace.workflow.endTime = Date.now();\n\t\t\ttrace.workflow.duration =\n\t\t\t\ttrace.workflow.endTime - trace.workflow.startTime;\n\t\t}\n\t}\n\n\tgetTrace(traceId: string): PrimitiveTrace | undefined {\n\t\treturn this.traces.get(traceId);\n\t}\n\n\tprintTrace(traceId: string) {\n\t\tconst trace = this.traces.get(traceId);\n\t\tif (!trace) return;\n\t\tif ('workflow' in trace) {\n\t\t\tconst wf = trace.workflow;\n\t\t\tconst duration = wf.endTime\n\t\t\t\t? wf.endTime - wf.startTime\n\t\t\t\t: Date.now() - wf.startTime;\n\t\t\tconsole.log('\\nšŸ“Š Workflow Trace:');\n\t\t\tconsole.log(`Name: ${wf.name}`);\n\t\t\tconsole.log(`Duration: ${duration}ms`);\n\t\t\tconsole.log(`Start Time: ${new Date(wf.startTime).toISOString()}`);\n\t\t\tif (wf.endTime) {\n\t\t\t\tconsole.log(`End Time: ${new Date(wf.endTime).toISOString()}`);\n\t\t\t}\n\t\t\tconsole.log('\\nSteps:');\n\t\t\twf.steps.forEach(step => {\n\t\t\t\tconsole.log(`\\n Step: ${step.name}`);\n\t\t\t\tconsole.log(` Duration: ${step.duration}ms`);\n\t\t\t\tif (step.traces && step.traces.length > 0) {\n\t\t\t\t\tconsole.log(` Traces:`, step.traces);\n\t\t\t\t}\n\t\t\t\tconsole.log(` Output:`, step.output);\n\t\t\t});\n\t\t} else {\n\t\t\tconsole.log('\\nšŸ“Š Primitive Trace:');\n\t\t\tconsole.dir(trace, {depth: 4});\n\t\t}\n\t}\n}\n","import {TraceManager, StepTrace} from './trace';\nimport {Langbase} from './langbase';\n\n// Cross-platform global object\nconst _global: any =\n\ttypeof global !== 'undefined'\n\t\t? global\n\t\t: typeof window !== 'undefined'\n\t\t\t? window\n\t\t\t: typeof self !== 'undefined'\n\t\t\t\t? self\n\t\t\t\t: typeof globalThis !== 'undefined'\n\t\t\t\t\t? globalThis\n\t\t\t\t\t: {};\n\n// Global state for tracing\n_global._activeTraceCollector = _global._activeTraceCollector || null;\n_global._workflowDebugEnabled = _global._workflowDebugEnabled || false;\n\ntype WorkflowContext = {\n\toutputs: Record<string, any>;\n};\n\ntype RetryConfig = {\n\tlimit: number;\n\tdelay: number;\n\tbackoff: 'exponential' | 'linear' | 'fixed';\n};\n\ntype StepConfig<T = any> = {\n\tid: string;\n\ttimeout?: number;\n\tretries?: RetryConfig;\n\trun: () => Promise<T>;\n};\n\ntype WorkflowConfig = {\n\tdebug?: boolean;\n\tname?: string;\n\tlangbase?: Langbase; // Now optional for backward compatibility\n};\n\nclass TimeoutError extends Error {\n\tconstructor(stepId: string, timeout: number) {\n\t\tsuper(`Step \"${stepId}\" timed out after ${timeout}ms`);\n\t\tthis.name = 'TimeoutError';\n\t}\n}\n\nexport class Workflow {\n\tprivate context: WorkflowContext;\n\tprivate debug: boolean;\n\tprivate name: string;\n\tprivate traceManager?: TraceManager; // Optional\n\tprivate traceId?: string; // Optional\n\tprivate langbase?: Langbase; // Optional\n\n\tprivate originalMethods: Map<string, Function> = new Map();\n\tpublic readonly step: <T = any>(config: StepConfig<T>) => Promise<T>;\n\n\tconstructor(config?: WorkflowConfig) {\n\t\tthis.context = {outputs: {}};\n\t\tthis.debug = config?.debug ?? false;\n\t\tthis.name = config?.name ?? 'workflow';\n\t\tthis.langbase = config?.langbase;\n\n\t\t// Only initialize tracing if langbase is provided\n\t\tif (this.langbase) {\n\t\t\tthis.traceManager = new TraceManager();\n\t\t\tthis.traceId = this.traceManager.createTrace('workflow', {\n\t\t\t\tname: this.name,\n\t\t\t});\n\t\t\t// Set global debug flag\n\t\t\t_global._workflowDebugEnabled = this.debug;\n\t\t}\n\t\tthis.step = this._step.bind(this);\n\t}\n\n\t/**\n\t * Replace a method in the Langbase instance with a traced version\n\t */\n\tprivate interceptMethod(obj: any, method: string, path: string = ''): void {\n\t\tif (!this.langbase) return; // Skip if no langbase instance provided (no tracing)\n\t\tif (!obj || typeof obj[method] !== 'function') return;\n\n\t\tconst fullPath = path ? `${path}.${method}` : method;\n\t\tconst originalMethod = obj[method];\n\n\t\t// Only replace if not already intercepted\n\t\tif (!this.originalMethods.has(fullPath)) {\n\t\t\tthis.originalMethods.set(fullPath, originalMethod);\n\n\t\t\tconst debug = this.debug;\n\n\t\t\t// Replace with intercepted version\n\t\t\tobj[method] = async function (...args: any[]) {\n\t\t\t\t// Add custom arguments for tracing\n\t\t\t\t// Add rawResponse to the options if it's an object\n\t\t\t\tconst lastArg = args[args.length - 1];\n\t\t\t\tconst newArgs = [...args];\n\n\t\t\t\tif (lastArg && typeof lastArg === 'object') {\n\t\t\t\t\tnewArgs[newArgs.length - 1] = {\n\t\t\t\t\t\t...lastArg,\n\t\t\t\t\t\trawResponse: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\t// Append a new object if the last argument is not an object\n\t\t\t\telse {\n\t\t\t\t\tnewArgs.push({rawResponse: true});\n\t\t\t\t}\n\n\t\t\t\tconst result = await originalMethod.apply(this, newArgs);\n\n\t\t\t\t// Process result for tracing if we have an active collector\n\t\t\t\tif (_global._activeTraceCollector) {\n\t\t\t\t\t// Extract or create traceId\n\t\t\t\t\tlet traceId: string | undefined;\n\n\t\t\t\t\t// Check if result is an object with response headers\n\t\t\t\t\tif (result && typeof result === 'object') {\n\t\t\t\t\t\t// Extract from response headers\n\t\t\t\t\t\tif ('rawResponse' in result && result.rawResponse) {\n\t\t\t\t\t\t\t// Check for lb-trace-id in headers\n\t\t\t\t\t\t\tif (result.rawResponse.headers['lb-trace-id']) {\n\t\t\t\t\t\t\t\t// Plain object headers\n\t\t\t\t\t\t\t\ttraceId =\n\t\t\t\t\t\t\t\t\tresult.rawResponse.headers['lb-trace-id'];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Notify collector if traceId was found\n\t\t\t\t\t\tif (traceId && _global._activeTraceCollector) {\n\t\t\t\t\t\t\tif (debug) {\n\t\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t\t`šŸ” Trace ID extracted: ${traceId}`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_global._activeTraceCollector(traceId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Restore all original methods that were intercepted\n\t */\n\tprivate restoreOriginalMethods(): void {\n\t\tif (!this.langbase) return; // Skip if no langbase (no tracing)\n\t\tthis.originalMethods.forEach((originalMethod, path) => {\n\t\t\t// Parse the path to find the object and method\n\t\t\tconst parts = path.split('.');\n\t\t\tconst methodName = parts.pop()!;\n\t\t\tlet obj: any = this.langbase;\n\n\t\t\t// Navigate to the correct object\n\t\t\tfor (const part of parts) {\n\t\t\t\tif (obj && typeof obj === 'object' && part in obj) {\n\t\t\t\t\tobj = obj[part as keyof typeof obj]; // Type safe access\n\t\t\t\t} else {\n\t\t\t\t\treturn; // Skip if path no longer exists\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Restore original method\n\t\t\tif (\n\t\t\t\tobj &&\n\t\t\t\tmethodName in obj &&\n\t\t\t\ttypeof obj[methodName] === 'function'\n\t\t\t) {\n\t\t\t\tobj[methodName] = originalMethod;\n\t\t\t}\n\t\t});\n\n\t\t// Clear the map\n\t\tthis.originalMethods.clear();\n\t}\n\n\t/**\n\t * Intercept all important methods in the Langbase instance\n\t */\n\tprivate setupMethodInterceptors(): void {\n\t\tif (!this.langbase) return; // Skip if no langbase (no tracing)\n\t\t// Agent methods\n\t\tthis.interceptMethod(this.langbase.agent, 'run', 'agent');\n\n\t\t// Pipes methods\n\t\tthis.interceptMethod(this.langbase.pipes, 'run', 'pipes');\n\t\tthis.interceptMethod(this.langbase.pipe, 'run', 'pipe');\n\n\t\t// Memory methods\n\t\tif (this.langbase.memories) {\n\t\t\tthis.interceptMethod(\n\t\t\t\tthis.langbase.memories,\n\t\t\t\t'retrieve',\n\t\t\t\t'memories',\n\t\t\t);\n\t\t}\n\t\tif (this.langbase.memory) {\n\t\t\tthis.interceptMethod(this.langbase.memory, 'retrieve', 'memory');\n\t\t}\n\n\t\t// Tool methods\n\t\tif (this.langbase.tools) {\n\t\t\tthis.interceptMethod(this.langbase.tools, 'webSearch', 'tools');\n\t\t\tthis.interceptMethod(this.langbase.tools, 'crawl', 'tools');\n\t\t}\n\t\tif (this.langbase.tool) {\n\t\t\tthis.interceptMethod(this.langbase.tool, 'webSearch', 'tool');\n\t\t\tthis.interceptMethod(this.langbase.tool, 'crawl', 'tool');\n\t\t}\n\n\t\t// Top-level methods\n\t\tthis.interceptMethod(this.langbase, 'embed');\n\t\tthis.interceptMethod(this.langbase, 'chunk');\n\t\tthis.interceptMethod(this.langbase, 'parse');\n\t}\n\n\tprivate async _step<T = any>(config: StepConfig<T>): Promise<T> {\n\t\tconst stepStartTime = Date.now();\n\t\t// Initialize an array to collect trace IDs\n\t\tconst stepTraces: string[] = [];\n\n\t\t// Function to collect trace IDs\n\t\tconst collectTrace = (traceId: string) => {\n\t\t\tif (this.debug) {\n\t\t\t\tconsole.log(`šŸ“‹ Collected trace ID: ${traceId}`);\n\t\t\t}\n\t\t\tstepTraces.push(traceId);\n\t\t};\n\n\t\tif (this.debug) {\n\t\t\tconsole.log(`\\nšŸ”„ Starting step: ${config.id}`);\n\t\t\tconsole.time(`ā±ļø Step ${config.id}`);\n\t\t\tif (config.timeout) console.log(`ā³ Timeout: ${config.timeout}ms`);\n\t\t\tif (config.retries)\n\t\t\t\tconsole.log(`šŸ”„ Retries: ${JSON.stringify(config.retries)}`);\n\t\t}\n\n\t\tlet lastError: Error | null = null;\n\t\tlet attempt = 1;\n\t\tconst maxAttempts = config.retries?.limit\n\t\t\t? config.retries.limit + 1\n\t\t\t: 1;\n\n\t\t// Set up method interceptors before running the step (only if tracing)\n\t\tif (this.langbase) this.setupMethodInterceptors();\n\n\t\t// Set the global active trace collector (only if tracing)\n\t\tconst previousTraceCollector = _global._activeTraceCollector;\n\t\tif (this.langbase) _global._activeTraceCollector = collectTrace;\n\n\t\ttry {\n\t\t\t// Execute the step function directly\n\t\t\tlet stepPromise: Promise<T> = config.run();\n\n\t\t\t// Apply timeout if configured\n\t\t\tif (config.timeout) {\n\t\t\t\tstepPromise = this.withTimeout({\n\t\t\t\t\tpromise: stepPromise,\n\t\t\t\t\ttimeout: config.timeout,\n\t\t\t\t\tstepId: config.id,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Wait for the step to complete\n\t\t\tconst result = await stepPromise;\n\n\t\t\t// Store step result in context\n\t\t\tthis.context.outputs[config.id] = result;\n\n\t\t\tif (this.debug) {\n\t\t\t\tconsole.timeEnd(`ā±ļø Step ${config.id}`);\n\t\t\t\tconsole.log(`šŸ“¤ Output:`, result);\n\n\t\t\t\tif (stepTraces.length > 0) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`šŸ“‹ Trace IDs (${stepTraces.length}):`,\n\t\t\t\t\t\tstepTraces,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`šŸ” No trace IDs captured for this step`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create step trace (only if tracing)\n\t\t\tif (this.langbase && this.traceManager && this.traceId) {\n\t\t\t\tconst stepEndTime = Date.now();\n\t\t\t\tconst stepTrace: StepTrace = {\n\t\t\t\t\tname: config.id,\n\t\t\t\t\toutput: result,\n\t\t\t\t\ttraces: stepTraces.length > 0 ? stepTraces : null,\n\t\t\t\t\tduration: stepEndTime - stepStartTime,\n\t\t\t\t\tstartTime: stepStartTime,\n\t\t\t\t\tendTime: stepEndTime,\n\t\t\t\t};\n\t\t\t\tthis.traceManager.addStep(this.traceId, stepTrace);\n\t\t\t}\n\n\t\t\t// Restore original methods and trace collector (only if tracing)\n\t\t\tif (this.langbase) {\n\t\t\t\tthis.restoreOriginalMethods();\n\t\t\t\t_global._activeTraceCollector = previousTraceCollector;\n\t\t\t}\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Restore original methods and trace collector on error (only if tracing)\n\t\t\tif (this.langbase) {\n\t\t\t\tthis.restoreOriginalMethods();\n\t\t\t\t_global._activeTraceCollector = previousTraceCollector;\n\t\t\t}\n\n\t\t\t// Store error for potential retry or final throw\n\t\t\tlastError = error as Error;\n\n\t\t\t// If retries are configured, try again\n\t\t\tif (attempt < maxAttempts) {\n\t\t\t\tconst delay = config.retries\n\t\t\t\t\t? this.calculateDelay(\n\t\t\t\t\t\t\tconfig.retries.delay,\n\t\t\t\t\t\t\tattempt,\n\t\t\t\t\t\t\tconfig.retries.backoff,\n\t\t\t\t\t\t)\n\t\t\t\t\t: 0;\n\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`āš ļø Attempt ${attempt} failed, retrying in ${delay}ms...`,\n\t\t\t\t\t);\n\t\t\t\t\tconsole.error(error);\n\t\t\t\t}\n\n\t\t\t\tawait this.sleep(delay);\n\t\t\t\tattempt++;\n\n\t\t\t\t// Try again with the next attempt\n\t\t\t\treturn this._step(config);\n\t\t\t} else {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tconsole.timeEnd(`ā±ļø Step ${config.id}`);\n\t\t\t\t\tconsole.error(`āŒ Failed step: ${config.id}`, error);\n\t\t\t\t}\n\t\t\t\tthrow lastError;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async withTimeout<T>({\n\t\tpromise,\n\t\ttimeout,\n\t\tstepId,\n\t}: {\n\t\tpromise: Promise<T>;\n\t\ttimeout: number;\n\t\tstepId: string;\n\t}): Promise<T> {\n\t\tconst timeoutPromise = new Promise<never>((_, reject) => {\n\t\t\tsetTimeout(\n\t\t\t\t() => reject(new TimeoutError(stepId, timeout)),\n\t\t\t\ttimeout,\n\t\t\t);\n\t\t});\n\t\treturn Promise.race([promise, timeoutPromise]);\n\t}\n\n\tprivate calculateDelay(\n\t\tbaseDelay: number,\n\t\tattempt: number,\n\t\tbackoff: RetryConfig['backoff'],\n\t): number {\n\t\tswitch (backoff) {\n\t\t\tcase 'exponential':\n\t\t\t\treturn baseDelay * Math.pow(2, attempt - 1);\n\t\t\tcase 'linear':\n\t\t\t\treturn baseDelay * attempt;\n\t\t\tcase 'fixed':\n\t\t\tdefault:\n\t\t\t\treturn baseDelay;\n\t\t}\n\t}\n\n\tprivate async sleep(ms: number): Promise<void> {\n\t\treturn new Promise(resolve => setTimeout(resolve, ms));\n\t}\n\n\tpublic async end(): Promise<void> {\n\t\t// If tracing is not enabled, do nothing (no-op for backward compatibility)\n\t\tif (!this.langbase || !this.traceManager || !this.traceId) return;\n\t\t// Finalise and grab the trace\n\t\tthis.traceManager.endTrace(this.traceId);\n\t\tif (this.debug) {\n\t\t\tthis.traceManager.printTrace(this.traceId);\n\t\t}\n\t\tconst traceData = this.traceManager.getTrace(this.traceId);\n\n\t\t// --- send to LB API v1/traces/create using SDK method ---\n\t\ttry {\n\t\t\tconst res = await this.langbase.traces.create(traceData);\n\n\t\t\tif (!res || res.error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`āŒ Trace upload failed: ${res?.status || ''} ${res?.statusText || ''}`,\n\t\t\t\t);\n\t\t\t} else if (this.debug) {\n\t\t\t\tconsole.log(`āœ… Trace ${this.traceId} sent to collector`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error('āŒ Error while sending trace', err);\n\t\t}\n\t\t// -------------------------------------------------------------------------\n\n\t\tif (this.debug) {\n\t\t\tconsole.log('\\nšŸ” DEBUG: Final trace data:');\n\t\t\tconsole.log(JSON.stringify(traceData, null, 2));\n\t\t}\n\t}\n}\n","import {convertDocToFormData} from '@/lib/utils/doc-to-formdata';\nimport {Request} from '../common/request';\nimport {Workflow} from './workflows';\n\nexport type Role = 'user' | 'assistant' | 'system' | 'tool';\n\nexport interface RunOptionsBase {\n\tmessages?: Message[];\n\tvariables?: Variable[];\n\tthreadId?: string;\n\trawResponse?: boolean;\n\trunTools?: boolean;\n\ttools?: Tools[];\n\tname?: string; // Pipe name for SDK,\n\tapiKey?: string; // pipe level key for SDK\n\tllmKey?: string; // LLM API key\n\tjson?: boolean;\n\tmemory?: RuntimeMemory;\n}\n\nexport interface RunOptionsT extends RunOptionsBase {\n\tstream?: false;\n}\n\nexport interface RunOptionsStreamT extends RunOptionsBase {\n\tstream: true;\n}\n\nexport interface AgentRunOptionsBase {\n\tinput: string | PromptMessage[];\n\tinstructions?: string | null;\n\tmodel: string;\n\tapiKey: string;\n\ttop_p?: number;\n\tmax_tokens?: number;\n\ttemperature?: number;\n\tpresence_penalty?: number;\n\tfrequency_penalty?: number;\n\tstop?: string[];\n\ttools?: Tools[];\n\ttool_choice?: 'auto' | 'required' | ToolChoice;\n\tparallel_tool_calls?: boolean;\n\tmcp_servers?: McpServerSchema[];\n\treasoning_effort?: string | null;\n\tmax_completion_tokens?: number;\n\tresponse_format?: ResponseFormat;\n\tcustomModelParams?: Record<string, any>;\n}\n\nexport type AgentRunOptionsWithoutMcp = Omit<\n\tAgentRunOptionsBase,\n\t'mcp_servers'\n> & {\n\tstream?: false;\n};\n\nexport type AgentRunOptionsWithMcp = AgentRunOptionsBase & {\n\tmcp_servers: McpServerSchema[];\n\tstream: false;\n};\n\nexport type AgentRunOptionsStreamT = Omit<\n\tAgentRunOptionsBase,\n\t'mcp_servers'\n> & {\n\tstream: true;\n};\n\nexport type AgentRunOptions =\n\t| AgentRunOptionsWithoutMcp\n\t| AgentRunOptionsWithMcp;\nexport type AgentRunOptionsStream = AgentRunOptionsStreamT;\n\nexport interface McpServerSchema {\n\tname: string;\n\ttype: 'url';\n\turl: string;\n\tauthorization_token?: string;\n\ttool_configuration?: {\n\t\tallowed_tools?: string[];\n\t\tenabled?: boolean;\n\t};\n\tcustom_headers?: Record<string, string>;\n}\n\ninterface ChoiceGenerate {\n\tindex: number;\n\tmessage: Message;\n\tlogprobs: boolean | null;\n\tfinish_reason: string;\n}\n\nexport interface Usage {\n\tprompt_tokens: number;\n\tcompletion_tokens: number;\n\ttotal_tokens: number;\n}\n\nexport interface RunResponse {\n\tcompletion: string;\n\tthreadId?: string;\n\tid: string;\n\tobject: string;\n\tcreated: number;\n\tmodel: string;\n\tchoices: ChoiceGenerate[];\n\tusage: Usage;\n\tsystem_fingerprint: string | null;\n\trawResponse?: {\n\t\theaders: Record<string, string>;\n\t};\n}\n\nexport interface AgentRunResponse {\n\toutput: string;\n\tthreadId?: string;\n\tid: string;\n\tobject: string;\n\tcreated: number;\n\tmodel: string;\n\tchoices: ChoiceGenerate[];\n\tusage: Usage;\n\tsystem_fingerprint: string | null;\n\trawResponse?: {\n\t\theaders: Record<string, string>;\n\t};\n}\n\nexport interface RunResponseStream {\n\tstream: ReadableStream<any>;\n\tthreadId: string | null;\n\trawResponse?: {\n\t\theaders: Record<string, string>;\n\t};\n}\n\n// Union type for RunOptions\nexport type RunOptions =\n\t| (RunOptionsT & {name: string; apiKey?: never})\n\t| (RunOptionsT & {name?: never;