@tanstack/ai 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.d.ts +4 -0
- package/dist/esm/activities/chat/index.js +50 -16
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.js +5 -4
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +16 -1
- package/dist/esm/activities/chat/stream/processor.js +120 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +1 -0
- package/dist/esm/activities/chat/tools/schema-converter.js +4 -2
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +3 -1
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +6 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.d.ts +3 -2
- package/dist/esm/activities/stream-generation-result.js +12 -4
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +2 -2
- package/dist/esm/stream-to-response.js +0 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.d.ts +18 -0
- package/dist/esm/strip-to-spec-middleware.js +20 -0
- package/dist/esm/strip-to-spec-middleware.js.map +1 -0
- package/dist/esm/types.d.ts +229 -95
- package/package.json +3 -2
- package/src/activities/chat/index.ts +90 -25
- package/src/activities/chat/middleware/compose.ts +6 -4
- package/src/activities/chat/stream/processor.ts +152 -25
- package/src/activities/chat/stream/types.ts +1 -0
- package/src/activities/chat/tools/schema-converter.ts +8 -2
- package/src/activities/chat/tools/tool-calls.ts +4 -2
- package/src/activities/generateVideo/index.ts +13 -6
- package/src/activities/stream-generation-result.ts +19 -11
- package/src/stream-to-response.ts +2 -4
- package/src/strip-to-spec-middleware.ts +37 -0
- package/src/types.ts +276 -109
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-calls.js","sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type {\n CustomEvent,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager {\n private toolCallsMap = new Map<number, ToolCall>()\n private tools: ReadonlyArray<Tool>\n\n constructor(tools: ReadonlyArray<Tool>) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n const index = event.index ?? this.toolCallsMap.size\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name: event.toolName,\n arguments: '',\n },\n ...(event.providerMetadata && {\n providerMetadata: event.providerMetadata,\n }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n }\n break\n }\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n let result = await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolResultContent =\n typeof result === 'string' ? result : JSON.stringify(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n model: finishEvent.model,\n timestamp: Date.now(),\n result: toolResultContent,\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // Final flush in case events were emitted right at completion\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : skipResult || null,\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nasync function* executeServerTool(\n toolCall: ToolCall,\n tool: Tool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext,\n pendingEvents: Array<CustomEvent>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, void, void> {\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(tool.execute!(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n // Flush remaining events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n // Validate output against outputSchema if provided\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : result || null\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map of approval decisions (approval.id -> approved boolean)\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<Tool>,\n approvals: Map<string, boolean> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, Tool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return t?.needsApproval && !approvals.has(`approval_${tc.id}`)\n })\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n if (!tool.needsApproval || approvals.has(`approval_${toolCall.id}`)) {\n continue\n }\n }\n\n // Parse arguments, throwing error if invalid JSON\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context: ToolExecutionContext = {\n toolCallId: toolCall.id,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\n }),\n )\n }\n },\n }\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Apply middleware before-hook for approved tools\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-calls.js","sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type {\n CustomEvent,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager {\n private toolCallsMap = new Map<number, ToolCall>()\n private tools: ReadonlyArray<Tool>\n\n constructor(tools: ReadonlyArray<Tool>) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n const index = event.index ?? this.toolCallsMap.size\n const name = event.toolCallName\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name,\n arguments: '',\n },\n ...(event.providerMetadata && {\n providerMetadata: event.providerMetadata,\n }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n }\n break\n }\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n let result = await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolResultContent =\n typeof result === 'string' ? result : JSON.stringify(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.function.name,\n toolName: toolCall.function.name,\n model: finishEvent.model,\n timestamp: Date.now(),\n result: toolResultContent,\n } as ToolCallEndEvent\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // Final flush in case events were emitted right at completion\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : skipResult || null,\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nasync function* executeServerTool(\n toolCall: ToolCall,\n tool: Tool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext,\n pendingEvents: Array<CustomEvent>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, void, void> {\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(tool.execute!(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n // Flush remaining events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n // Validate output against outputSchema if provided\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : result || null\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map of approval decisions (approval.id -> approved boolean)\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<Tool>,\n approvals: Map<string, boolean> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, Tool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return t?.needsApproval && !approvals.has(`approval_${tc.id}`)\n })\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n if (!tool.needsApproval || approvals.has(`approval_${toolCall.id}`)) {\n continue\n }\n }\n\n // Parse arguments, throwing error if invalid JSON\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context: ToolExecutionContext = {\n toolCallId: toolCall.id,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\n }),\n )\n }\n },\n }\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Apply middleware before-hook for approved tools\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -31,9 +31,11 @@ async function* runStreamingVideoGeneration(options) {
|
|
|
31
31
|
const runId = options.runId ?? createId("run");
|
|
32
32
|
const pollingInterval = options.pollingInterval ?? 2e3;
|
|
33
33
|
const maxDuration = options.maxDuration ?? 6e5;
|
|
34
|
+
const threadId = createId("thread");
|
|
34
35
|
yield {
|
|
35
36
|
type: "RUN_STARTED",
|
|
36
37
|
runId,
|
|
38
|
+
threadId,
|
|
37
39
|
timestamp: Date.now()
|
|
38
40
|
};
|
|
39
41
|
try {
|
|
@@ -81,6 +83,7 @@ async function* runStreamingVideoGeneration(options) {
|
|
|
81
83
|
yield {
|
|
82
84
|
type: "RUN_FINISHED",
|
|
83
85
|
runId,
|
|
86
|
+
threadId,
|
|
84
87
|
finishReason: "stop",
|
|
85
88
|
timestamp: Date.now()
|
|
86
89
|
};
|
|
@@ -95,6 +98,9 @@ async function* runStreamingVideoGeneration(options) {
|
|
|
95
98
|
yield {
|
|
96
99
|
type: "RUN_ERROR",
|
|
97
100
|
runId,
|
|
101
|
+
threadId,
|
|
102
|
+
message: error.message || "Video generation failed",
|
|
103
|
+
code: error.code,
|
|
98
104
|
error: {
|
|
99
105
|
message: error.message || "Video generation failed",
|
|
100
106
|
code: error.code
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateVideo/index.ts"],"sourcesContent":["/**\n * Video Activity (Experimental)\n *\n * Generates videos from text prompts using a jobs/polling architecture.\n * This is a self-contained module with implementation, types, and JSDoc.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport type { VideoAdapter } from './adapter'\nimport type {\n StreamChunk,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'video' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a VideoAdapter via ~types.\n */\nexport type VideoProviderOptions<TAdapter> =\n TAdapter extends VideoAdapter<any, any, any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n/**\n * Extract the size type for a VideoAdapter's model via ~types.\n */\nexport type VideoSizeForAdapter<TAdapter> =\n TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>\n ? TModel extends keyof TSizeMap\n ? TSizeMap[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Types\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n// ===========================\n\n/**\n * Base options shared by all video activity operations.\n * The model is extracted from the adapter's model property.\n */\ninterface VideoActivityBaseOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> {\n /** The video adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n}\n\n/**\n * Options for creating a new video generation job.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The video adapter type\n * @template TStream - Whether to stream the output\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoCreateOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n> = VideoActivityBaseOptions<TAdapter> & {\n /** Request type - create a new job (default if not specified) */\n request?: 'create'\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on the provider (e.g., \"16:9\", \"1280x720\") */\n size?: VideoSizeForAdapter<TAdapter>\n /** Video duration in seconds */\n duration?: number\n /**\n * Whether to stream the video generation lifecycle.\n * When true, returns an AsyncIterable<StreamChunk> that handles the full\n * job lifecycle: create job, poll for status, yield updates, and yield final result.\n * When false or not provided, returns a Promise<VideoJobResult>.\n *\n * @default false\n */\n stream?: TStream\n /** Polling interval in milliseconds (stream mode only). @default 2000 */\n pollingInterval?: number\n /** Maximum time to wait before timing out in milliseconds (stream mode only). @default 600000 */\n maxDuration?: number\n /** Custom run ID (stream mode only) */\n runId?: string\n} & ({} extends VideoProviderOptions<TAdapter>\n ? {\n /** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>\n }\n : {\n /** Provider-specific options for video generation */ modelOptions: VideoProviderOptions<TAdapter>\n })\n\n/**\n * Options for polling the status of a video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoStatusOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get job status */\n request: 'status'\n /** The job ID to check status for */\n jobId: string\n}\n\n/**\n * Options for getting the URL of a completed video.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoUrlOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get video URL */\n request: 'url'\n /** The job ID to get URL for */\n jobId: string\n}\n\n/**\n * Union type for all video activity options.\n * Discriminated by the `request` field.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? VideoStatusOptions<TAdapter>\n : TRequest extends 'url'\n ? VideoUrlOptions<TAdapter>\n : VideoCreateOptions<TAdapter, TStream>\n\n// ===========================\n// Activity Result Types\n// ===========================\n\n/**\n * Result type for the video activity, based on request type and streaming.\n * - If stream is true (create request): AsyncIterable<StreamChunk>\n * - Otherwise: Promise<VideoJobResult | VideoStatusResult | VideoUrlResult>\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityResult<\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? Promise<VideoStatusResult>\n : TRequest extends 'url'\n ? Promise<VideoUrlResult>\n : TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<VideoJobResult>\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Generate video - creates a video generation job from a text prompt.\n *\n * Uses AI video generation models to create videos based on natural language descriptions.\n * Unlike image generation, video generation is asynchronous and requires polling for completion.\n *\n * When `stream: true` is passed, handles the full job lifecycle automatically:\n * create job → poll for status → stream updates → yield final result.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Create a video generation job\n * ```ts\n * import { generateVideo } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * // Start a video generation job\n * const { jobId } = await generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park'\n * })\n *\n * console.log('Job started:', jobId)\n * ```\n *\n * @example Stream the full video generation lifecycle\n * ```ts\n * import { generateVideo, toServerSentEventsResponse } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const stream = generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park',\n * stream: true,\n * pollingInterval: 3000,\n * })\n *\n * return toServerSentEventsResponse(stream)\n * ```\n */\nexport function generateVideo<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoActivityResult<'create', TStream> {\n if (options.stream) {\n return runStreamingVideoGeneration(\n options as VideoCreateOptions<TAdapter, true>,\n ) as VideoActivityResult<'create', TStream>\n }\n\n return runCreateVideoJob(options) as VideoActivityResult<'create', TStream>\n}\n\n/**\n * Internal implementation of non-streaming video job creation.\n */\nasync function runCreateVideoJob<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, boolean>): Promise<VideoJobResult> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n\n return adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Internal streaming implementation for video generation.\n * Handles the full job lifecycle: create job → poll for status → stream updates → yield final result.\n */\nasync function* runStreamingVideoGeneration<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, true>): AsyncIterable<StreamChunk> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n const runId = options.runId ?? createId('run')\n const pollingInterval = options.pollingInterval ?? 2000\n const maxDuration = options.maxDuration ?? 600_000\n\n yield {\n type: 'RUN_STARTED',\n runId,\n timestamp: Date.now(),\n }\n\n try {\n // Create the video generation job\n const jobResult = await adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n\n yield {\n type: 'CUSTOM',\n name: 'video:job:created',\n value: { jobId: jobResult.jobId },\n timestamp: Date.now(),\n }\n\n // Poll for completion\n const startTime = Date.now()\n while (Date.now() - startTime < maxDuration) {\n await sleep(pollingInterval)\n\n const statusResult = await adapter.getVideoStatus(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'video:status',\n value: {\n jobId: jobResult.jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n },\n timestamp: Date.now(),\n }\n\n if (statusResult.status === 'completed') {\n const urlResult = await adapter.getVideoUrl(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'generation:result',\n value: {\n jobId: jobResult.jobId,\n status: 'completed',\n url: urlResult.url,\n expiresAt: urlResult.expiresAt,\n },\n timestamp: Date.now(),\n }\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n finishReason: 'stop',\n timestamp: Date.now(),\n }\n return\n }\n\n if (statusResult.status === 'failed') {\n throw new Error(statusResult.error || 'Video generation failed')\n }\n }\n\n throw new Error('Video generation timed out')\n } catch (error: any) {\n yield {\n type: 'RUN_ERROR',\n runId,\n error: {\n message: error.message || 'Video generation failed',\n code: error.code,\n },\n timestamp: Date.now(),\n }\n }\n}\n\n/**\n * Get video job status - returns the current status, progress, and URL if available.\n *\n * This function combines status checking and URL retrieval. If the job is completed,\n * it will automatically fetch and include the video URL.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Check job status\n * ```ts\n * import { getVideoJobStatus } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const result = await getVideoJobStatus({\n * adapter: openaiVideo('sora-2'),\n * jobId: 'job-123'\n * })\n *\n * console.log('Status:', result.status)\n * console.log('Progress:', result.progress)\n * if (result.url) {\n * console.log('Video URL:', result.url)\n * }\n * ```\n */\nexport async function getVideoJobStatus<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: {\n adapter: TAdapter & { kind: typeof kind }\n jobId: string\n}): Promise<{\n status: 'pending' | 'processing' | 'completed' | 'failed'\n progress?: number\n url?: string\n error?: string\n}> {\n const { adapter, jobId } = options\n const requestId = createId('video-status')\n const startTime = Date.now()\n\n aiEventClient.emit('video:request:started', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n timestamp: startTime,\n })\n\n // Get status first\n const statusResult = await adapter.getVideoStatus(jobId)\n\n // If completed, also get the URL\n if (statusResult.status === 'completed') {\n try {\n const urlResult = await adapter.getVideoUrl(jobId)\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Failed to get video URL'\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: 'failed',\n progress: statusResult.progress,\n error: errorMessage,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // Provider reported completed but result fetch failed — treat as failed\n return {\n status: 'failed' as const,\n progress: statusResult.progress,\n error: errorMessage,\n }\n }\n }\n\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n\n // Return status for non-completed jobs\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateVideo() function without executing.\n */\nexport function createVideoOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoCreateOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n VideoAdapter,\n VideoAdapterConfig,\n AnyVideoAdapter,\n} from './adapter'\nexport { BaseVideoAdapter } from './adapter'\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateVideo/index.ts"],"sourcesContent":["/**\n * Video Activity (Experimental)\n *\n * Generates videos from text prompts using a jobs/polling architecture.\n * This is a self-contained module with implementation, types, and JSDoc.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport type { VideoAdapter } from './adapter'\nimport type {\n StreamChunk,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'video' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a VideoAdapter via ~types.\n */\nexport type VideoProviderOptions<TAdapter> =\n TAdapter extends VideoAdapter<any, any, any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n/**\n * Extract the size type for a VideoAdapter's model via ~types.\n */\nexport type VideoSizeForAdapter<TAdapter> =\n TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>\n ? TModel extends keyof TSizeMap\n ? TSizeMap[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Types\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n// ===========================\n\n/**\n * Base options shared by all video activity operations.\n * The model is extracted from the adapter's model property.\n */\ninterface VideoActivityBaseOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> {\n /** The video adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n}\n\n/**\n * Options for creating a new video generation job.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The video adapter type\n * @template TStream - Whether to stream the output\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoCreateOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n> = VideoActivityBaseOptions<TAdapter> & {\n /** Request type - create a new job (default if not specified) */\n request?: 'create'\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on the provider (e.g., \"16:9\", \"1280x720\") */\n size?: VideoSizeForAdapter<TAdapter>\n /** Video duration in seconds */\n duration?: number\n /**\n * Whether to stream the video generation lifecycle.\n * When true, returns an AsyncIterable<StreamChunk> that handles the full\n * job lifecycle: create job, poll for status, yield updates, and yield final result.\n * When false or not provided, returns a Promise<VideoJobResult>.\n *\n * @default false\n */\n stream?: TStream\n /** Polling interval in milliseconds (stream mode only). @default 2000 */\n pollingInterval?: number\n /** Maximum time to wait before timing out in milliseconds (stream mode only). @default 600000 */\n maxDuration?: number\n /** Custom run ID (stream mode only) */\n runId?: string\n} & ({} extends VideoProviderOptions<TAdapter>\n ? {\n /** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>\n }\n : {\n /** Provider-specific options for video generation */ modelOptions: VideoProviderOptions<TAdapter>\n })\n\n/**\n * Options for polling the status of a video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoStatusOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get job status */\n request: 'status'\n /** The job ID to check status for */\n jobId: string\n}\n\n/**\n * Options for getting the URL of a completed video.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoUrlOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get video URL */\n request: 'url'\n /** The job ID to get URL for */\n jobId: string\n}\n\n/**\n * Union type for all video activity options.\n * Discriminated by the `request` field.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? VideoStatusOptions<TAdapter>\n : TRequest extends 'url'\n ? VideoUrlOptions<TAdapter>\n : VideoCreateOptions<TAdapter, TStream>\n\n// ===========================\n// Activity Result Types\n// ===========================\n\n/**\n * Result type for the video activity, based on request type and streaming.\n * - If stream is true (create request): AsyncIterable<StreamChunk>\n * - Otherwise: Promise<VideoJobResult | VideoStatusResult | VideoUrlResult>\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityResult<\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? Promise<VideoStatusResult>\n : TRequest extends 'url'\n ? Promise<VideoUrlResult>\n : TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<VideoJobResult>\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Generate video - creates a video generation job from a text prompt.\n *\n * Uses AI video generation models to create videos based on natural language descriptions.\n * Unlike image generation, video generation is asynchronous and requires polling for completion.\n *\n * When `stream: true` is passed, handles the full job lifecycle automatically:\n * create job → poll for status → stream updates → yield final result.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Create a video generation job\n * ```ts\n * import { generateVideo } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * // Start a video generation job\n * const { jobId } = await generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park'\n * })\n *\n * console.log('Job started:', jobId)\n * ```\n *\n * @example Stream the full video generation lifecycle\n * ```ts\n * import { generateVideo, toServerSentEventsResponse } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const stream = generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park',\n * stream: true,\n * pollingInterval: 3000,\n * })\n *\n * return toServerSentEventsResponse(stream)\n * ```\n */\nexport function generateVideo<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoActivityResult<'create', TStream> {\n if (options.stream) {\n return runStreamingVideoGeneration(\n options as VideoCreateOptions<TAdapter, true>,\n ) as VideoActivityResult<'create', TStream>\n }\n\n return runCreateVideoJob(options) as VideoActivityResult<'create', TStream>\n}\n\n/**\n * Internal implementation of non-streaming video job creation.\n */\nasync function runCreateVideoJob<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, boolean>): Promise<VideoJobResult> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n\n return adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Internal streaming implementation for video generation.\n * Handles the full job lifecycle: create job → poll for status → stream updates → yield final result.\n */\nasync function* runStreamingVideoGeneration<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, true>): AsyncIterable<StreamChunk> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n const runId = options.runId ?? createId('run')\n const pollingInterval = options.pollingInterval ?? 2000\n const maxDuration = options.maxDuration ?? 600_000\n\n const threadId = createId('thread')\n\n yield {\n type: 'RUN_STARTED',\n runId,\n threadId,\n timestamp: Date.now(),\n } as StreamChunk\n\n try {\n // Create the video generation job\n const jobResult = await adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n\n yield {\n type: 'CUSTOM',\n name: 'video:job:created',\n value: { jobId: jobResult.jobId },\n timestamp: Date.now(),\n } as StreamChunk\n\n // Poll for completion\n const startTime = Date.now()\n while (Date.now() - startTime < maxDuration) {\n await sleep(pollingInterval)\n\n const statusResult = await adapter.getVideoStatus(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'video:status',\n value: {\n jobId: jobResult.jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n },\n timestamp: Date.now(),\n } as StreamChunk\n\n if (statusResult.status === 'completed') {\n const urlResult = await adapter.getVideoUrl(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'generation:result',\n value: {\n jobId: jobResult.jobId,\n status: 'completed',\n url: urlResult.url,\n expiresAt: urlResult.expiresAt,\n },\n timestamp: Date.now(),\n } as StreamChunk\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n threadId,\n finishReason: 'stop',\n timestamp: Date.now(),\n } as StreamChunk\n return\n }\n\n if (statusResult.status === 'failed') {\n throw new Error(statusResult.error || 'Video generation failed')\n }\n }\n\n throw new Error('Video generation timed out')\n } catch (error: any) {\n yield {\n type: 'RUN_ERROR',\n runId,\n threadId,\n message: error.message || 'Video generation failed',\n code: error.code,\n error: {\n message: error.message || 'Video generation failed',\n code: error.code,\n },\n timestamp: Date.now(),\n } as StreamChunk\n }\n}\n\n/**\n * Get video job status - returns the current status, progress, and URL if available.\n *\n * This function combines status checking and URL retrieval. If the job is completed,\n * it will automatically fetch and include the video URL.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Check job status\n * ```ts\n * import { getVideoJobStatus } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const result = await getVideoJobStatus({\n * adapter: openaiVideo('sora-2'),\n * jobId: 'job-123'\n * })\n *\n * console.log('Status:', result.status)\n * console.log('Progress:', result.progress)\n * if (result.url) {\n * console.log('Video URL:', result.url)\n * }\n * ```\n */\nexport async function getVideoJobStatus<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: {\n adapter: TAdapter & { kind: typeof kind }\n jobId: string\n}): Promise<{\n status: 'pending' | 'processing' | 'completed' | 'failed'\n progress?: number\n url?: string\n error?: string\n}> {\n const { adapter, jobId } = options\n const requestId = createId('video-status')\n const startTime = Date.now()\n\n aiEventClient.emit('video:request:started', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n timestamp: startTime,\n })\n\n // Get status first\n const statusResult = await adapter.getVideoStatus(jobId)\n\n // If completed, also get the URL\n if (statusResult.status === 'completed') {\n try {\n const urlResult = await adapter.getVideoUrl(jobId)\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Failed to get video URL'\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: 'failed',\n progress: statusResult.progress,\n error: errorMessage,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // Provider reported completed but result fetch failed — treat as failed\n return {\n status: 'failed' as const,\n progress: statusResult.progress,\n error: errorMessage,\n }\n }\n }\n\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n\n // Return status for non-completed jobs\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateVideo() function without executing.\n */\nexport function createVideoOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoCreateOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n VideoAdapter,\n VideoAdapterConfig,\n AnyVideoAdapter,\n} from './adapter'\nexport { BaseVideoAdapter } from './adapter'\n"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,10 @@ import { StreamChunk } from '../types.js';
|
|
|
6
6
|
* to be sent over the same streaming transport as chat.
|
|
7
7
|
*
|
|
8
8
|
* @param generator - An async function that performs the generation and returns the result
|
|
9
|
-
* @param options - Optional configuration (runId)
|
|
10
|
-
* @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events
|
|
9
|
+
* @param options - Optional configuration (runId, threadId)
|
|
10
|
+
* @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events on success, or RUN_STARTED and RUN_ERROR on failure
|
|
11
11
|
*/
|
|
12
12
|
export declare function streamGenerationResult<TResult>(generator: () => Promise<TResult>, options?: {
|
|
13
13
|
runId?: string;
|
|
14
|
+
threadId?: string;
|
|
14
15
|
}): AsyncIterable<StreamChunk>;
|
|
@@ -1,31 +1,39 @@
|
|
|
1
|
+
import { EventType } from "@ag-ui/core";
|
|
1
2
|
function createId(prefix) {
|
|
2
3
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3
4
|
}
|
|
4
5
|
async function* streamGenerationResult(generator, options) {
|
|
5
6
|
const runId = createId("run");
|
|
7
|
+
const threadId = createId("thread");
|
|
6
8
|
yield {
|
|
7
|
-
type:
|
|
9
|
+
type: EventType.RUN_STARTED,
|
|
8
10
|
runId,
|
|
11
|
+
threadId,
|
|
9
12
|
timestamp: Date.now()
|
|
10
13
|
};
|
|
11
14
|
try {
|
|
12
15
|
const result = await generator();
|
|
13
16
|
yield {
|
|
14
|
-
type:
|
|
17
|
+
type: EventType.CUSTOM,
|
|
15
18
|
name: "generation:result",
|
|
16
19
|
value: result,
|
|
17
20
|
timestamp: Date.now()
|
|
18
21
|
};
|
|
19
22
|
yield {
|
|
20
|
-
type:
|
|
23
|
+
type: EventType.RUN_FINISHED,
|
|
21
24
|
runId,
|
|
25
|
+
threadId,
|
|
22
26
|
finishReason: "stop",
|
|
23
27
|
timestamp: Date.now()
|
|
24
28
|
};
|
|
25
29
|
} catch (error) {
|
|
26
30
|
yield {
|
|
27
|
-
type:
|
|
31
|
+
type: EventType.RUN_ERROR,
|
|
28
32
|
runId,
|
|
33
|
+
threadId,
|
|
34
|
+
message: error.message || "Generation failed",
|
|
35
|
+
code: error.code,
|
|
36
|
+
// Deprecated nested form for backward compatibility
|
|
29
37
|
error: {
|
|
30
38
|
message: error.message || "Generation failed",
|
|
31
39
|
code: error.code
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-generation-result.js","sources":["../../../src/activities/stream-generation-result.ts"],"sourcesContent":["/**\n * Internal helper for wrapping one-shot generation results as StreamChunk\n * async iterables. NOT exported from the package — used only by activity\n * implementations to support `stream: true`.\n */\n\nimport type { StreamChunk } from '../types'\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Wrap a one-shot generation result as a StreamChunk async iterable.\n *\n * This allows non-streaming activities (image, speech, transcription, summarize)\n * to be sent over the same streaming transport as chat.\n *\n * @param generator - An async function that performs the generation and returns the result\n * @param options - Optional configuration (runId)\n * @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events\n */\nexport async function* streamGenerationResult<TResult>(\n generator: () => Promise<TResult>,\n options?: { runId?: string },\n): AsyncIterable<StreamChunk> {\n const runId = options?.runId ?? createId('run')\n\n yield {\n type:
|
|
1
|
+
{"version":3,"file":"stream-generation-result.js","sources":["../../../src/activities/stream-generation-result.ts"],"sourcesContent":["/**\n * Internal helper for wrapping one-shot generation results as StreamChunk\n * async iterables. NOT exported from the package — used only by activity\n * implementations to support `stream: true`.\n */\n\nimport { EventType } from '@ag-ui/core'\nimport type { StreamChunk } from '../types'\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Wrap a one-shot generation result as a StreamChunk async iterable.\n *\n * This allows non-streaming activities (image, speech, transcription, summarize)\n * to be sent over the same streaming transport as chat.\n *\n * @param generator - An async function that performs the generation and returns the result\n * @param options - Optional configuration (runId, threadId)\n * @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events on success, or RUN_STARTED and RUN_ERROR on failure\n */\nexport async function* streamGenerationResult<TResult>(\n generator: () => Promise<TResult>,\n options?: { runId?: string; threadId?: string },\n): AsyncIterable<StreamChunk> {\n const runId = options?.runId ?? createId('run')\n const threadId = options?.threadId ?? createId('thread')\n\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n timestamp: Date.now(),\n } as StreamChunk\n\n try {\n const result = await generator()\n\n yield {\n type: EventType.CUSTOM,\n name: 'generation:result',\n value: result as unknown,\n timestamp: Date.now(),\n } as StreamChunk\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n finishReason: 'stop',\n timestamp: Date.now(),\n } as StreamChunk\n } catch (error: any) {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n threadId,\n message: error.message || 'Generation failed',\n code: error.code,\n // Deprecated nested form for backward compatibility\n error: {\n message: error.message || 'Generation failed',\n code: error.code,\n },\n timestamp: Date.now(),\n } as StreamChunk\n }\n}\n"],"names":[],"mappings":";AASA,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAYA,gBAAuB,uBACrB,WACA,SAC4B;AAC5B,QAAM,QAA0B,SAAS,KAAK;AAC9C,QAAM,WAAgC,SAAS,QAAQ;AAEvD,QAAM;AAAA,IACJ,MAAM,UAAU;AAAA,IAChB;AAAA,IACA;AAAA,IACA,WAAW,KAAK,IAAA;AAAA,EAAI;AAGtB,MAAI;AACF,UAAM,SAAS,MAAM,UAAA;AAErB,UAAM;AAAA,MACJ,MAAM,UAAU;AAAA,MAChB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,UAAM;AAAA,MACJ,MAAM,UAAU;AAAA,MAChB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB,SAAS,OAAY;AACnB,UAAM;AAAA,MACJ,MAAM,UAAU;AAAA,MAChB;AAAA,MACA;AAAA,MACA,SAAS,MAAM,WAAW;AAAA,MAC1B,MAAM,MAAM;AAAA;AAAA,MAEZ,OAAO;AAAA,QACL,SAAS,MAAM,WAAW;AAAA,QAC1B,MAAM,MAAM;AAAA,MAAA;AAAA,MAEd,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB;AACF;"}
|
package/dist/esm/index.js
CHANGED
|
@@ -17,9 +17,11 @@ import { createModel, extendAdapter } from "./extend-adapter.js";
|
|
|
17
17
|
import { StreamProcessor, createReplayStream } from "./activities/chat/stream/processor.js";
|
|
18
18
|
import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PunctuationStrategy, WordBoundaryStrategy } from "./activities/chat/stream/strategies.js";
|
|
19
19
|
import { PartialJSONParser, defaultJSONParser, parsePartialJSON } from "./activities/chat/stream/json-parser.js";
|
|
20
|
+
import { EventType } from "@ag-ui/core";
|
|
20
21
|
export {
|
|
21
22
|
BatchStrategy,
|
|
22
23
|
CompositeStrategy,
|
|
24
|
+
EventType,
|
|
23
25
|
ImmediateStrategy,
|
|
24
26
|
PartialJSONParser,
|
|
25
27
|
PunctuationStrategy,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -26,7 +26,7 @@ export declare function streamToText(stream: AsyncIterable<StreamChunk>): Promis
|
|
|
26
26
|
* This creates a ReadableStream that emits chunks in SSE format:
|
|
27
27
|
* - Each chunk is prefixed with "data: "
|
|
28
28
|
* - Each chunk is followed by "\n\n"
|
|
29
|
-
* - Stream ends
|
|
29
|
+
* - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)
|
|
30
30
|
*
|
|
31
31
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
32
32
|
* @param abortController - Optional AbortController to abort when stream is cancelled
|
|
@@ -39,7 +39,7 @@ export declare function toServerSentEventsStream(stream: AsyncIterable<StreamChu
|
|
|
39
39
|
* This creates a Response that emits chunks in SSE format:
|
|
40
40
|
* - Each chunk is prefixed with "data: "
|
|
41
41
|
* - Each chunk is followed by "\n\n"
|
|
42
|
-
* - Stream ends
|
|
42
|
+
* - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)
|
|
43
43
|
*
|
|
44
44
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
45
45
|
* @param init - Optional Response initialization options (including `abortController`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-to-response.js","sources":["../../src/stream-to-response.ts"],"sourcesContent":["import type { StreamChunk } from './types'\n\n/**\n * Collect all text content from a StreamChunk async iterable and return as a string.\n *\n * This function consumes the entire stream, accumulating content from TEXT_MESSAGE_CONTENT events,\n * and returns the final concatenated text.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @returns Promise<string> - The accumulated text content\n *\n * @example\n * ```typescript\n * const stream = chat({\n * adapter: openaiText(),\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }]\n * });\n * const text = await streamToText(stream);\n * console.log(text); // \"Hello! How can I help you today?\"\n * ```\n */\nexport async function streamToText(\n stream: AsyncIterable<StreamChunk>,\n): Promise<string> {\n let accumulatedContent = ''\n\n for await (const chunk of stream) {\n if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {\n accumulatedContent += chunk.delta\n }\n }\n\n return accumulatedContent\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in Server-Sent Events format\n *\n * This creates a ReadableStream that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends with \"data: [DONE]\\n\\n\"\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in Server-Sent Events format\n */\nexport function toServerSentEventsStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as Server-Sent Events format\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`),\n )\n }\n\n // Send completion marker\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'))\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error event (AG-UI RUN_ERROR)\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({\n type: 'RUN_ERROR',\n timestamp: Date.now(),\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in Server-Sent Events format\n *\n * This creates a Response that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends with \"data: [DONE]\\n\\n\"\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in Server-Sent Events format\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toServerSentEventsResponse(stream, { abortController });\n * ```\n */\nexport function toServerSentEventsResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n const { headers, abortController, ...responseInit } = init ?? {}\n\n // Start with default SSE headers\n const mergedHeaders = new Headers({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n })\n\n // Override with user headers if provided, handling all HeadersInit forms:\n // Headers instance, string[][], or plain object\n if (headers) {\n const userHeaders = new Headers(headers)\n userHeaders.forEach((value, key) => {\n mergedHeaders.set(key, value)\n })\n }\n\n return new Response(toServerSentEventsStream(stream, abortController), {\n ...responseInit,\n headers: mergedHeaders,\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * This creates a ReadableStream that emits chunks as newline-delimited JSON:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * const readableStream = toHttpStream(stream);\n * // Use with Response for HTTP streaming (not SSE)\n * return new Response(readableStream, {\n * headers: { 'Content-Type': 'application/x-ndjson' }\n * });\n * ```\n */\nexport function toHttpStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as newline-delimited JSON\n controller.enqueue(encoder.encode(`${JSON.stringify(chunk)}\\n`))\n }\n\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error event (AG-UI RUN_ERROR)\n controller.enqueue(\n encoder.encode(\n `${JSON.stringify({\n type: 'RUN_ERROR',\n timestamp: Date.now(),\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in HTTP stream format (newline-delimited JSON)\n *\n * This creates a Response that emits chunks in HTTP stream format:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toHttpResponse(stream, { abortController });\n * ```\n */\nexport function toHttpResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n return new Response(toHttpStream(stream, init?.abortController), {\n ...init,\n })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream-to-response.js","sources":["../../src/stream-to-response.ts"],"sourcesContent":["import type { StreamChunk } from './types'\n\n/**\n * Collect all text content from a StreamChunk async iterable and return as a string.\n *\n * This function consumes the entire stream, accumulating content from TEXT_MESSAGE_CONTENT events,\n * and returns the final concatenated text.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @returns Promise<string> - The accumulated text content\n *\n * @example\n * ```typescript\n * const stream = chat({\n * adapter: openaiText(),\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }]\n * });\n * const text = await streamToText(stream);\n * console.log(text); // \"Hello! How can I help you today?\"\n * ```\n */\nexport async function streamToText(\n stream: AsyncIterable<StreamChunk>,\n): Promise<string> {\n let accumulatedContent = ''\n\n for await (const chunk of stream) {\n if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {\n accumulatedContent += chunk.delta\n }\n }\n\n return accumulatedContent\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in Server-Sent Events format\n *\n * This creates a ReadableStream that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in Server-Sent Events format\n */\nexport function toServerSentEventsStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as Server-Sent Events format\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`),\n )\n }\n\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error event (AG-UI RUN_ERROR)\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({\n type: 'RUN_ERROR',\n timestamp: Date.now(),\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in Server-Sent Events format\n *\n * This creates a Response that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends when the underlying iterable is exhausted (RUN_FINISHED is the terminal event)\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in Server-Sent Events format\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toServerSentEventsResponse(stream, { abortController });\n * ```\n */\nexport function toServerSentEventsResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n const { headers, abortController, ...responseInit } = init ?? {}\n\n // Start with default SSE headers\n const mergedHeaders = new Headers({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n })\n\n // Override with user headers if provided, handling all HeadersInit forms:\n // Headers instance, string[][], or plain object\n if (headers) {\n const userHeaders = new Headers(headers)\n userHeaders.forEach((value, key) => {\n mergedHeaders.set(key, value)\n })\n }\n\n return new Response(toServerSentEventsStream(stream, abortController), {\n ...responseInit,\n headers: mergedHeaders,\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * This creates a ReadableStream that emits chunks as newline-delimited JSON:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * const readableStream = toHttpStream(stream);\n * // Use with Response for HTTP streaming (not SSE)\n * return new Response(readableStream, {\n * headers: { 'Content-Type': 'application/x-ndjson' }\n * });\n * ```\n */\nexport function toHttpStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as newline-delimited JSON\n controller.enqueue(encoder.encode(`${JSON.stringify(chunk)}\\n`))\n }\n\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error event (AG-UI RUN_ERROR)\n controller.enqueue(\n encoder.encode(\n `${JSON.stringify({\n type: 'RUN_ERROR',\n timestamp: Date.now(),\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in HTTP stream format (newline-delimited JSON)\n *\n * This creates a Response that emits chunks in HTTP stream format:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toHttpResponse(stream, { abortController });\n * ```\n */\nexport function toHttpResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n return new Response(toHttpStream(stream, init?.abortController), {\n ...init,\n })\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ChatMiddleware } from './activities/chat/middleware/types.js';
|
|
2
|
+
import { StreamChunk } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Strip only the deprecated nested `error` object from RUN_ERROR events.
|
|
5
|
+
* The flat `message`/`code` fields are the spec-compliant form.
|
|
6
|
+
*
|
|
7
|
+
* All other fields pass through unchanged. @ag-ui/core's BaseEventSchema
|
|
8
|
+
* uses `.passthrough()`, so extra fields (model, content, usage,
|
|
9
|
+
* finishReason, toolName, stepId, etc.) are allowed and won't break
|
|
10
|
+
* spec validation or verifyEvents.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stripToSpec(chunk: StreamChunk): StreamChunk;
|
|
13
|
+
/**
|
|
14
|
+
* Middleware that ensures events are AG-UI spec compliant.
|
|
15
|
+
* Currently only strips the deprecated nested `error` object from RUN_ERROR.
|
|
16
|
+
* All other fields pass through unchanged (passthrough allowed by spec).
|
|
17
|
+
*/
|
|
18
|
+
export declare function stripToSpecMiddleware(): ChatMiddleware;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function stripToSpec(chunk) {
|
|
2
|
+
if (chunk.type === "RUN_ERROR" && "error" in chunk) {
|
|
3
|
+
const { error: _deprecated, ...rest } = chunk;
|
|
4
|
+
return rest;
|
|
5
|
+
}
|
|
6
|
+
return chunk;
|
|
7
|
+
}
|
|
8
|
+
function stripToSpecMiddleware() {
|
|
9
|
+
return {
|
|
10
|
+
name: "strip-to-spec",
|
|
11
|
+
onChunk(_ctx, chunk) {
|
|
12
|
+
return stripToSpec(chunk);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
stripToSpec,
|
|
18
|
+
stripToSpecMiddleware
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=strip-to-spec-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-to-spec-middleware.js","sources":["../../src/strip-to-spec-middleware.ts"],"sourcesContent":["import type { ChatMiddleware } from './activities/chat/middleware/types'\nimport type { StreamChunk } from './types'\n\n/**\n * Strip only the deprecated nested `error` object from RUN_ERROR events.\n * The flat `message`/`code` fields are the spec-compliant form.\n *\n * All other fields pass through unchanged. @ag-ui/core's BaseEventSchema\n * uses `.passthrough()`, so extra fields (model, content, usage,\n * finishReason, toolName, stepId, etc.) are allowed and won't break\n * spec validation or verifyEvents.\n */\nexport function stripToSpec(chunk: StreamChunk): StreamChunk {\n // Only strip the deprecated nested error object from RUN_ERROR\n if (\n (chunk as StreamChunk & { type: string }).type === 'RUN_ERROR' &&\n 'error' in chunk\n ) {\n const { error: _deprecated, ...rest } = chunk as Record<string, unknown>\n return rest as StreamChunk\n }\n return chunk\n}\n\n/**\n * Middleware that ensures events are AG-UI spec compliant.\n * Currently only strips the deprecated nested `error` object from RUN_ERROR.\n * All other fields pass through unchanged (passthrough allowed by spec).\n */\nexport function stripToSpecMiddleware(): ChatMiddleware {\n return {\n name: 'strip-to-spec',\n onChunk(_ctx, chunk) {\n return stripToSpec(chunk)\n },\n }\n}\n"],"names":[],"mappings":"AAYO,SAAS,YAAY,OAAiC;AAE3D,MACG,MAAyC,SAAS,eACnD,WAAW,OACX;AACA,UAAM,EAAE,OAAO,aAAa,GAAG,SAAS;AACxC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,wBAAwC;AACtD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,MAAM,OAAO;AACnB,aAAO,YAAY,KAAK;AAAA,IAC1B;AAAA,EAAA;AAEJ;"}
|