@mastra/core
Version:
1 lines • 93.9 kB
Source Map (JSON)
{"version":3,"file":"stream-CGwJ76we.cjs","names":["isUrlString","exhaustiveCheck","EventEmitter","DelayedPromise","#baseStream","WritableStream","#bufferedChunks","#emitter","#updateUsageCount","#status","#tripwireData","#streamError","#usageCount","#finalWorkflowResult","#delayedPromises","#streamFinished","#consumptionStarted","ReadableStream","#getDelayedPromise","ReadableStream","#usageCount","#run","#streamPromise","#objectPromise","#objectStream","#objectStreamController","isUrlString","DefaultGeneratedFile","DefaultGeneratedFileWithType","TransformStream"],"sources":["../src/stream/aisdk/v5/compat/ui-message.ts","../src/stream/base/consume-stream.ts","../src/stream/RunOutput.ts","../src/stream/MastraAgentNetworkStream.ts","../src/stream/aisdk/v5/transform.ts","../src/stream/caching-transform-stream.ts","../src/stream/smooth-stream.ts"],"sourcesContent":["import type { InferUIMessageChunk, TextStreamPart, ToolSet, UIMessage, IdGenerator } from '@internal/ai-sdk-v5';\nimport { isUrlString } from './content';\n\nexport function getResponseUIMessageId({\n originalMessages,\n responseMessageId,\n}: {\n originalMessages: UIMessage[] | undefined;\n responseMessageId: string | IdGenerator | undefined;\n}) {\n // when there are no original messages (i.e. no persistence),\n // the assistant message id generation is handled on the client side.\n if (originalMessages == null) {\n return undefined;\n }\n\n const lastMessage = originalMessages[originalMessages.length - 1];\n\n return lastMessage?.role === 'assistant'\n ? lastMessage.id\n : typeof responseMessageId === 'function'\n ? responseMessageId()\n : responseMessageId;\n}\n\nexport function convertFullStreamChunkToUIMessageStream<UI_MESSAGE extends UIMessage>({\n part,\n messageMetadataValue,\n sendReasoning,\n sendSources,\n onError,\n sendStart,\n sendFinish,\n responseMessageId,\n}: {\n // tool-output is a custom mastra chunk type used in ToolStream\n part: TextStreamPart<ToolSet> | { type: 'tool-output'; toolCallId: string; output: any };\n messageMetadataValue?: unknown;\n sendReasoning?: boolean;\n sendSources?: boolean;\n onError: (error: unknown) => string;\n sendStart?: boolean;\n sendFinish?: boolean;\n responseMessageId?: string;\n}): InferUIMessageChunk<UI_MESSAGE> | undefined {\n const partType = part.type;\n\n switch (partType) {\n case 'text-start': {\n return {\n type: 'text-start',\n id: part.id,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n case 'text-delta': {\n return {\n type: 'text-delta',\n id: part.id,\n delta: part.text,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n case 'text-end': {\n return {\n type: 'text-end',\n id: part.id,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n case 'reasoning-start': {\n return {\n type: 'reasoning-start',\n id: part.id,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n case 'reasoning-delta': {\n if (sendReasoning) {\n return {\n type: 'reasoning-delta',\n id: part.id,\n delta: part.text,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n return;\n }\n\n case 'reasoning-end': {\n return {\n type: 'reasoning-end',\n id: part.id,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n case 'file': {\n // URL-backed generated files store the URL string where base64 normally\n // lives; emit it as the url directly instead of a broken data URI.\n const base64 = part.file.base64;\n return {\n type: 'file',\n mediaType: part.file.mediaType,\n url: isUrlString(base64) ? base64 : `data:${part.file.mediaType};base64,${base64}`,\n };\n }\n\n case 'source': {\n if (sendSources && part.sourceType === 'url') {\n return {\n type: 'source-url',\n sourceId: part.id,\n url: part.url,\n title: part.title,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n\n if (sendSources && part.sourceType === 'document') {\n return {\n type: 'source-document',\n sourceId: part.id,\n mediaType: part.mediaType,\n title: part.title,\n filename: part.filename,\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n };\n }\n return;\n }\n\n case 'tool-input-start': {\n return {\n type: 'tool-input-start',\n toolCallId: part.id,\n toolName: part.toolName,\n ...(part.providerExecuted != null ? { providerExecuted: part.providerExecuted } : {}),\n ...(part.dynamic != null ? { dynamic: part.dynamic } : {}),\n };\n }\n\n case 'tool-input-delta': {\n return {\n type: 'tool-input-delta',\n toolCallId: part.id,\n inputTextDelta: part.delta,\n };\n }\n\n case 'tool-call': {\n return {\n type: 'tool-input-available',\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n input: part.input,\n ...(part.providerExecuted != null ? { providerExecuted: part.providerExecuted } : {}),\n ...(part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}),\n ...(part.dynamic != null ? { dynamic: part.dynamic } : {}),\n };\n }\n\n case 'tool-result': {\n return {\n type: 'tool-output-available',\n toolCallId: part.toolCallId,\n output: part.output,\n ...(part.providerExecuted != null ? { providerExecuted: part.providerExecuted } : {}),\n ...(part.dynamic != null ? { dynamic: part.dynamic } : {}),\n };\n }\n\n case 'tool-output': {\n return {\n ...part.output,\n };\n }\n\n case 'tool-error': {\n return {\n type: 'tool-output-error',\n toolCallId: part.toolCallId,\n errorText: onError(part.error),\n ...(part.providerExecuted != null ? { providerExecuted: part.providerExecuted } : {}),\n ...(part.dynamic != null ? { dynamic: part.dynamic } : {}),\n };\n }\n\n case 'error': {\n return {\n type: 'error',\n errorText: onError(part.error),\n };\n }\n\n case 'start-step': {\n return { type: 'start-step' };\n }\n\n case 'finish-step': {\n return { type: 'finish-step' };\n }\n\n case 'start': {\n if (sendStart) {\n return {\n type: 'start' as const,\n ...(messageMetadataValue != null ? { messageMetadata: messageMetadataValue } : {}),\n ...(responseMessageId != null ? { messageId: responseMessageId } : {}),\n } as InferUIMessageChunk<UI_MESSAGE>;\n }\n return;\n }\n\n case 'finish': {\n if (sendFinish) {\n return {\n type: 'finish' as const,\n ...(messageMetadataValue != null ? { messageMetadata: messageMetadataValue } : {}),\n } as InferUIMessageChunk<UI_MESSAGE>;\n }\n return;\n }\n\n case 'abort': {\n return part;\n }\n\n case 'tool-input-end': {\n return;\n }\n\n case 'raw': {\n // Raw chunks are not included in UI message streams\n // as they contain provider-specific data for developer use\n return;\n }\n\n default: {\n const exhaustiveCheck: never = partType;\n throw new Error(`Unknown chunk type: ${exhaustiveCheck}`);\n }\n }\n}\n","export async function consumeStream({\n stream,\n onError,\n}: {\n stream: ReadableStream;\n onError?: (error: unknown) => void;\n}) {\n const reader = stream.getReader();\n try {\n while (true) {\n const { done } = await reader.read();\n if (done) break;\n }\n } catch (error) {\n onError == null ? void 0 : onError(error);\n } finally {\n reader.releaseLock();\n }\n}\n","import EventEmitter from 'node:events';\nimport { ReadableStream, WritableStream } from 'node:stream/web';\nimport type { ReadableStreamGetReaderOptions, ReadableWritablePair, StreamPipeOptions } from 'node:stream/web';\nimport type { LanguageModelUsage } from '@internal/ai-sdk-v5';\nimport type { WorkflowResult, WorkflowRunStatus } from '../workflows';\nimport { DelayedPromise } from './aisdk/v5/compat';\nimport type { MastraBaseStream } from './base/base';\nimport { consumeStream } from './base/consume-stream';\nimport { ChunkFrom } from './types';\nimport type { StepTripwireData, WorkflowStreamEvent } from './types';\n\ntype AggregatedLanguageModelUsage = Required<LanguageModelUsage> & {\n cacheCreationInputTokens: number;\n};\n\nexport class WorkflowRunOutput<\n TResult extends WorkflowResult<any, any, any, any> = WorkflowResult<any, any, any, any>,\n> implements MastraBaseStream<WorkflowStreamEvent> {\n #status: WorkflowRunStatus = 'running';\n #tripwireData: StepTripwireData | undefined;\n #usageCount: AggregatedLanguageModelUsage = {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n cachedInputTokens: 0,\n cacheCreationInputTokens: 0,\n reasoningTokens: 0,\n };\n #consumptionStarted = false;\n #baseStream: ReadableStream<WorkflowStreamEvent>;\n #emitter = new EventEmitter();\n #bufferedChunks: WorkflowStreamEvent[] = [];\n\n #streamFinished = false;\n\n #streamError: Error | undefined;\n\n #finalWorkflowResult: unknown;\n\n #delayedPromises = {\n usage: new DelayedPromise<LanguageModelUsage>(),\n result: new DelayedPromise<TResult>(),\n };\n\n /**\n * Unique identifier for this workflow run\n */\n public runId: string;\n /**\n * Unique identifier for this workflow\n */\n public workflowId: string;\n\n constructor({\n runId,\n workflowId,\n stream,\n }: {\n runId: string;\n workflowId: string;\n stream: ReadableStream<WorkflowStreamEvent>;\n }) {\n const self = this;\n this.runId = runId;\n this.workflowId = workflowId;\n\n this.#baseStream = stream;\n stream\n .pipeTo(\n new WritableStream({\n start() {\n const chunk: WorkflowStreamEvent = {\n type: 'workflow-start',\n runId: self.runId,\n from: ChunkFrom.WORKFLOW,\n payload: {\n workflowId: self.workflowId,\n },\n } as WorkflowStreamEvent;\n\n self.#bufferedChunks.push(chunk);\n self.#emitter.emit('chunk', chunk);\n },\n write(chunk) {\n if (chunk.type !== 'workflow-step-finish') {\n self.#bufferedChunks.push(chunk);\n self.#emitter.emit('chunk', chunk);\n }\n\n if (chunk.type === 'workflow-step-output') {\n if ('output' in chunk.payload && chunk.payload.output) {\n const output = chunk.payload.output;\n if (output.type === 'finish') {\n if (output.payload && 'usage' in output.payload && output.payload.usage) {\n self.#updateUsageCount(output.payload.usage);\n } else if (output.payload && 'output' in output.payload && output.payload.output) {\n const outputPayload = output.payload.output;\n if ('usage' in outputPayload && outputPayload.usage) {\n self.#updateUsageCount(outputPayload.usage);\n }\n }\n }\n }\n } else if (chunk.type === 'workflow-canceled') {\n self.#status = 'canceled';\n } else if (chunk.type === 'workflow-step-suspended') {\n self.#status = 'suspended';\n } else if (chunk.type === 'workflow-step-result' && chunk.payload.status === 'failed') {\n // Check if the failure was due to a tripwire\n if (chunk.payload.tripwire) {\n self.#status = 'tripwire';\n self.#tripwireData = chunk.payload.tripwire;\n } else {\n self.#status = 'failed';\n }\n } else if (chunk.type === 'workflow-paused') {\n self.#status = 'paused';\n }\n },\n close() {\n if (self.#status === 'running') {\n self.#status = 'success';\n }\n\n self.#emitter.emit('chunk', {\n type: 'workflow-finish',\n runId: self.runId,\n from: ChunkFrom.WORKFLOW,\n payload: {\n workflowStatus: self.#status,\n metadata: self.#streamError\n ? {\n error: self.#streamError,\n errorMessage: self.#streamError?.message,\n }\n : {},\n output: {\n usage: self.#usageCount,\n },\n ...(self.#status === 'success' && self.#finalWorkflowResult !== undefined\n ? { finalWorkflowResult: self.#finalWorkflowResult }\n : {}),\n // Include tripwire data when status is 'tripwire'\n ...(self.#status === 'tripwire' && self.#tripwireData ? { tripwire: self.#tripwireData } : {}),\n },\n });\n\n self.#delayedPromises.usage.resolve(self.#usageCount);\n\n Object.entries(self.#delayedPromises).forEach(([key, promise]) => {\n if (promise.status.type === 'pending') {\n promise.reject(new Error(`promise '${key}' was not resolved or rejected when stream finished`));\n }\n });\n\n self.#streamFinished = true;\n self.#emitter.emit('finish');\n },\n }),\n )\n .catch(reason => {\n // eslint-disable-next-line no-console\n console.log(' something went wrong', reason);\n });\n }\n\n #getDelayedPromise<T>(promise: DelayedPromise<T>): Promise<T> {\n if (!this.#consumptionStarted) {\n void this.consumeStream();\n }\n return promise.promise;\n }\n\n #updateUsageCount(\n usage:\n | {\n inputTokens?: `${number}` | number;\n outputTokens?: `${number}` | number;\n totalTokens?: `${number}` | number;\n reasoningTokens?: `${number}` | number;\n cachedInputTokens?: `${number}` | number;\n cacheCreationInputTokens?: `${number}` | number;\n }\n | {\n promptTokens?: `${number}` | number;\n completionTokens?: `${number}` | number;\n totalTokens?: `${number}` | number;\n reasoningTokens?: `${number}` | number;\n cachedInputTokens?: `${number}` | number;\n cacheCreationInputTokens?: `${number}` | number;\n },\n ) {\n let totalUsage = {\n inputTokens: this.#usageCount.inputTokens ?? 0,\n outputTokens: this.#usageCount.outputTokens ?? 0,\n totalTokens: this.#usageCount.totalTokens ?? 0,\n reasoningTokens: this.#usageCount.reasoningTokens ?? 0,\n cachedInputTokens: this.#usageCount.cachedInputTokens ?? 0,\n cacheCreationInputTokens: this.#usageCount.cacheCreationInputTokens ?? 0,\n };\n if ('inputTokens' in usage) {\n totalUsage.inputTokens += parseInt(usage?.inputTokens?.toString() ?? '0', 10);\n totalUsage.outputTokens += parseInt(usage?.outputTokens?.toString() ?? '0', 10);\n // we need to handle both formats because you can use a V1 model inside a stream workflow\n } else if ('promptTokens' in usage) {\n totalUsage.inputTokens += parseInt(usage?.promptTokens?.toString() ?? '0', 10);\n totalUsage.outputTokens += parseInt(usage?.completionTokens?.toString() ?? '0', 10);\n }\n totalUsage.totalTokens += parseInt(usage?.totalTokens?.toString() ?? '0', 10);\n\n totalUsage.reasoningTokens += parseInt(usage?.reasoningTokens?.toString() ?? '0', 10);\n totalUsage.cachedInputTokens += parseInt(usage?.cachedInputTokens?.toString() ?? '0', 10);\n totalUsage.cacheCreationInputTokens += parseInt(usage?.cacheCreationInputTokens?.toString() ?? '0', 10);\n this.#usageCount = totalUsage;\n }\n\n /**\n * @internal\n */\n updateResults(results: TResult) {\n if (results.status === 'success') {\n this.#finalWorkflowResult = results.result;\n }\n this.#delayedPromises.result.resolve(results);\n }\n\n /**\n * @internal\n */\n rejectResults(error: Error) {\n this.#delayedPromises.result.reject(error);\n this.#status = 'failed';\n this.#streamError = error;\n }\n\n /**\n * @internal\n */\n resume(stream: ReadableStream<WorkflowStreamEvent>) {\n this.#baseStream = stream;\n this.#streamFinished = false;\n this.#consumptionStarted = false;\n this.#status = 'running';\n this.#delayedPromises = {\n usage: new DelayedPromise<LanguageModelUsage>(),\n result: new DelayedPromise<TResult>(),\n };\n\n const self = this;\n stream\n .pipeTo(\n new WritableStream({\n start() {\n const chunk: WorkflowStreamEvent = {\n type: 'workflow-start',\n runId: self.runId,\n from: ChunkFrom.WORKFLOW,\n payload: {\n workflowId: self.workflowId,\n },\n } as WorkflowStreamEvent;\n\n self.#bufferedChunks.push(chunk);\n self.#emitter.emit('chunk', chunk);\n },\n write(chunk) {\n if (chunk.type !== 'workflow-step-finish') {\n self.#bufferedChunks.push(chunk);\n self.#emitter.emit('chunk', chunk);\n }\n\n if (chunk.type === 'workflow-step-output') {\n if ('output' in chunk.payload && chunk.payload.output) {\n const output = chunk.payload.output;\n if (output.type === 'finish') {\n if (output.payload && 'usage' in output.payload && output.payload.usage) {\n self.#updateUsageCount(output.payload.usage);\n } else if (output.payload && 'output' in output.payload && output.payload.output) {\n const outputPayload = output.payload.output;\n if ('usage' in outputPayload && outputPayload.usage) {\n self.#updateUsageCount(outputPayload.usage);\n }\n }\n }\n }\n } else if (chunk.type === 'workflow-canceled') {\n self.#status = 'canceled';\n } else if (chunk.type === 'workflow-step-suspended') {\n self.#status = 'suspended';\n } else if (chunk.type === 'workflow-step-result' && chunk.payload.status === 'failed') {\n // Check if the failure was due to a tripwire\n if (chunk.payload.tripwire) {\n self.#status = 'tripwire';\n self.#tripwireData = chunk.payload.tripwire;\n } else {\n self.#status = 'failed';\n }\n } else if (chunk.type === 'workflow-paused') {\n self.#status = 'paused';\n }\n },\n close() {\n if (self.#status === 'running') {\n self.#status = 'success';\n }\n\n self.#emitter.emit('chunk', {\n type: 'workflow-finish',\n runId: self.runId,\n from: ChunkFrom.WORKFLOW,\n payload: {\n workflowStatus: self.#status,\n metadata: self.#streamError\n ? {\n error: self.#streamError,\n errorMessage: self.#streamError?.message,\n }\n : {},\n output: {\n usage: self.#usageCount,\n },\n ...(self.#status === 'success' && self.#finalWorkflowResult !== undefined\n ? { finalWorkflowResult: self.#finalWorkflowResult }\n : {}),\n // Include tripwire data when status is 'tripwire'\n ...(self.#status === 'tripwire' && self.#tripwireData ? { tripwire: self.#tripwireData } : {}),\n },\n });\n\n self.#streamFinished = true;\n self.#emitter.emit('finish');\n },\n }),\n )\n .catch(reason => {\n // eslint-disable-next-line no-console\n console.log(' something went wrong', reason);\n });\n }\n\n async consumeStream(options?: Parameters<typeof consumeStream>[0]): Promise<void> {\n if (this.#consumptionStarted) {\n return;\n }\n\n this.#consumptionStarted = true;\n\n try {\n await consumeStream({\n stream: this.#baseStream as globalThis.ReadableStream,\n onError: options?.onError,\n });\n } catch (error) {\n options?.onError?.(error);\n }\n }\n\n get fullStream(): ReadableStream<WorkflowStreamEvent> {\n const self = this;\n // Holds this subscriber's own detach function once start() registers its\n // listeners. cancel() must only remove this subscriber's handlers — the\n // emitter is shared across every concurrent fullStream/observe consumer of\n // the same run, so removeAllListeners() here would silently kill every\n // other subscriber (see #19743).\n let detach: (() => void) | undefined;\n return new ReadableStream<WorkflowStreamEvent>({\n start(controller) {\n // Replay existing buffered chunks\n self.#bufferedChunks.forEach(chunk => {\n controller.enqueue(chunk);\n });\n\n // If stream already finished, close immediately\n if (self.#streamFinished) {\n controller.close();\n return;\n }\n\n // Listen for new chunks and stream finish\n const chunkHandler = (chunk: WorkflowStreamEvent) => {\n controller.enqueue(chunk);\n };\n\n const finishHandler = () => {\n self.#emitter.off('chunk', chunkHandler);\n self.#emitter.off('finish', finishHandler);\n controller.close();\n };\n\n self.#emitter.on('chunk', chunkHandler);\n self.#emitter.on('finish', finishHandler);\n\n detach = () => {\n self.#emitter.off('chunk', chunkHandler);\n self.#emitter.off('finish', finishHandler);\n };\n },\n\n pull(_controller) {\n // Only start consumption when someone is actively reading the stream\n if (!self.#consumptionStarted) {\n void self.consumeStream();\n }\n },\n\n cancel() {\n // Only detach this subscriber's own listeners — never the whole emitter.\n detach?.();\n },\n });\n }\n\n get status() {\n return this.#status;\n }\n\n get result() {\n return this.#getDelayedPromise(this.#delayedPromises.result);\n }\n\n get usage() {\n return this.#getDelayedPromise(this.#delayedPromises.usage);\n }\n\n /**\n * @deprecated Use `fullStream.locked` instead\n */\n get locked(): boolean {\n console.warn('WorkflowRunOutput.locked is deprecated. Use fullStream.locked instead.');\n return this.fullStream.locked;\n }\n\n /**\n * @deprecated Use `fullStream.cancel()` instead\n */\n cancel(reason?: any): Promise<void> {\n console.warn('WorkflowRunOutput.cancel() is deprecated. Use fullStream.cancel() instead.');\n return this.fullStream.cancel(reason);\n }\n\n /**\n * @deprecated Use `fullStream.getReader()` instead\n */\n getReader(\n options?: ReadableStreamGetReaderOptions,\n ): ReadableStreamDefaultReader<WorkflowStreamEvent> | ReadableStreamBYOBReader {\n console.warn('WorkflowRunOutput.getReader() is deprecated. Use fullStream.getReader() instead.');\n return this.fullStream.getReader(options as any) as any;\n }\n\n /**\n * @deprecated Use `fullStream.pipeThrough()` instead\n */\n pipeThrough<T>(\n transform: ReadableWritablePair<T, WorkflowStreamEvent>,\n options?: StreamPipeOptions,\n ): ReadableStream<T> {\n console.warn('WorkflowRunOutput.pipeThrough() is deprecated. Use fullStream.pipeThrough() instead.');\n return this.fullStream.pipeThrough(transform as any, options) as ReadableStream<T>;\n }\n\n /**\n * @deprecated Use `fullStream.pipeTo()` instead\n */\n pipeTo(destination: WritableStream<WorkflowStreamEvent>, options?: StreamPipeOptions): Promise<void> {\n console.warn('WorkflowRunOutput.pipeTo() is deprecated. Use fullStream.pipeTo() instead.');\n return this.fullStream.pipeTo(destination, options);\n }\n\n /**\n * @deprecated Use `fullStream.tee()` instead\n */\n tee(): [ReadableStream<WorkflowStreamEvent>, ReadableStream<WorkflowStreamEvent>] {\n console.warn('WorkflowRunOutput.tee() is deprecated. Use fullStream.tee() instead.');\n return this.fullStream.tee();\n }\n\n /**\n * @deprecated Use `fullStream[Symbol.asyncIterator]()` instead\n */\n [Symbol.asyncIterator](): AsyncIterableIterator<WorkflowStreamEvent> {\n console.warn(\n 'WorkflowRunOutput[Symbol.asyncIterator]() is deprecated. Use fullStream[Symbol.asyncIterator]() instead.',\n );\n return this.fullStream[Symbol.asyncIterator]();\n }\n\n /**\n * Helper method to treat this object as a ReadableStream\n * @deprecated Use `fullStream` directly instead\n */\n toReadableStream(): ReadableStream<WorkflowStreamEvent> {\n console.warn('WorkflowRunOutput.toReadableStream() is deprecated. Use fullStream directly instead.');\n return this.fullStream;\n }\n}\n","import { ReadableStream } from 'node:stream/web';\nimport type { DefaultEngineType, Run, Step } from '../workflows';\nimport type { ChunkType } from './types';\n\nexport class MastraAgentNetworkStream<OUTPUT = undefined> extends ReadableStream<ChunkType<OUTPUT>> {\n #usageCount = {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n cachedInputTokens: 0,\n cacheCreationInputTokens: 0,\n reasoningTokens: 0,\n };\n #streamPromise: {\n promise: Promise<void>;\n resolve: (value: void) => void;\n reject: (reason?: any) => void;\n };\n #objectPromise: {\n promise: Promise<OUTPUT | undefined>;\n resolve: (value: OUTPUT | undefined) => void;\n reject: (reason?: any) => void;\n };\n #objectStreamController: ReadableStreamDefaultController<Partial<OUTPUT>> | null = null;\n #objectStream: ReadableStream<Partial<OUTPUT>> | null = null;\n #run: Run;\n runId: string;\n\n constructor({\n createStream,\n run,\n }: {\n createStream: (writer: WritableStream<ChunkType<OUTPUT>>) => Promise<ReadableStream<any>> | ReadableStream<any>;\n run: Run<DefaultEngineType, Step<string, any, any, any, any, any, DefaultEngineType>[], any, any, any>;\n }) {\n const deferredPromise = {\n promise: null,\n resolve: null,\n reject: null,\n } as unknown as {\n promise: Promise<void>;\n resolve: (value: void) => void;\n reject: (reason?: any) => void;\n };\n deferredPromise.promise = new Promise((resolve, reject) => {\n deferredPromise.resolve = resolve;\n deferredPromise.reject = reject;\n });\n\n // Object promise for structured output\n const objectDeferredPromise = {\n promise: null,\n resolve: null,\n reject: null,\n } as unknown as {\n promise: Promise<OUTPUT | undefined>;\n resolve: (value: OUTPUT | undefined) => void;\n reject: (reason?: any) => void;\n };\n objectDeferredPromise.promise = new Promise((resolve, reject) => {\n objectDeferredPromise.resolve = resolve;\n objectDeferredPromise.reject = reject;\n });\n\n // Object stream controller reference\n let objectStreamController: ReadableStreamDefaultController<Partial<OUTPUT>> | null = null;\n\n const updateUsageCount = (usage: {\n inputTokens?: `${number}` | number;\n outputTokens?: `${number}` | number;\n totalTokens?: `${number}` | number;\n reasoningTokens?: `${number}` | number;\n cachedInputTokens?: `${number}` | number;\n cacheCreationInputTokens?: `${number}` | number;\n }) => {\n this.#usageCount.inputTokens += parseInt(usage?.inputTokens?.toString() ?? '0', 10);\n this.#usageCount.outputTokens += parseInt(usage?.outputTokens?.toString() ?? '0', 10);\n this.#usageCount.totalTokens += parseInt(usage?.totalTokens?.toString() ?? '0', 10);\n this.#usageCount.reasoningTokens += parseInt(usage?.reasoningTokens?.toString() ?? '0', 10);\n this.#usageCount.cachedInputTokens += parseInt(usage?.cachedInputTokens?.toString() ?? '0', 10);\n this.#usageCount.cacheCreationInputTokens += parseInt(usage?.cacheCreationInputTokens?.toString() ?? '0', 10);\n };\n\n super({\n start: async controller => {\n try {\n const writer = new WritableStream<ChunkType<OUTPUT>>({\n write: chunk => {\n if (\n (chunk.type === 'step-output' &&\n chunk.payload?.output?.from === 'AGENT' &&\n chunk.payload?.output?.type === 'finish') ||\n (chunk.type === 'step-output' &&\n chunk.payload?.output?.from === 'WORKFLOW' &&\n chunk.payload?.output?.type === 'finish')\n ) {\n const output = chunk.payload?.output;\n if (output && 'payload' in output && output.payload) {\n const finishPayload = output.payload;\n if ('usage' in finishPayload && finishPayload.usage) {\n updateUsageCount(finishPayload.usage);\n } else if ('output' in finishPayload && finishPayload.output) {\n const outputPayload = finishPayload.output;\n if ('usage' in outputPayload && outputPayload.usage) {\n updateUsageCount(outputPayload.usage);\n }\n }\n }\n }\n\n controller.enqueue(chunk);\n },\n });\n\n const stream: ReadableStream<ChunkType<OUTPUT>> = await createStream(writer);\n\n const getInnerChunk = (chunk: ChunkType<OUTPUT>) => {\n if (chunk.type === 'workflow-step-output') {\n return getInnerChunk(chunk.payload.output as any);\n }\n return chunk;\n };\n\n let objectResolved = false;\n\n for await (const chunk of stream) {\n if (chunk.type === 'workflow-step-output') {\n const innerChunk = getInnerChunk(chunk);\n if (\n innerChunk.type === 'routing-agent-end' ||\n innerChunk.type === 'agent-execution-end' ||\n innerChunk.type === 'workflow-execution-end'\n ) {\n if (innerChunk.payload?.usage) {\n updateUsageCount(innerChunk.payload.usage);\n }\n }\n\n // Handle network-object chunks (partial objects during streaming)\n if (innerChunk.type === 'network-object') {\n if (objectStreamController) {\n objectStreamController.enqueue((innerChunk as any).payload?.object);\n }\n controller.enqueue(innerChunk);\n }\n // Handle network-object-result chunks (final structured object)\n else if (innerChunk.type === 'network-object-result') {\n if (!objectResolved) {\n objectResolved = true;\n objectDeferredPromise.resolve((innerChunk as any).payload?.object);\n if (objectStreamController) {\n objectStreamController.close();\n }\n }\n controller.enqueue(innerChunk);\n } else if (innerChunk.type === 'network-execution-event-finish') {\n const finishPayload = {\n ...innerChunk.payload,\n usage: this.#usageCount,\n };\n controller.enqueue({ ...innerChunk, payload: finishPayload });\n } else {\n controller.enqueue(innerChunk);\n }\n }\n }\n\n // If no object was resolved, resolve with undefined\n if (!objectResolved) {\n objectDeferredPromise.resolve(undefined);\n if (objectStreamController) {\n objectStreamController.close();\n }\n }\n\n controller.close();\n deferredPromise.resolve();\n } catch (error) {\n controller.error(error);\n deferredPromise.reject(error);\n objectDeferredPromise.reject(error);\n if (objectStreamController) {\n objectStreamController.error(error);\n }\n }\n },\n });\n\n this.#run = run;\n this.#streamPromise = deferredPromise;\n\n this.runId = run.runId;\n this.#objectPromise = objectDeferredPromise;\n\n // Create object stream\n this.#objectStream = new ReadableStream<Partial<OUTPUT>>({\n start: ctrl => {\n objectStreamController = ctrl;\n this.#objectStreamController = ctrl;\n },\n });\n }\n\n get status() {\n return this.#streamPromise.promise.then(() => this.#run._getExecutionResults()).then(res => res!.status);\n }\n\n get result() {\n return this.#streamPromise.promise.then(() => this.#run._getExecutionResults());\n }\n\n get usage() {\n return this.#streamPromise.promise.then(() => this.#usageCount);\n }\n\n /**\n * Returns a promise that resolves to the structured output object.\n * Only available when structuredOutput option is provided to network().\n * Resolves to undefined if no structuredOutput was requested.\n */\n get object(): Promise<OUTPUT | undefined> {\n return this.#objectPromise.promise;\n }\n\n /**\n * Returns a ReadableStream of partial objects during structured output generation.\n * Useful for streaming partial results as they're being generated.\n */\n get objectStream(): ReadableStream<Partial<OUTPUT>> {\n return this.#objectStream!;\n }\n}\n","import type {\n LanguageModelV2FinishReason,\n LanguageModelV2StreamPart,\n LanguageModelV2Usage,\n SharedV2ProviderMetadata,\n} from '@ai-sdk/provider-v5';\nimport type { LanguageModelV3FinishReason, LanguageModelV3Usage } from '@ai-sdk/provider-v6';\nimport type { ModelMessage, ObjectStreamPart, TextStreamPart, ToolSet } from '@internal/ai-sdk-v5';\nimport type { AIV5ResponseMessage } from '../../../agent/message-list';\nimport type { ChunkType, LanguageModelUsage } from '../../types';\nimport { ChunkFrom } from '../../types';\nimport { isUrlString } from './compat/content';\nimport { DefaultGeneratedFile, DefaultGeneratedFileWithType } from './file';\n\n/**\n * Sanitizes tool-call input strings for safe JSON parsing.\n *\n * LLMs sometimes append internal tokens like `<|call|>`, `<|endoftext|>`, or `<|end|>`\n * to otherwise valid JSON in streamed tool-call arguments, causing JSON.parse to fail.\n *\n * This function first attempts JSON.parse on the original input. If parsing succeeds,\n * the original string is returned unchanged — this avoids corrupting valid JSON payloads\n * that legitimately contain `<|...|>` patterns inside string values.\n *\n * Only when the original input is not valid JSON does the function fall back to stripping\n * `<|...|>` token patterns and surrounding whitespace via regex.\n *\n * @see https://github.com/mastra-ai/mastra/issues/13261\n * @see https://github.com/mastra-ai/mastra/issues/13185\n */\nexport function sanitizeToolCallInput(input: string): string {\n // Fast path: if input is already valid JSON, return unchanged to avoid\n // corrupting <|...|> patterns that appear inside JSON string values.\n try {\n JSON.parse(input);\n return input;\n } catch {\n // Input is not valid JSON — strip LLM-specific tokens and retry.\n // The pattern starts at the literal `<|` (no leading `\\s*`) to keep\n // matching linear on adversarial inputs (CodeQL js/polynomial-redos);\n // leftover whitespace is harmless to JSON.parse and trimmed at the ends.\n return input.replace(/<\\|[^|]*\\|>\\s*/g, '').trim();\n }\n}\n\n/**\n * Attempts to repair common JSON malformations produced by LLM providers.\n *\n * Some LLM providers (e.g., Kimi/K2) occasionally generate malformed JSON for\n * tool call arguments. This function applies a sequence of targeted fixes for\n * the most common errors before giving up.\n *\n * Repairs applied (in order):\n * 1. Missing quote before property name: `{\"a\":\"b\",c\":\"d\"}` → `{\"a\":\"b\",\"c\":\"d\"}`\n * 2. Unquoted property names: `{command:\"value\"}` → `{\"command\":\"value\"}`\n * 3. Single quotes → double quotes (only outside already-double-quoted strings)\n * 4. Trailing commas: `{\"a\":1,}` → `{\"a\":1}`\n *\n * @returns The parsed object if repair succeeds, or null if the JSON is unrecoverable.\n * @see https://github.com/mastra-ai/mastra/issues/11078\n */\nexport function tryRepairJson(input: string): Record<string, any> | null {\n let repaired = input.trim();\n\n // Fix 1: Missing quote before property name after comma or opening brace\n // e.g. {\"a\":\"b\",c\":\"d\"} → {\"a\":\"b\",\"c\":\"d\"}\n // Matches: ,c\" or {c\" where c is a word character sequence followed by \"\n // but NOT already preceded by a quote\n repaired = repaired.replace(/([{,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\"/g, (match, prefix, name) => {\n // Check if the name is already quoted — if so, leave it alone\n if (prefix.trimEnd().endsWith('\"')) {\n return match;\n }\n return `${prefix}\"${name}\"`;\n });\n\n // Fix 2: Unquoted property names (must come after Fix 1 since Fix 1 handles the partial-quote case)\n // e.g. {command:\"value\"} → {\"command\":\"value\"}\n repaired = repaired.replace(/([{,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*:/g, '$1\"$2\":');\n\n // Fix 3: Single quotes → double quotes\n // Simple approach: replace single quotes that act as JSON delimiters\n repaired = repaired.replace(/'/g, '\"');\n\n // Fix 4: Trailing commas before closing braces/brackets\n // e.g. {\"a\":1,} → {\"a\":1}\n repaired = repaired.replace(/,(\\s*[}\\]])/g, '$1');\n\n // Fix 5: Unquoted date/datetime values (issue #14230)\n // e.g. {\"dueStart\": 2026-04-15} → {\"dueStart\": \"2026-04-15\"}\n // e.g. {\"start\": 2026-04-15T09:00:00} → {\"start\": \"2026-04-15T09:00:00\"}\n repaired = repaired.replace(/:\\s*(\\d{4}-\\d{2}-\\d{2}(?:T[\\d:]+)?)\\s*([,}])/g, ': \"$1\"$2');\n\n try {\n return JSON.parse(repaired);\n } catch {\n return null;\n }\n}\n\nexport type StreamPart =\n | Exclude<LanguageModelV2StreamPart, { type: 'finish' }>\n | {\n type: 'finish';\n /** Includes 'tripwire' and 'retry' for processor scenarios */\n finishReason: LanguageModelV2FinishReason | 'tripwire' | 'retry';\n usage: LanguageModelV2Usage;\n providerMetadata: SharedV2ProviderMetadata;\n messages: {\n all: ModelMessage[];\n user: ModelMessage[];\n nonUser: AIV5ResponseMessage[];\n };\n };\n\nexport function convertFullStreamChunkToMastra(value: StreamPart, ctx: { runId: string }): ChunkType | undefined {\n switch (value.type) {\n case 'response-metadata':\n return {\n type: 'response-metadata',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: { ...value },\n };\n case 'text-start':\n return {\n type: 'text-start',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n providerMetadata: value.providerMetadata,\n },\n };\n case 'text-delta':\n // Keep empty deltas that carry provider metadata (e.g. Gemini thought signatures),\n // otherwise that metadata is dropped before it reaches the consumer.\n if (value.delta || value.providerMetadata != null) {\n return {\n type: 'text-delta',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n providerMetadata: value.providerMetadata,\n text: value.delta,\n },\n };\n }\n return;\n\n case 'text-end':\n return {\n type: 'text-end',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: value,\n };\n\n case 'reasoning-start':\n return {\n type: 'reasoning-start',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n providerMetadata: value.providerMetadata,\n },\n };\n\n case 'reasoning-delta':\n return {\n type: 'reasoning-delta',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n providerMetadata: value.providerMetadata,\n text: value.delta,\n },\n };\n\n case 'reasoning-end':\n return {\n type: 'reasoning-end',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n providerMetadata: value.providerMetadata,\n },\n };\n\n case 'source':\n return {\n type: 'source',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n id: value.id,\n sourceType: value.sourceType,\n title: value.title || '',\n mimeType: value.sourceType === 'document' ? value.mediaType : undefined,\n filename: value.sourceType === 'document' ? value.filename : undefined,\n url: value.sourceType === 'url' ? value.url : undefined,\n providerMetadata: value.providerMetadata,\n },\n };\n\n case 'file': {\n const pm = (value as any).providerMetadata;\n return {\n type: 'file',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n data: value.data,\n // URL-backed generated files flatten to URL strings, which are not base64.\n base64: typeof value.data === 'string' && !isUrlString(value.data) ? value.data : undefined,\n mimeType: value.mediaType,\n ...(pm != null ? { providerMetadata: pm } : {}),\n },\n };\n }\n\n case 'tool-call': {\n let toolCallInput: Record<string, any> | undefined = undefined;\n\n if (value.input) {\n const sanitized = sanitizeToolCallInput(value.input);\n if (sanitized) {\n try {\n toolCallInput = JSON.parse(sanitized);\n } catch {\n // JSON.parse failed — attempt to repair common LLM JSON errors\n const repaired = tryRepairJson(sanitized);\n if (repaired) {\n toolCallInput = repaired;\n } else {\n console.error('Error converting tool call input to JSON', {\n input: value.input,\n });\n toolCallInput = undefined;\n }\n }\n }\n }\n\n return {\n type: 'tool-call',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n toolCallId: value.toolCallId,\n toolName: value.toolName,\n args: toolCallInput,\n providerExecuted: value.providerExecuted,\n providerMetadata: value.providerMetadata,\n ...((value as { observability?: unknown }).observability\n ? { observability: (value as { observability?: unknown }).observability as any }\n : {}),\n },\n };\n }\n\n case 'tool-result':\n return {\n type: 'tool-result',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n toolCallId: value.toolCallId,\n toolName: value.toolName,\n result: value.result,\n isError: value.isError,\n providerExecuted: value.providerExecuted,\n providerMetadata: value.providerMetadata,\n },\n };\n\n case 'tool-input-start':\n return {\n type: 'tool-call-input-streaming-start',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n toolCallId: value.id,\n toolName: value.toolName,\n providerExecuted: value.providerExecuted,\n providerMetadata: value.providerMetadata,\n dynamic: (value as { dynamic?: boolean }).dynamic,\n ...((value as { observability?: unknown }).observability\n ? { observability: (value as { observability?: unknown }).observability as any }\n : {}),\n },\n };\n\n case 'tool-input-delta':\n if (value.delta) {\n return {\n type: 'tool-call-delta',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n argsTextDelta: value.delta,\n toolCallId: value.id,\n providerMetadata: value.providerMetadata,\n },\n };\n }\n return;\n\n case 'tool-input-end':\n return {\n type: 'tool-call-input-streaming-end',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n toolCallId: value.id,\n providerMetadata: value.providerMetadata,\n },\n };\n\n case 'finish':\n const { finishReason, usage, providerMetadata, messages, ...rest } = value;\n return {\n type: 'finish',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: {\n providerMetadata: value.providerMetadata,\n stepResult: {\n reason: normalizeFinishReason(value.finishReason),\n },\n output: {\n // Normalize usage to handle both V2 (flat) and V3 (nested) formats\n usage: normalizeUsage(value.usage),\n },\n metadata: {\n providerMetadata: value.providerMetadata,\n },\n messages: messages ?? {\n all: [],\n user: [],\n nonUser: [],\n },\n ...rest,\n },\n };\n case 'error':\n return {\n type: 'error',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: value,\n };\n\n case 'raw':\n return {\n type: 'raw',\n runId: ctx.runId,\n from: ChunkFrom.AGENT,\n payload: value.rawValue as Record<string, unknown>,\n };\n }\n return;\n}\n\nexport type OutputChunkType<OUTPUT = undefined> =\n | TextStreamPart<ToolSet>\n | ObjectStreamPart<Partial<OUTPUT>>\n | undefined;\n\nexport function convertMastraChunkToAISDKv5<OUTPUT = undefined>({\n chunk,\n mode = 'stream',\n}: {\n chunk: ChunkType<OUTPUT>;\n mode?: 'generate' | 'stream';\n}): OutputChunkType<OUTPUT> {\n switch (chunk.type) {\n case 'start':\n return {\n type: 'start',\n };\n case 'step-start':\n const { messageId: _messageId, ...rest } = chunk.payload;\n return {\n type: 'start-step',\n request: rest.request,\n warnings: rest.warnings || [],\n };\n case 'raw':\n return {\n type: 'raw',\n rawValue: chunk.payload,\n };\n\n case 'finish': {\n return {\n type: 'finish',\n // Cast needed: Mastra extends reason with 'tripwire' | 'retry' for processor scenarios\n finishReason: chunk.payload.stepResult.reason as LanguageModelV2FinishReason,\n // Cast needed: Mastra's LanguageModelUsage has optional properties, V2 has required-but-nullable\n totalUsage: chunk.payload.output.usage as LanguageModelV2Usage,\n };\n }\n case 'reasoning-start':\n return {\n type: 'reasoning-start',\n id: chunk.payload.id,\n providerMetadata: chunk.payload.providerMetadata,\n };\n case 'reasoning-delta':\n return {\n type: 'reasoning-delta',\n id: chunk.payload.id,\n text: chunk.payload.text,\n providerMetadata: chunk.payload.providerMetadata,\n };\n case 'reasoning-signature':\n throw new Error('AISDKv5 chunk type \"reasoning-signature\" not supported');\n case 'redacted-reasoning':\n throw new Error('AISDKv5 chunk type \"redacted-reasoning\" not supported');\n\n case 'reasoning-end':\n return {\n type: 'reasoning-end',\n id: chunk.payload.id,\n providerMetadata: chunk.payload.providerMetadata,\n };\n case 'source':\n if (chunk.payload.sourceType === 'url') {\n return {\n type: 'source',\n sourceType: 'url',\n id: chunk.payload.id,\n url: chunk.payload.url!,\n title: chunk.payload.title,\n providerMetadata: chunk.payload.providerMetadata,\n };\n } else {\n return {\n type: 'source',\n sourceType: 'document',\n id: chunk.payload.id,\n mediaType: chunk.payload.mimeType!,\n title: chunk.payload.title,\n filename: chunk.payload.filename,\n providerMetadata: chunk.payload.providerMetadata,\n };\n }\n case 'file': {\n const filePart =\n mode === 'generate'\n ? {\n type: 'file' as const,\n file: new DefaultGeneratedFile({\n data: chunk.payload.data,\n mediaType: chunk.payload.mime