@ulearning/ai-sdk-adapter 0.0.0-stage → 1.0.0-next.0
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/LICENSE +21 -0
- package/README.md +29 -2
- package/dist/adapters/aiSDKFormatAdapter.d.ts +6 -0
- package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
- package/dist/adapters/aiSDKFormatAdapter.js +17 -0
- package/dist/adapters/aiSDKFormatAdapter.js.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts +3 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.js +36 -0
- package/dist/adapters/vercelAttachmentAdapter.js.map +1 -0
- package/dist/aiSDKExtras.d.ts +13 -0
- package/dist/aiSDKExtras.d.ts.map +1 -0
- package/dist/aiSDKExtras.js +6 -0
- package/dist/aiSDKExtras.js.map +1 -0
- package/dist/converters/convertMessage.d.ts +44 -0
- package/dist/converters/convertMessage.d.ts.map +1 -0
- package/dist/converters/convertMessage.js +465 -0
- package/dist/converters/convertMessage.js.map +1 -0
- package/dist/converters/toCreateMessage.d.ts +4 -0
- package/dist/converters/toCreateMessage.d.ts.map +1 -0
- package/dist/converters/toCreateMessage.js +65 -0
- package/dist/converters/toCreateMessage.js.map +1 -0
- package/dist/converters/toolApprovalAnswers.d.ts +3 -0
- package/dist/converters/toolApprovalAnswers.d.ts.map +1 -0
- package/dist/converters/toolApprovalAnswers.js +16 -0
- package/dist/converters/toolApprovalAnswers.js.map +1 -0
- package/dist/hooks.d.ts +17 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +19 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/model-context/injectInteractableContext.d.ts +37 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -0
- package/dist/model-context/injectInteractableContext.js +54 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -0
- package/dist/model-context/injectQuoteContext.d.ts +25 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -0
- package/dist/model-context/injectQuoteContext.js +51 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -0
- package/dist/runtime/AISDKChat.d.ts +13 -0
- package/dist/runtime/AISDKChat.d.ts.map +1 -0
- package/dist/runtime/AISDKChat.js +34 -0
- package/dist/runtime/AISDKChat.js.map +1 -0
- package/dist/runtime/AISDKThreads.d.ts +24 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -0
- package/dist/runtime/AISDKThreads.js +92 -0
- package/dist/runtime/AISDKThreads.js.map +1 -0
- package/dist/runtime/DynamicChatTransport.d.ts +50 -0
- package/dist/runtime/DynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/DynamicChatTransport.js +153 -0
- package/dist/runtime/DynamicChatTransport.js.map +1 -0
- package/dist/runtime/getResumableAdapter.d.ts +5 -0
- package/dist/runtime/getResumableAdapter.d.ts.map +1 -0
- package/dist/runtime/getResumableAdapter.js +11 -0
- package/dist/runtime/getResumableAdapter.js.map +1 -0
- package/dist/runtime/toolHistoryCodec.d.ts +20 -0
- package/dist/runtime/toolHistoryCodec.d.ts.map +1 -0
- package/dist/runtime/toolHistoryCodec.js +122 -0
- package/dist/runtime/toolHistoryCodec.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +95 -0
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -0
- package/dist/runtime/useAISDKRuntime.js +754 -0
- package/dist/runtime/useAISDKRuntime.js.map +1 -0
- package/dist/runtime/useChatRuntime.d.ts +17 -0
- package/dist/runtime/useChatRuntime.d.ts.map +1 -0
- package/dist/runtime/useChatRuntime.js +50 -0
- package/dist/runtime/useChatRuntime.js.map +1 -0
- package/dist/runtime/useChatSnapshot.d.ts +6 -0
- package/dist/runtime/useChatSnapshot.d.ts.map +1 -0
- package/dist/runtime/useChatSnapshot.js +62 -0
- package/dist/runtime/useChatSnapshot.js.map +1 -0
- package/dist/runtime/useChatThread.d.ts +121 -0
- package/dist/runtime/useChatThread.d.ts.map +1 -0
- package/dist/runtime/useChatThread.js +255 -0
- package/dist/runtime/useChatThread.js.map +1 -0
- package/dist/runtime/useDynamicChatTransport.d.ts +4 -0
- package/dist/runtime/useDynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/useDynamicChatTransport.js +26 -0
- package/dist/runtime/useDynamicChatTransport.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts +12 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -0
- package/dist/runtime/useExternalHistory.js +556 -0
- package/dist/runtime/useExternalHistory.js.map +1 -0
- package/dist/runtime/useMessageQueue.d.ts +14 -0
- package/dist/runtime/useMessageQueue.d.ts.map +1 -0
- package/dist/runtime/useMessageQueue.js +142 -0
- package/dist/runtime/useMessageQueue.js.map +1 -0
- package/dist/runtime/useResourceCleanup.d.ts +2 -0
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -0
- package/dist/runtime/useResourceCleanup.js +47 -0
- package/dist/runtime/useResourceCleanup.js.map +1 -0
- package/dist/runtime/useStreamingTiming.d.ts +12 -0
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -0
- package/dist/runtime/useStreamingTiming.js +36 -0
- package/dist/runtime/useStreamingTiming.js.map +1 -0
- package/dist/tools/generativeTools.d.ts +71 -0
- package/dist/tools/generativeTools.d.ts.map +1 -0
- package/dist/tools/generativeTools.js +258 -0
- package/dist/tools/generativeTools.js.map +1 -0
- package/dist/tools/mcp-stdio.node.d.ts +2 -0
- package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.node.js +1 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts +3 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.unsupported.js +10 -0
- package/dist/tools/mcp-stdio.unsupported.js.map +1 -0
- package/dist/transport/AssistantChatTransport.d.ts +20 -0
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -0
- package/dist/transport/AssistantChatTransport.js +151 -0
- package/dist/transport/AssistantChatTransport.js.map +1 -0
- package/dist/transport/resumable.d.ts +36 -0
- package/dist/transport/resumable.d.ts.map +1 -0
- package/dist/transport/resumable.js +112 -0
- package/dist/transport/resumable.js.map +1 -0
- package/dist/usage.d.ts +24 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +121 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/getVercelAIMessages.d.ts +4 -0
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -0
- package/dist/utils/getVercelAIMessages.js +8 -0
- package/dist/utils/getVercelAIMessages.js.map +1 -0
- package/dist/utils/sliceMessagesUntil.d.ts +3 -0
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -0
- package/dist/utils/sliceMessagesUntil.js +10 -0
- package/dist/utils/sliceMessagesUntil.js.map +1 -0
- package/package.json +96 -3
- package/src/__tests__/standalone.e2e.ts +118 -0
- package/src/adapters/aiSDKFormatAdapter.test.ts +36 -0
- package/src/adapters/aiSDKFormatAdapter.ts +18 -0
- package/src/adapters/vercelAttachmentAdapter.test.ts +52 -0
- package/src/adapters/vercelAttachmentAdapter.ts +36 -0
- package/src/aiSDKExtras.ts +31 -0
- package/src/converters/convertMessage.metadata-key.test.tsx +182 -0
- package/src/converters/convertMessage.test.ts +2428 -0
- package/src/converters/convertMessage.tool-args-status.test.tsx +185 -0
- package/src/converters/convertMessage.ts +919 -0
- package/src/converters/toCreateMessage.test.ts +716 -0
- package/src/converters/toCreateMessage.ts +110 -0
- package/src/converters/toolApprovalAnswers.ts +27 -0
- package/src/hooks.test.ts +168 -0
- package/src/hooks.ts +26 -0
- package/src/index.ts +29 -0
- package/src/model-context/injectInteractableContext.test.ts +167 -0
- package/src/model-context/injectInteractableContext.ts +64 -0
- package/src/model-context/injectQuoteContext.test.ts +33 -0
- package/src/model-context/injectQuoteContext.ts +63 -0
- package/src/runtime/AISDKChat.artifacts.test.ts +48 -0
- package/src/runtime/AISDKChat.integration.test.tsx +148 -0
- package/src/runtime/AISDKChat.test.ts +241 -0
- package/src/runtime/AISDKChat.ts +43 -0
- package/src/runtime/AISDKThreads.test.ts +811 -0
- package/src/runtime/AISDKThreads.ts +194 -0
- package/src/runtime/DynamicChatTransport.test.ts +228 -0
- package/src/runtime/DynamicChatTransport.ts +281 -0
- package/src/runtime/__tests__/controlled-transport.ts +70 -0
- package/src/runtime/__tests__/tap.ts +80 -0
- package/src/runtime/__tests__/vue.ts +46 -0
- package/src/runtime/getResumableAdapter.ts +16 -0
- package/src/runtime/toolHistoryCodec.test.ts +187 -0
- package/src/runtime/toolHistoryCodec.ts +245 -0
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1956 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +580 -0
- package/src/runtime/useAISDKRuntime.denied-tool.test.tsx +122 -0
- package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +302 -0
- package/src/runtime/useAISDKRuntime.metadata-cache.test.tsx +157 -0
- package/src/runtime/useAISDKRuntime.queue.test.tsx +707 -0
- package/src/runtime/useAISDKRuntime.reload.test.tsx +217 -0
- package/src/runtime/useAISDKRuntime.test.ts +2345 -0
- package/src/runtime/useAISDKRuntime.tool-output.test.tsx +113 -0
- package/src/runtime/useAISDKRuntime.ts +1503 -0
- package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
- package/src/runtime/useChatOnTap.test.ts +60 -0
- package/src/runtime/useChatRuntime.fast-refresh.test.tsx +83 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +661 -0
- package/src/runtime/useChatRuntime.local-storage.test.tsx +114 -0
- package/src/runtime/useChatRuntime.native.test.ts +147 -0
- package/src/runtime/useChatRuntime.run-telemetry.test.tsx +226 -0
- package/src/runtime/useChatRuntime.test.ts +619 -0
- package/src/runtime/useChatRuntime.ts +80 -0
- package/src/runtime/useChatSnapshot.test.ts +60 -0
- package/src/runtime/useChatSnapshot.ts +86 -0
- package/src/runtime/useChatThread.binding.test.tsx +140 -0
- package/src/runtime/useChatThread.test.ts +993 -0
- package/src/runtime/useChatThread.transport.test.tsx +149 -0
- package/src/runtime/useChatThread.ts +533 -0
- package/src/runtime/useDynamicChatTransport.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +4187 -0
- package/src/runtime/useExternalHistory.ts +902 -0
- package/src/runtime/useMessageQueue.effects.test.tsx +88 -0
- package/src/runtime/useMessageQueue.test.tsx +256 -0
- package/src/runtime/useMessageQueue.ts +189 -0
- package/src/runtime/useResourceCleanup.ts +58 -0
- package/src/runtime/useStreamingTiming.test.tsx +96 -0
- package/src/runtime/useStreamingTiming.ts +69 -0
- package/src/tools/generativeTools.test.ts +1058 -0
- package/src/tools/generativeTools.ts +491 -0
- package/src/tools/mcp-stdio.node.ts +1 -0
- package/src/tools/mcp-stdio.unsupported.ts +12 -0
- package/src/transport/AssistantChatTransport.test.ts +631 -0
- package/src/transport/AssistantChatTransport.ts +236 -0
- package/src/transport/resumable.test.ts +262 -0
- package/src/transport/resumable.ts +162 -0
- package/src/usage.test.ts +204 -0
- package/src/usage.ts +179 -0
- package/src/utils/getVercelAIMessages.ts +11 -0
- package/src/utils/sliceMessagesUntil.test.ts +51 -0
- package/src/utils/sliceMessagesUntil.ts +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generativeTools.js","names":[],"sources":["../../src/tools/generativeTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { MCPClient, MCPClientConfig } from \"@ai-sdk/mcp\";\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport { Experimental_StdioMCPTransport } from \"#mcp-stdio\";\nimport {\n toJSONSchema,\n type Tool,\n type McpServerConfig,\n type ToolModelOutputFunction,\n} from \"@ulearning/ai-stream\";\nimport type {\n McpToolkitToolConfig,\n Toolkit,\n ToolkitDefinition,\n} from \"@ulearning/ai-core/vue\";\nimport { frontendTools, type FrontendTools } from \"@ulearning/ai-stream/ai-sdk\";\nimport {\n toAISDKContent,\n toAISDKDefaultOutput,\n unwrapModelContentEnvelope,\n type ModelContentEnvelope,\n} from \"@ulearning/ai-stream/internal\";\n\nconst EMPTY_SCHEMA = { type: \"object\" as const, properties: {} };\n\nconst humanNotSupported = (): never => {\n throw new Error(\n \"`human()` is not available during server-side tool execution.\",\n );\n};\n\n// AI SDK leaves `abortSignal` optional; assistant-ui's execute requires one.\nconst neverAbort = new AbortController().signal;\n\ntype MCPConnectionTimeoutPhase = \"connecting\" | \"listing tools\";\n\nclass MCPConnectionTimeoutError extends Error {}\n\nconst createMcpConnectionTimeoutError = (\n name: string,\n phase: MCPConnectionTimeoutPhase,\n timeoutMs: number,\n) =>\n new MCPConnectionTimeoutError(\n `MCP toolkit entry \"${name}\" timed out while ${phase} after ${timeoutMs}ms.`,\n );\n\nconst withMcpConnectionTimeout = async <T>(\n promise: Promise<T>,\n options: {\n name: string;\n config: McpServerConfig;\n phase: MCPConnectionTimeoutPhase;\n startedAt: number;\n },\n): Promise<T> => {\n const timeoutMs = options.config.connectionTimeout;\n if (timeoutMs === undefined) return await promise;\n const remainingMs = timeoutMs - (Date.now() - options.startedAt);\n const timeoutError = () =>\n createMcpConnectionTimeoutError(options.name, options.phase, timeoutMs);\n if (remainingMs <= 0) throw timeoutError();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(timeoutError()), remainingMs);\n }),\n ]);\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n};\n\n// Converted schemas are shared by parameter identity and treated as immutable.\nconst convertedParameterSchemas = new WeakMap<\n object,\n ReturnType<typeof toJSONSchema>\n>();\n\nconst getOrConvertParameterSchema = (\n parameters: NonNullable<Tool[\"parameters\"]>,\n) => {\n const cached = convertedParameterSchemas.get(parameters);\n if (cached) return cached;\n\n const converted = toJSONSchema(parameters);\n convertedParameterSchemas.set(parameters, converted);\n return converted;\n};\n\nconst parametersToInputSchema = (\n parameters: Tool[\"parameters\"] | undefined,\n) => {\n if (!parameters) return jsonSchema(EMPTY_SCHEMA);\n return jsonSchema(getOrConvertParameterSchema(parameters));\n};\n\n/**\n * @deprecated Options for the deprecated {@link generativeTools}. Use\n * {@link AISDKToolkit} with {@link AISDKToolkitOptions} /\n * {@link AISDKToolkitToolsOptions} instead.\n */\nexport interface GenerativeToolsOptions {\n /**\n * The server build of a generative toolkit (schema + server `execute`). Typed\n * as the canonical {@link Toolkit} so callers don't need to cast; the server\n * build carries `execute`, recovered internally as {@link ToolkitDefinition}.\n */\n toolkit: Toolkit;\n /**\n * Tools uploaded by the frontend (the request body's `tools`). Merged in\n * alongside the `toolkit`; a server `execute` from `toolkit` takes precedence\n * over an uploaded entry of the same name.\n */\n frontendTools?: FrontendTools;\n}\n\nexport type AISDKToolkitOptions = {\n toolkit: Toolkit;\n};\n\nexport type AISDKToolkitToolsOptions = {\n /**\n * Tools uploaded by the frontend request body.\n */\n frontend?: FrontendTools;\n};\n\n/**\n * Builds an AI SDK `ToolSet` for server-side use with `streamText` /\n * `generateText` from a generative `toolkit` and the frontend-uploaded tools.\n *\n * Each toolkit tool's `execute` runs on the server. Pair this with the\n * `\"use generative\"` compiler: import the toolkit in a server route (where it\n * resolves to the server build — schema + `execute`, with `render` stripped) and\n * pass it here. Tools without an `execute` are still exposed to the model but\n * left for the client to fulfill. `frontendTools` lets the client contribute\n * tools that aren't in the static toolkit.\n *\n * @deprecated Use {@link AISDKToolkit} instead:\n * `new AISDKToolkit({ toolkit }).tools({ frontend })`. It is a strict superset\n * (it also opens MCP server connections), so it replaces `generativeTools`\n * everywhere. The `frontendTools` option is named `frontend` on `.tools()`, and\n * `.tools()` is async. `generativeTools` will be removed in a future version.\n *\n * @example\n * ```ts\n * // Define once at module scope so any MCP connections pool across requests.\n * const aiToolkit = new AISDKToolkit({ toolkit: docsToolkit });\n *\n * // In your route handler:\n * const { tools } = await req.json();\n * streamText({\n * abortSignal: req.signal,\n * model,\n * messages,\n * tools: await aiToolkit.tools({ frontend: tools }),\n * });\n * ```\n */\nexport const generativeTools = (options: GenerativeToolsOptions): ToolSet => {\n assertNoMcpToolkitTools(options.toolkit);\n return {\n ...(options.frontendTools ? frontendTools(options.frontendTools) : {}),\n // `toolkit` last so its server-side `execute` wins over an uploaded entry of\n // the same name. The cast recovers the declaration shape — the server build\n // carries `execute`, which the canonical `Toolkit` type erases.\n ...toProviderToolSet(options.toolkit),\n ...toServerToolSet(options.toolkit as ToolkitDefinition),\n };\n};\n\nexport class AISDKToolkit {\n readonly #toolkit: Toolkit;\n readonly #mcpClients = new Map<string, Promise<MCPClient>>();\n\n constructor(options: AISDKToolkitOptions) {\n this.#toolkit = options.toolkit;\n }\n\n async tools(options: AISDKToolkitToolsOptions = {}): Promise<ToolSet> {\n const frontendToolSet = options.frontend\n ? frontendTools(options.frontend)\n : {};\n const mcpToolSet = await this.#mcpTools();\n const providerToolSet = toProviderToolSet(this.#toolkit);\n const serverToolSet = toServerToolSet(this.#toolkit as ToolkitDefinition);\n\n assertNoMcpToolNameCollisions(mcpToolSet, [\n { source: \"frontend\", tools: frontendToolSet },\n { source: \"provider\", tools: providerToolSet },\n { source: \"toolkit\", tools: serverToolSet },\n ]);\n\n return {\n ...frontendToolSet,\n ...mcpToolSet.tools,\n ...providerToolSet,\n ...serverToolSet,\n };\n }\n\n async close(): Promise<void> {\n const clientEntries = [...this.#mcpClients.entries()];\n const clientNames = clientEntries.map(([name]) => name);\n this.#mcpClients.clear();\n const clientResults = await Promise.allSettled(\n clientEntries.map(([, clientPromise]) => clientPromise),\n );\n const clients = clientResults.flatMap((result, index) =>\n result.status === \"fulfilled\"\n ? [[clientNames[index]!, result.value] as const]\n : [],\n );\n const closeResults = await Promise.allSettled(\n clients.map(([, client]) => client.close()),\n );\n const errors = [\n ...clientResults.flatMap((result, index) =>\n result.status === \"rejected\"\n ? [toMcpToolkitError(clientNames[index]!, \"connect\", result.reason)]\n : [],\n ),\n ...closeResults.flatMap((result, index) =>\n result.status === \"rejected\"\n ? [toMcpToolkitError(clients[index]![0], \"close\", result.reason)]\n : [],\n ),\n ];\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) {\n throw new AggregateError(\n errors,\n \"Failed to close one or more MCP clients\",\n );\n }\n }\n\n async #mcpTools(): Promise<McpToolSet> {\n const toolSets = await Promise.all(\n Object.entries(this.#toolkit)\n .filter((entry): entry is [string, McpToolkitTool] =>\n isMcpToolkitTool(entry[1]),\n )\n .map(async ([name, tool]) => {\n const startedAt = Date.now();\n const clientPromise = this.#mcpClient(name, tool.server, startedAt);\n const client = await clientPromise.catch((error: unknown) => {\n if (error instanceof MCPConnectionTimeoutError) throw error;\n throw toMcpToolkitError(name, \"connect\", error);\n });\n try {\n const tools = await withMcpConnectionTimeout(client.tools(), {\n name,\n config: tool.server,\n phase: \"listing tools\",\n startedAt,\n });\n return [name, tool, tools] as const;\n } catch (error) {\n if (error instanceof MCPConnectionTimeoutError) {\n if (this.#mcpClients.get(name) === clientPromise) {\n this.#mcpClients.delete(name);\n void client.close().catch(() => {});\n }\n throw error;\n }\n throw toMcpToolkitError(name, \"list tools\", error);\n }\n }),\n );\n\n const tools = Object.create(null) as ToolSet;\n const toolSources = new Map<string, string>();\n for (const [serverName, mcpTool, toolSet] of toolSets) {\n for (const [toolName, tool] of Object.entries(toolSet)) {\n if (isDisabledMcpTool(mcpTool.tools?.[toolName])) continue;\n const exposedName = `${mcpTool.prefix ?? \"\"}${toolName}`;\n const existingServerName = toolSources.get(exposedName);\n if (existingServerName) {\n throw new Error(\n `MCP tool name collision: \"${exposedName}\" is exposed by both \"${existingServerName}\" and \"${serverName}\". Rename one of the toolkit entries or expose distinct MCP tool names.`,\n );\n }\n toolSources.set(exposedName, serverName);\n tools[exposedName] = tool as ToolSet[string];\n }\n }\n return { tools, sources: toolSources };\n }\n\n #mcpClient(\n name: string,\n config: McpServerConfig,\n startedAt: number,\n ): Promise<MCPClient> {\n const existing = this.#mcpClients.get(name);\n if (existing) return existing;\n const createPromise = createMCPClient(\n toMCPClientConfig(config),\n ) as Promise<MCPClient>;\n let next: Promise<MCPClient>;\n next = withMcpConnectionTimeout(createPromise, {\n name,\n config,\n phase: \"connecting\",\n startedAt,\n }).catch((error) => {\n if (this.#mcpClients.get(name) === next) {\n this.#mcpClients.delete(name);\n }\n if (error instanceof MCPConnectionTimeoutError) {\n void createPromise.then((client) => client.close()).catch(() => {});\n }\n throw error;\n });\n this.#mcpClients.set(name, next);\n return next;\n }\n}\n\nconst toMCPClientConfig = (config: McpServerConfig): MCPClientConfig => {\n if (config.type === \"stdio\") {\n return {\n transport: new Experimental_StdioMCPTransport({\n command: config.command,\n ...(config.args && { args: [...config.args] }),\n ...(config.env && { env: config.env }),\n ...(config.cwd && { cwd: config.cwd }),\n }),\n };\n }\n\n return {\n transport: {\n type: config.type,\n url: config.url,\n ...(config.headers && { headers: config.headers }),\n ...(config.redirect && { redirect: config.redirect }),\n },\n };\n};\n\ntype ToolkitTool = Toolkit[string];\n\ntype McpToolkitTool = ToolkitTool & {\n type: \"mcp\";\n server: McpServerConfig;\n prefix?: string | undefined;\n tools?: Record<string, McpToolkitToolConfig> | undefined;\n};\n\ntype McpToolSet = {\n tools: ToolSet;\n sources: Map<string, string>;\n};\n\nconst assertNoMcpToolNameCollisions = (\n mcp: McpToolSet,\n toolSets: readonly { source: string; tools: ToolSet }[],\n): void => {\n for (const [toolName, serverName] of mcp.sources) {\n for (const { source, tools } of toolSets) {\n if (!Object.prototype.hasOwnProperty.call(tools, toolName)) continue;\n throw new Error(\n `MCP tool \"${toolName}\" from \"${serverName}\" conflicts with ${source} tool \"${toolName}\". Rename one of the tools so each model-visible tool name is unique.`,\n );\n }\n }\n};\n\nconst isMcpToolkitTool = (tool: ToolkitTool): tool is McpToolkitTool =>\n tool.type === \"mcp\" && !tool.disabled;\n\nconst getErrorMessage = (error: unknown): string =>\n error instanceof Error ? error.message || error.name : String(error);\n\nconst toMcpToolkitError = (\n entryName: string,\n action: \"connect\" | \"list tools\" | \"close\",\n error: unknown,\n): Error => {\n return new Error(\n `MCP toolkit entry \"${entryName}\" failed to ${action}: ${getErrorMessage(error)}`,\n { cause: error },\n );\n};\n\nconst isDisabledMcpTool = (config: McpToolkitToolConfig | undefined): boolean =>\n config?.disabled === true;\n\nconst assertNoMcpToolkitTools = (toolkit: Toolkit): void => {\n const mcpToolName = Object.entries(toolkit).find(([, tool]) =>\n isMcpToolkitTool(tool),\n )?.[0];\n if (!mcpToolName) return;\n\n throw new Error(\n `MCP toolkit entry \"${mcpToolName}\" requires AISDKToolkit. Use new AISDKToolkit({ toolkit }).tools(...) instead of generativeTools(...).`,\n );\n};\n\ntype AISDKToModelOutputOptions<TArgs, TResult> = Omit<\n Parameters<ToolModelOutputFunction<TArgs, TResult>>[0],\n \"output\"\n> & {\n output: TResult | ModelContentEnvelope<TResult>;\n};\n\nconst toAISDKToModelOutput =\n <TArgs, TResult>(toModelOutput?: ToolModelOutputFunction<TArgs, TResult>) =>\n async (options: AISDKToModelOutputOptions<TArgs, TResult>) => {\n const { result, modelContent } = unwrapModelContentEnvelope(options.output);\n\n if (modelContent !== undefined) {\n return toAISDKContent(modelContent, { taggedFileData: true });\n }\n\n if (!toModelOutput) {\n return toAISDKDefaultOutput(result);\n }\n\n const parts = await toModelOutput({\n ...options,\n output: result,\n });\n return toAISDKContent(parts, { taggedFileData: true });\n };\n\nconst toServerToolSet = (toolkit: ToolkitDefinition): ToolSet =>\n Object.fromEntries(\n Object.entries(toolkit)\n .filter(\n ([, t]) => t.type !== \"mcp\" && t.type !== \"provider\" && !t.disabled,\n )\n .map(([name, t]) => {\n const execute = t.execute;\n return [\n name,\n {\n ...(t.description !== undefined && { description: t.description }),\n inputSchema: parametersToInputSchema(t.parameters),\n toModelOutput: toAISDKToModelOutput(t.toModelOutput),\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n ...(execute && {\n execute: (\n args: unknown,\n callOptions: { toolCallId: string; abortSignal?: AbortSignal },\n ) =>\n execute(args as never, {\n toolCallId: callOptions.toolCallId,\n abortSignal: callOptions.abortSignal ?? neverAbort,\n human: humanNotSupported,\n }),\n }),\n },\n ];\n }),\n ) as ToolSet;\n\nconst toProviderToolSet = (toolkit: Toolkit): ToolSet =>\n Object.fromEntries(\n Object.entries(toolkit)\n .filter((entry): entry is [string, ProviderToolkitTool] =>\n isProviderToolkitTool(entry[1]),\n )\n .map(([name, t]) => [\n name,\n {\n type: \"provider\",\n id: t.providerId,\n args: t.args,\n ...(t.parameters && {\n inputSchema: parametersToInputSchema(t.parameters),\n }),\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n ...(t.supportsDeferredResults !== undefined && {\n supportsDeferredResults: t.supportsDeferredResults,\n }),\n },\n ]),\n ) as ToolSet;\n\ntype ProviderToolkitTool = Extract<Toolkit[string], { type: \"provider\" }>;\n\nconst isProviderToolkitTool = (\n tool: Toolkit[string],\n): tool is ProviderToolkitTool => tool.type === \"provider\" && !tool.disabled;\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-stdio.node.d.ts","mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,uBAAuB","names":[],"ignoreList":[],"sources":["../../src/tools/mcp-stdio.node.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-stdio.unsupported.d.ts","mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,IAAI,qBAAqB,EAAE,MAAM,uBAAuB;AAUpG,eAAO,MAAM,8BAA8B,EACE,OAAO,qBAAqB","names":[],"ignoreList":[],"sources":["../../src/tools/mcp-stdio.unsupported.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/tools/mcp-stdio.unsupported.ts
|
|
2
|
+
var UnsupportedStdioMCPTransport = class {
|
|
3
|
+
constructor() {
|
|
4
|
+
throw new Error("stdio MCP transport requires a runtime that can spawn a subprocess, such as Node, Bun, or Deno (with --allow-run). Use an HTTP or SSE MCP server config in browser, React Native, edge, or worker runtimes.");
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
const Experimental_StdioMCPTransport = UnsupportedStdioMCPTransport;
|
|
8
|
+
export { Experimental_StdioMCPTransport };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=mcp-stdio.unsupported.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-stdio.unsupported.js","names":[],"sources":["../../src/tools/mcp-stdio.unsupported.ts"],"sourcesContent":["import type { Experimental_StdioMCPTransport as NodeStdioMCPTransport } from \"@ai-sdk/mcp/mcp-stdio\";\n\nclass UnsupportedStdioMCPTransport {\n constructor() {\n throw new Error(\n \"stdio MCP transport requires a runtime that can spawn a subprocess, such as Node, Bun, or Deno (with --allow-run). Use an HTTP or SSE MCP server config in browser, React Native, edge, or worker runtimes.\",\n );\n }\n}\n\nexport const Experimental_StdioMCPTransport =\n UnsupportedStdioMCPTransport as unknown as typeof NodeStdioMCPTransport;\n"],"mappings":";AAEA,IAAM,+BAAN,MAAmC;CACjC,cAAc;EACZ,MAAM,IAAI,MACR,6MACF;CACF;AACF;AAEA,MAAa,iCACX"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AssistantRuntime, ThreadListItemRuntime } from "@ulearning/ai-core";
|
|
2
|
+
import { DefaultChatTransport, type HttpChatTransportInitOptions, type UIMessage } from "ai";
|
|
3
|
+
import { type AssistantChatResumableOptions } from "./resumable.js";
|
|
4
|
+
export type InitializableThreadListItem = Pick<ThreadListItemRuntime, "initialize">;
|
|
5
|
+
export type AssistantChatTransportInitOptions<UI_MESSAGE extends UIMessage> = HttpChatTransportInitOptions<UI_MESSAGE> & {
|
|
6
|
+
resumable?: AssistantChatResumableOptions;
|
|
7
|
+
};
|
|
8
|
+
export declare class AssistantChatTransport<UI_MESSAGE extends UIMessage> extends DefaultChatTransport<UI_MESSAGE> {
|
|
9
|
+
private runtime;
|
|
10
|
+
private getThreadListItem;
|
|
11
|
+
private readonly resumable;
|
|
12
|
+
private readonly __internal_initOptions;
|
|
13
|
+
/** Constructs an unwired copy with the same init options. */
|
|
14
|
+
__internal_clone(): AssistantChatTransport<UI_MESSAGE>;
|
|
15
|
+
constructor(initOptions?: AssistantChatTransportInitOptions<UI_MESSAGE>);
|
|
16
|
+
setRuntime(runtime: AssistantRuntime): void;
|
|
17
|
+
getResumableAdapter(): AssistantChatResumableOptions | undefined;
|
|
18
|
+
__internal_setGetThreadListItem(getter: () => InitializableThreadListItem | undefined): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=AssistantChatTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantChatTransport.d.ts","mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,oBAAoB;AAC3B,OAAO,EACL,oBAAoB,EACpB,KAAK,4BAA4B,EACjC,KAAK,SAAS,EACf,MAAM,IAAI;AAGX,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,gBAAa;AAEpB,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,qBAAqB,EACrB,YAAY,CACb;AAWD,MAAM,MAAM,iCAAiC,CAAC,UAAU,SAAS,SAAS,IACxE,4BAA4B,CAAC,UAAU,CAAC,GAAG;IACzC,SAAS,CAAC,EAAE,6BAA6B;CAC1C;AAEH,qBAAa,sBAAsB,CACjC,UAAU,SAAS,SAAS,CAC5B,SAAQ,oBAAoB,CAAC,UAAU,CAAC;IACxC,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,iBAAiB;IAGzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAE1B,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAIvC;IACO,gBAAgB,IAAI,sBAAsB,CAAC,UAAU,CAAC;gBAOjD,WAAW,CAAC,EAAE,iCAAiC,CAAC,UAAU,CAAC;IA0DvE,UAAU,CAAC,OAAO,EAAE,gBAAgB;IAIpC,mBAAmB,IAAI,6BAA6B,GAAG,SAAS;IAIhE,+BAA+B,CAC7B,MAAM,EAAE,MAAM,2BAA2B,GAAG,SAAS","names":[],"ignoreList":[],"sources":["../../src/transport/AssistantChatTransport.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { RESUMABLE_STREAM_ID_HEADER } from "./resumable.js";
|
|
2
|
+
import { DefaultChatTransport } from "ai";
|
|
3
|
+
import { SSEEventDecoder } from "@ulearning/ai-stream/utils";
|
|
4
|
+
import { toToolsJSONSchema } from "@ulearning/ai-stream";
|
|
5
|
+
// src/transport/AssistantChatTransport.ts
|
|
6
|
+
const FINISH_BUFFER_LIMIT = 4096;
|
|
7
|
+
const RESUMABLE_THREAD_ID_HEADER = "x-assistant-ui-resumable-thread-id";
|
|
8
|
+
const RESUMABLE_RECONNECT_ID_HEADER = "x-assistant-ui-resumable-reconnect-id";
|
|
9
|
+
const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
|
|
10
|
+
101,
|
|
11
|
+
204,
|
|
12
|
+
205,
|
|
13
|
+
304
|
|
14
|
+
]);
|
|
15
|
+
var AssistantChatTransport = class extends DefaultChatTransport {
|
|
16
|
+
runtime;
|
|
17
|
+
getThreadListItem;
|
|
18
|
+
resumable;
|
|
19
|
+
__internal_initOptions;
|
|
20
|
+
/** Constructs an unwired copy with the same init options. */
|
|
21
|
+
__internal_clone() {
|
|
22
|
+
const Constructor = this.constructor;
|
|
23
|
+
return new Constructor(this.__internal_initOptions);
|
|
24
|
+
}
|
|
25
|
+
constructor(initOptions) {
|
|
26
|
+
const { resumable, ...rest } = initOptions ?? {};
|
|
27
|
+
const userFetch = rest.fetch;
|
|
28
|
+
const userPrepareReconnect = rest.prepareReconnectToStreamRequest;
|
|
29
|
+
super({
|
|
30
|
+
...rest,
|
|
31
|
+
...resumable && {
|
|
32
|
+
fetch: wrapFetchWithResumable(resumable, userFetch),
|
|
33
|
+
prepareReconnectToStreamRequest: wrapPrepareReconnect(resumable, userPrepareReconnect)
|
|
34
|
+
},
|
|
35
|
+
prepareSendMessagesRequest: async (options) => {
|
|
36
|
+
const threadId = options.id;
|
|
37
|
+
const context = this.runtime?.thread.getModelContext();
|
|
38
|
+
const id = (await (this.getThreadListItem?.() ?? this.runtime?.threads.mainItem)?.initialize())?.remoteId ?? options.id;
|
|
39
|
+
const optionsEx = {
|
|
40
|
+
...options,
|
|
41
|
+
id,
|
|
42
|
+
body: {
|
|
43
|
+
callSettings: context?.callSettings,
|
|
44
|
+
system: context?.system,
|
|
45
|
+
config: context?.config,
|
|
46
|
+
tools: toToolsJSONSchema(context?.tools ?? {}),
|
|
47
|
+
...options?.body
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const preparedRequest = await rest.prepareSendMessagesRequest?.(optionsEx);
|
|
51
|
+
const headers = resumable ? new Headers(preparedRequest?.headers ?? options.headers) : void 0;
|
|
52
|
+
headers?.set(RESUMABLE_THREAD_ID_HEADER, threadId);
|
|
53
|
+
return {
|
|
54
|
+
...preparedRequest,
|
|
55
|
+
...headers && { headers },
|
|
56
|
+
body: preparedRequest?.body ?? {
|
|
57
|
+
...optionsEx.body,
|
|
58
|
+
id,
|
|
59
|
+
messages: options.messages,
|
|
60
|
+
trigger: options.trigger,
|
|
61
|
+
messageId: options.messageId,
|
|
62
|
+
metadata: options.requestMetadata
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this.resumable = resumable;
|
|
68
|
+
this.__internal_initOptions = initOptions;
|
|
69
|
+
}
|
|
70
|
+
setRuntime(runtime) {
|
|
71
|
+
this.runtime = runtime;
|
|
72
|
+
}
|
|
73
|
+
getResumableAdapter() {
|
|
74
|
+
return this.resumable;
|
|
75
|
+
}
|
|
76
|
+
__internal_setGetThreadListItem(getter) {
|
|
77
|
+
this.getThreadListItem = getter;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function wrapFetchWithResumable(resumable, userFetch) {
|
|
81
|
+
const baseFetch = userFetch ? (input, init) => userFetch(input, init) : globalThis.fetch.bind(globalThis);
|
|
82
|
+
return async (input, init) => {
|
|
83
|
+
const headers = new Headers(init?.headers);
|
|
84
|
+
const threadId = headers.get(RESUMABLE_THREAD_ID_HEADER) ?? void 0;
|
|
85
|
+
const reconnectingStreamId = headers.get(RESUMABLE_RECONNECT_ID_HEADER);
|
|
86
|
+
const checkpointAtRequest = reconnectingStreamId ?? resumable.storage.getStreamId(threadId);
|
|
87
|
+
headers.delete(RESUMABLE_THREAD_ID_HEADER);
|
|
88
|
+
headers.delete(RESUMABLE_RECONNECT_ID_HEADER);
|
|
89
|
+
const res = await baseFetch(input, {
|
|
90
|
+
...init,
|
|
91
|
+
headers
|
|
92
|
+
});
|
|
93
|
+
const id = res.headers.get(RESUMABLE_STREAM_ID_HEADER);
|
|
94
|
+
const ownsCheckpoint = !reconnectingStreamId || resumable.storage.getStreamId(threadId) === reconnectingStreamId;
|
|
95
|
+
if (id && ownsCheckpoint) resumable.storage.setStreamId(id, threadId);
|
|
96
|
+
if ((res.status === 204 || res.status === 404) && reconnectingStreamId && resumable.storage.getStreamId(threadId) === reconnectingStreamId) resumable.storage.clear(threadId);
|
|
97
|
+
if (!res.body || NULL_BODY_STATUSES.has(res.status)) return res;
|
|
98
|
+
const detectFinish = resumable.isFinishEvent ?? createDefaultFinishDetector();
|
|
99
|
+
const decoder = resumable.isFinishEvent ? new TextDecoder() : void 0;
|
|
100
|
+
let accumulator = "";
|
|
101
|
+
const tap = new TransformStream({ transform(chunk, controller) {
|
|
102
|
+
controller.enqueue(chunk);
|
|
103
|
+
accumulator += decoder?.decode(chunk, { stream: true }) ?? "";
|
|
104
|
+
if (detectFinish(chunk, accumulator)) {
|
|
105
|
+
if (ownsCheckpoint && resumable.storage.getStreamId(threadId) === (id ?? checkpointAtRequest)) resumable.storage.clear(threadId);
|
|
106
|
+
accumulator = "";
|
|
107
|
+
} else if (accumulator.length > FINISH_BUFFER_LIMIT) accumulator = accumulator.slice(-1024);
|
|
108
|
+
} });
|
|
109
|
+
return new Response(res.body.pipeThrough(tap), {
|
|
110
|
+
status: res.status,
|
|
111
|
+
statusText: res.statusText,
|
|
112
|
+
headers: res.headers
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createDefaultFinishDetector() {
|
|
117
|
+
const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
118
|
+
const events = new SSEEventDecoder({
|
|
119
|
+
maxLineLength: Number.MAX_SAFE_INTEGER,
|
|
120
|
+
maxEventLength: Number.MAX_SAFE_INTEGER
|
|
121
|
+
});
|
|
122
|
+
return (chunk) => events.push(decoder.decode(chunk, { stream: true })).some(({ data }) => {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(data)?.type === "finish";
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function wrapPrepareReconnect(resumable, userPrepareReconnect) {
|
|
131
|
+
return async (options) => {
|
|
132
|
+
const streamId = resumable.storage.getStreamId(options.id);
|
|
133
|
+
if (!streamId) throw new Error("AssistantChatTransport: no resumable stream id available; nothing to resume");
|
|
134
|
+
const api = typeof resumable.resumeApi === "function" ? resumable.resumeApi(streamId) : resumable.resumeApi;
|
|
135
|
+
const userPrepared = await userPrepareReconnect?.({
|
|
136
|
+
...options,
|
|
137
|
+
api
|
|
138
|
+
});
|
|
139
|
+
const headers = new Headers(userPrepared?.headers ?? options.headers);
|
|
140
|
+
headers.set(RESUMABLE_THREAD_ID_HEADER, options.id);
|
|
141
|
+
headers.set(RESUMABLE_RECONNECT_ID_HEADER, streamId);
|
|
142
|
+
return {
|
|
143
|
+
...userPrepared,
|
|
144
|
+
headers,
|
|
145
|
+
api: userPrepared?.api ?? api
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export { AssistantChatTransport };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=AssistantChatTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantChatTransport.js","names":[],"sources":["../../src/transport/AssistantChatTransport.ts"],"sourcesContent":["import type {\n AssistantRuntime,\n ThreadListItemRuntime,\n} from \"@ulearning/ai-core\";\nimport {\n DefaultChatTransport,\n type HttpChatTransportInitOptions,\n type UIMessage,\n} from \"ai\";\nimport { toToolsJSONSchema } from \"@ulearning/ai-stream\";\nimport { SSEEventDecoder } from \"@ulearning/ai-stream/utils\";\nimport {\n RESUMABLE_STREAM_ID_HEADER,\n type AssistantChatResumableOptions,\n} from \"./resumable\";\n\nexport type InitializableThreadListItem = Pick<\n ThreadListItemRuntime,\n \"initialize\"\n>;\n\nconst FINISH_BUFFER_LIMIT = 4096;\nconst FINISH_BUFFER_TAIL = 1024;\nconst RESUMABLE_THREAD_ID_HEADER = \"x-assistant-ui-resumable-thread-id\";\nconst RESUMABLE_RECONNECT_ID_HEADER = \"x-assistant-ui-resumable-reconnect-id\";\n\n// 101/204/205/304 are null-body statuses per the fetch spec: `new Response(body, { status })`\n// throws for them, and WebKit returns a non-null empty body, so the body check alone does not guard it.\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\nexport type AssistantChatTransportInitOptions<UI_MESSAGE extends UIMessage> =\n HttpChatTransportInitOptions<UI_MESSAGE> & {\n resumable?: AssistantChatResumableOptions;\n };\n\nexport class AssistantChatTransport<\n UI_MESSAGE extends UIMessage,\n> extends DefaultChatTransport<UI_MESSAGE> {\n private runtime: AssistantRuntime | undefined;\n private getThreadListItem:\n | (() => InitializableThreadListItem | undefined)\n | undefined;\n private readonly resumable: AssistantChatResumableOptions | undefined;\n\n private readonly __internal_initOptions:\n | AssistantChatTransportInitOptions<UI_MESSAGE>\n | undefined;\n\n /** Constructs an unwired copy with the same init options. */\n public __internal_clone(): AssistantChatTransport<UI_MESSAGE> {\n const Constructor = this.constructor as new (\n initOptions?: AssistantChatTransportInitOptions<UI_MESSAGE>,\n ) => AssistantChatTransport<UI_MESSAGE>;\n return new Constructor(this.__internal_initOptions);\n }\n\n constructor(initOptions?: AssistantChatTransportInitOptions<UI_MESSAGE>) {\n const { resumable, ...rest } = initOptions ?? {};\n const userFetch = rest.fetch;\n const userPrepareReconnect = rest.prepareReconnectToStreamRequest;\n\n super({\n ...rest,\n ...(resumable && {\n fetch: wrapFetchWithResumable(resumable, userFetch),\n prepareReconnectToStreamRequest: wrapPrepareReconnect(\n resumable,\n userPrepareReconnect,\n ),\n }),\n prepareSendMessagesRequest: async (options) => {\n const threadId = options.id;\n const context = this.runtime?.thread.getModelContext();\n const threadListItem =\n this.getThreadListItem?.() ?? this.runtime?.threads.mainItem;\n const id = (await threadListItem?.initialize())?.remoteId ?? options.id;\n\n const optionsEx = {\n ...options,\n id,\n body: {\n callSettings: context?.callSettings,\n system: context?.system,\n config: context?.config,\n tools: toToolsJSONSchema(context?.tools ?? {}),\n ...options?.body,\n },\n };\n const preparedRequest =\n await rest.prepareSendMessagesRequest?.(optionsEx);\n const headers = resumable\n ? new Headers(preparedRequest?.headers ?? options.headers)\n : undefined;\n headers?.set(RESUMABLE_THREAD_ID_HEADER, threadId);\n\n return {\n ...preparedRequest,\n ...(headers && { headers }),\n body: preparedRequest?.body ?? {\n ...optionsEx.body,\n id,\n messages: options.messages,\n trigger: options.trigger,\n messageId: options.messageId,\n metadata: options.requestMetadata,\n },\n };\n },\n });\n\n this.resumable = resumable;\n this.__internal_initOptions = initOptions;\n }\n\n setRuntime(runtime: AssistantRuntime) {\n this.runtime = runtime;\n }\n\n getResumableAdapter(): AssistantChatResumableOptions | undefined {\n return this.resumable;\n }\n\n __internal_setGetThreadListItem(\n getter: () => InitializableThreadListItem | undefined,\n ) {\n this.getThreadListItem = getter;\n }\n}\n\nfunction wrapFetchWithResumable(\n resumable: AssistantChatResumableOptions,\n userFetch: HttpChatTransportInitOptions<UIMessage>[\"fetch\"],\n): NonNullable<HttpChatTransportInitOptions<UIMessage>[\"fetch\"]> {\n const baseFetch: typeof globalThis.fetch = userFetch\n ? (input, init) => userFetch(input as RequestInfo | URL, init)\n : globalThis.fetch.bind(globalThis);\n\n return async (input, init) => {\n const headers = new Headers(init?.headers);\n const threadId = headers.get(RESUMABLE_THREAD_ID_HEADER) ?? undefined;\n const reconnectingStreamId = headers.get(RESUMABLE_RECONNECT_ID_HEADER);\n const checkpointAtRequest =\n reconnectingStreamId ?? resumable.storage.getStreamId(threadId);\n headers.delete(RESUMABLE_THREAD_ID_HEADER);\n headers.delete(RESUMABLE_RECONNECT_ID_HEADER);\n const res = await baseFetch(input, { ...init, headers });\n const id = res.headers.get(RESUMABLE_STREAM_ID_HEADER);\n const ownsCheckpoint =\n !reconnectingStreamId ||\n resumable.storage.getStreamId(threadId) === reconnectingStreamId;\n if (id && ownsCheckpoint) resumable.storage.setStreamId(id, threadId);\n if (\n (res.status === 204 || res.status === 404) &&\n reconnectingStreamId &&\n resumable.storage.getStreamId(threadId) === reconnectingStreamId\n ) {\n resumable.storage.clear(threadId);\n }\n if (!res.body || NULL_BODY_STATUSES.has(res.status)) return res;\n\n const detectFinish =\n resumable.isFinishEvent ?? createDefaultFinishDetector();\n const decoder = resumable.isFinishEvent ? new TextDecoder() : undefined;\n let accumulator = \"\";\n const tap = new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n accumulator += decoder?.decode(chunk, { stream: true }) ?? \"\";\n if (detectFinish(chunk, accumulator)) {\n if (\n ownsCheckpoint &&\n resumable.storage.getStreamId(threadId) ===\n (id ?? checkpointAtRequest)\n ) {\n resumable.storage.clear(threadId);\n }\n accumulator = \"\";\n } else if (accumulator.length > FINISH_BUFFER_LIMIT) {\n accumulator = accumulator.slice(-FINISH_BUFFER_TAIL);\n }\n },\n });\n\n return new Response(res.body.pipeThrough(tap), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n };\n}\n\nfunction createDefaultFinishDetector() {\n const decoder = new TextDecoder(\"utf-8\", { ignoreBOM: true });\n // Observing completion must not impose a smaller event limit than the AI SDK.\n const events = new SSEEventDecoder({\n maxLineLength: Number.MAX_SAFE_INTEGER,\n maxEventLength: Number.MAX_SAFE_INTEGER,\n });\n return (chunk: Uint8Array) =>\n events.push(decoder.decode(chunk, { stream: true })).some(({ data }) => {\n try {\n return JSON.parse(data)?.type === \"finish\";\n } catch {\n return false;\n }\n });\n}\n\nfunction wrapPrepareReconnect(\n resumable: AssistantChatResumableOptions,\n userPrepareReconnect: HttpChatTransportInitOptions<UIMessage>[\"prepareReconnectToStreamRequest\"],\n): NonNullable<\n HttpChatTransportInitOptions<UIMessage>[\"prepareReconnectToStreamRequest\"]\n> {\n return async (options) => {\n const streamId = resumable.storage.getStreamId(options.id);\n if (!streamId) {\n throw new Error(\n \"AssistantChatTransport: no resumable stream id available; nothing to resume\",\n );\n }\n const api =\n typeof resumable.resumeApi === \"function\"\n ? resumable.resumeApi(streamId)\n : resumable.resumeApi;\n const userPrepared = await userPrepareReconnect?.({ ...options, api });\n const headers = new Headers(userPrepared?.headers ?? options.headers);\n headers.set(RESUMABLE_THREAD_ID_HEADER, options.id);\n headers.set(RESUMABLE_RECONNECT_ID_HEADER, streamId);\n return {\n ...userPrepared,\n headers,\n api: userPrepared?.api ?? api,\n };\n };\n}\n"],"mappings":";;;;;AAqBA,MAAM,sBAAsB;AAE5B,MAAM,6BAA6B;AACnC,MAAM,gCAAgC;AAItC,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;AAAG,CAAC;AAOvD,IAAa,yBAAb,cAEU,qBAAiC;CACzC;CACA;CAGA;CAEA;;CAKA,mBAA8D;EAC5D,MAAM,cAAc,KAAK;EAGzB,OAAO,IAAI,YAAY,KAAK,sBAAsB;CACpD;CAEA,YAAY,aAA6D;EACvE,MAAM,EAAE,WAAW,GAAG,SAAS,eAAe,CAAC;EAC/C,MAAM,YAAY,KAAK;EACvB,MAAM,uBAAuB,KAAK;EAElC,MAAM;GACJ,GAAG;GACH,GAAI,aAAa;IACf,OAAO,uBAAuB,WAAW,SAAS;IAClD,iCAAiC,qBAC/B,WACA,oBACF;GACF;GACA,4BAA4B,OAAO,YAAY;IAC7C,MAAM,WAAW,QAAQ;IACzB,MAAM,UAAU,KAAK,SAAS,OAAO,gBAAgB;IAGrD,MAAM,MAAM,OADV,KAAK,oBAAoB,KAAK,KAAK,SAAS,QAAQ,SAAA,EACpB,WAAW,EAAA,EAAI,YAAY,QAAQ;IAErE,MAAM,YAAY;KAChB,GAAG;KACH;KACA,MAAM;MACJ,cAAc,SAAS;MACvB,QAAQ,SAAS;MACjB,QAAQ,SAAS;MACjB,OAAO,kBAAkB,SAAS,SAAS,CAAC,CAAC;MAC7C,GAAG,SAAS;KACd;IACF;IACA,MAAM,kBACJ,MAAM,KAAK,6BAA6B,SAAS;IACnD,MAAM,UAAU,YACZ,IAAI,QAAQ,iBAAiB,WAAW,QAAQ,OAAO,IACvD,KAAA;IACJ,SAAS,IAAI,4BAA4B,QAAQ;IAEjD,OAAO;KACL,GAAG;KACH,GAAI,WAAW,EAAE,QAAQ;KACzB,MAAM,iBAAiB,QAAQ;MAC7B,GAAG,UAAU;MACb;MACA,UAAU,QAAQ;MAClB,SAAS,QAAQ;MACjB,WAAW,QAAQ;MACnB,UAAU,QAAQ;KACpB;IACF;GACF;EACF,CAAC;EAED,KAAK,YAAY;EACjB,KAAK,yBAAyB;CAChC;CAEA,WAAW,SAA2B;EACpC,KAAK,UAAU;CACjB;CAEA,sBAAiE;EAC/D,OAAO,KAAK;CACd;CAEA,gCACE,QACA;EACA,KAAK,oBAAoB;CAC3B;AACF;AAEA,SAAS,uBACP,WACA,WAC+D;CAC/D,MAAM,YAAqC,aACtC,OAAO,SAAS,UAAU,OAA4B,IAAI,IAC3D,WAAW,MAAM,KAAK,UAAU;CAEpC,OAAO,OAAO,OAAO,SAAS;EAC5B,MAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;EACzC,MAAM,WAAW,QAAQ,IAAI,0BAA0B,KAAK,KAAA;EAC5D,MAAM,uBAAuB,QAAQ,IAAI,6BAA6B;EACtE,MAAM,sBACJ,wBAAwB,UAAU,QAAQ,YAAY,QAAQ;EAChE,QAAQ,OAAO,0BAA0B;EACzC,QAAQ,OAAO,6BAA6B;EAC5C,MAAM,MAAM,MAAM,UAAU,OAAO;GAAE,GAAG;GAAM;EAAQ,CAAC;EACvD,MAAM,KAAK,IAAI,QAAQ,IAAI,0BAA0B;EACrD,MAAM,iBACJ,CAAC,wBACD,UAAU,QAAQ,YAAY,QAAQ,MAAM;EAC9C,IAAI,MAAM,gBAAgB,UAAU,QAAQ,YAAY,IAAI,QAAQ;EACpE,KACG,IAAI,WAAW,OAAO,IAAI,WAAW,QACtC,wBACA,UAAU,QAAQ,YAAY,QAAQ,MAAM,sBAE5C,UAAU,QAAQ,MAAM,QAAQ;EAElC,IAAI,CAAC,IAAI,QAAQ,mBAAmB,IAAI,IAAI,MAAM,GAAG,OAAO;EAE5D,MAAM,eACJ,UAAU,iBAAiB,4BAA4B;EACzD,MAAM,UAAU,UAAU,gBAAgB,IAAI,YAAY,IAAI,KAAA;EAC9D,IAAI,cAAc;EAClB,MAAM,MAAM,IAAI,gBAAwC,EACtD,UAAU,OAAO,YAAY;GAC3B,WAAW,QAAQ,KAAK;GACxB,eAAe,SAAS,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,KAAK;GAC3D,IAAI,aAAa,OAAO,WAAW,GAAG;IACpC,IACE,kBACA,UAAU,QAAQ,YAAY,QAAQ,OACnC,MAAM,sBAET,UAAU,QAAQ,MAAM,QAAQ;IAElC,cAAc;GAChB,OAAO,IAAI,YAAY,SAAS,qBAC9B,cAAc,YAAY,MAAM,KAAmB;EAEvD,EACF,CAAC;EAED,OAAO,IAAI,SAAS,IAAI,KAAK,YAAY,GAAG,GAAG;GAC7C,QAAQ,IAAI;GACZ,YAAY,IAAI;GAChB,SAAS,IAAI;EACf,CAAC;CACH;AACF;AAEA,SAAS,8BAA8B;CACrC,MAAM,UAAU,IAAI,YAAY,SAAS,EAAE,WAAW,KAAK,CAAC;CAE5D,MAAM,SAAS,IAAI,gBAAgB;EACjC,eAAe,OAAO;EACtB,gBAAgB,OAAO;CACzB,CAAC;CACD,QAAQ,UACN,OAAO,KAAK,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW;EACtE,IAAI;GACF,OAAO,KAAK,MAAM,IAAI,CAAC,EAAE,SAAS;EACpC,QAAQ;GACN,OAAO;EACT;CACF,CAAC;AACL;AAEA,SAAS,qBACP,WACA,sBAGA;CACA,OAAO,OAAO,YAAY;EACxB,MAAM,WAAW,UAAU,QAAQ,YAAY,QAAQ,EAAE;EACzD,IAAI,CAAC,UACH,MAAM,IAAI,MACR,6EACF;EAEF,MAAM,MACJ,OAAO,UAAU,cAAc,aAC3B,UAAU,UAAU,QAAQ,IAC5B,UAAU;EAChB,MAAM,eAAe,MAAM,uBAAuB;GAAE,GAAG;GAAS;EAAI,CAAC;EACrE,MAAM,UAAU,IAAI,QAAQ,cAAc,WAAW,QAAQ,OAAO;EACpE,QAAQ,IAAI,4BAA4B,QAAQ,EAAE;EAClD,QAAQ,IAAI,+BAA+B,QAAQ;EACnD,OAAO;GACL,GAAG;GACH;GACA,KAAK,cAAc,OAAO;EAC5B;CACF;AACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Response header used by the [Resumable Streams](/docs/guides/resumable-streams) server and client wiring. */
|
|
2
|
+
export declare const RESUMABLE_STREAM_ID_HEADER = "x-resumable-stream-id";
|
|
3
|
+
export type ResumableClientStorage = {
|
|
4
|
+
getStreamId(threadId?: string): string | null;
|
|
5
|
+
setStreamId(id: string, threadId?: string): void;
|
|
6
|
+
clear(threadId?: string): void;
|
|
7
|
+
/** Subscribes to stream id changes so automatic resume can react after mount. */
|
|
8
|
+
subscribe?(listener: () => void, threadId?: string): () => void;
|
|
9
|
+
};
|
|
10
|
+
/** `sessionStorage`-backed storage for the pending resumable stream id. See the [Resumable Streams](/docs/guides/resumable-streams) guide for end-to-end wiring. */
|
|
11
|
+
export declare function createResumableSessionStorage(options?: {
|
|
12
|
+
/**
|
|
13
|
+
* Storage key for the pending stream id. A static string namespaces per route
|
|
14
|
+
* or chat surface. A getter is read lazily on every access, so the key can be
|
|
15
|
+
* derived from the active thread's identity; while the getter returns
|
|
16
|
+
* `undefined`, reads report no pending stream and writes are dropped, so a
|
|
17
|
+
* thread whose identity is not known yet never touches another thread's key.
|
|
18
|
+
*
|
|
19
|
+
* Under a remote thread list with more than one thread, scope the key per
|
|
20
|
+
* thread and create one storage instance per thread runtime rather than a
|
|
21
|
+
* single shared one. A shared key is written and cleared by whichever thread
|
|
22
|
+
* acts last, so one conversation's stream can resume inside another.
|
|
23
|
+
*/
|
|
24
|
+
key?: string | (() => string | undefined);
|
|
25
|
+
}): ResumableClientStorage;
|
|
26
|
+
export type AssistantChatResumableOptions = {
|
|
27
|
+
storage: ResumableClientStorage;
|
|
28
|
+
resumeApi: string | ((streamId: string) => string);
|
|
29
|
+
/**
|
|
30
|
+
* Called for each chunk with the accumulated decoded text. Return true when
|
|
31
|
+
* the stream has finished. The default detects complete SSE events whose
|
|
32
|
+
* top-level JSON type is `finish`. Cancellation does not invoke this callback.
|
|
33
|
+
*/
|
|
34
|
+
isFinishEvent?: (chunk: Uint8Array, accumulator: string) => boolean;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=resumable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable.d.ts","mappings":"AAIA;AACA,eAAO,MAAM,0BAA0B,0BAAmC;AAI1E,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAC7C,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAChD,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAC9B;IACA,SAAS,CAAC,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI;CAChE;AAgBD;AACA,wBAAgB,6BAA6B,CAAC,OAAO,CAAC,EAAE;IACtD;;;;;;;;;;;;IAYA,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC;CAC1C,GAAG,sBAAsB;AA0G1B,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,sBAAsB;IAC/B,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;;;;;IAKA,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO;CACpE","names":[],"ignoreList":[],"sources":["../../src/transport/resumable.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { RESUMABLE_STREAM_ID_HEADER as RESUMABLE_STREAM_ID_HEADER$1 } from "@ulearning/ai-stream/resumable";
|
|
3
|
+
// src/transport/resumable.ts
|
|
4
|
+
/** Response header used by the [Resumable Streams](/docs/guides/resumable-streams) server and client wiring. */
|
|
5
|
+
const RESUMABLE_STREAM_ID_HEADER = RESUMABLE_STREAM_ID_HEADER$1;
|
|
6
|
+
const DEFAULT_STORAGE_KEY = "aui-resumable-stream-id";
|
|
7
|
+
const getSessionStorage = () => {
|
|
8
|
+
if (typeof window === "undefined") return null;
|
|
9
|
+
try {
|
|
10
|
+
return window.sessionStorage;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** `sessionStorage`-backed storage for the pending resumable stream id. See the [Resumable Streams](/docs/guides/resumable-streams) guide for end-to-end wiring. */
|
|
16
|
+
function createResumableSessionStorage(options) {
|
|
17
|
+
const keyOption = options?.key;
|
|
18
|
+
const resolveKey = () => {
|
|
19
|
+
if (typeof keyOption !== "function") return keyOption ?? DEFAULT_STORAGE_KEY;
|
|
20
|
+
try {
|
|
21
|
+
return keyOption();
|
|
22
|
+
} catch {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const slots = /* @__PURE__ */ new Map();
|
|
27
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
28
|
+
const readSlot = (key) => {
|
|
29
|
+
let slot = slots.get(key);
|
|
30
|
+
if (slot) return slot;
|
|
31
|
+
const storage = getSessionStorage();
|
|
32
|
+
let value = null;
|
|
33
|
+
if (storage) try {
|
|
34
|
+
value = storage.getItem(key);
|
|
35
|
+
} catch {
|
|
36
|
+
value = null;
|
|
37
|
+
}
|
|
38
|
+
slot = {
|
|
39
|
+
value,
|
|
40
|
+
owner: void 0
|
|
41
|
+
};
|
|
42
|
+
slots.set(key, slot);
|
|
43
|
+
return slot;
|
|
44
|
+
};
|
|
45
|
+
const notify = (threadId) => {
|
|
46
|
+
for (const subscription of listeners) {
|
|
47
|
+
if (threadId && subscription.threadId !== threadId) continue;
|
|
48
|
+
try {
|
|
49
|
+
subscription.listener();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("[assistant-ui] resumable storage listener failed", error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
getStreamId(threadId) {
|
|
57
|
+
const key = resolveKey();
|
|
58
|
+
if (!key) return null;
|
|
59
|
+
const slot = readSlot(key);
|
|
60
|
+
if (slot.value === null) return null;
|
|
61
|
+
if (slot.owner && threadId && slot.owner !== threadId) return null;
|
|
62
|
+
return slot.value;
|
|
63
|
+
},
|
|
64
|
+
setStreamId(id, threadId) {
|
|
65
|
+
const key = resolveKey();
|
|
66
|
+
const storage = getSessionStorage();
|
|
67
|
+
if (!key || !storage) return;
|
|
68
|
+
try {
|
|
69
|
+
storage.setItem(key, id);
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const slot = readSlot(key);
|
|
74
|
+
slot.value = id;
|
|
75
|
+
if (threadId) slot.owner = threadId;
|
|
76
|
+
else if (!slot.owner) slot.owner = Array.from(listeners).find((subscription) => subscription.threadId !== void 0)?.threadId;
|
|
77
|
+
notify(slot.owner);
|
|
78
|
+
},
|
|
79
|
+
clear(threadId) {
|
|
80
|
+
const key = resolveKey();
|
|
81
|
+
if (!key) return;
|
|
82
|
+
const slot = readSlot(key);
|
|
83
|
+
if (slot.owner && threadId && slot.owner !== threadId) return;
|
|
84
|
+
const storage = getSessionStorage();
|
|
85
|
+
if (!storage) return;
|
|
86
|
+
try {
|
|
87
|
+
storage.removeItem(key);
|
|
88
|
+
} catch {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
slot.value = null;
|
|
92
|
+
slot.owner = void 0;
|
|
93
|
+
notify(threadId);
|
|
94
|
+
},
|
|
95
|
+
subscribe(listener, threadId) {
|
|
96
|
+
const key = resolveKey();
|
|
97
|
+
if (key && threadId) {
|
|
98
|
+
const slot = readSlot(key);
|
|
99
|
+
if (slot.value !== null) slot.owner ??= threadId;
|
|
100
|
+
}
|
|
101
|
+
const subscription = {
|
|
102
|
+
listener,
|
|
103
|
+
threadId
|
|
104
|
+
};
|
|
105
|
+
listeners.add(subscription);
|
|
106
|
+
return () => listeners.delete(subscription);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export { RESUMABLE_STREAM_ID_HEADER, createResumableSessionStorage };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=resumable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable.js","names":["RESUMABLE_STREAM_ID_HEADER_VALUE"],"sources":["../../src/transport/resumable.ts"],"sourcesContent":["\"use client\";\n\nimport { RESUMABLE_STREAM_ID_HEADER as RESUMABLE_STREAM_ID_HEADER_VALUE } from \"@ulearning/ai-stream/resumable\";\n\n/** Response header used by the [Resumable Streams](/docs/guides/resumable-streams) server and client wiring. */\nexport const RESUMABLE_STREAM_ID_HEADER = RESUMABLE_STREAM_ID_HEADER_VALUE;\n\nconst DEFAULT_STORAGE_KEY = \"aui-resumable-stream-id\";\n\nexport type ResumableClientStorage = {\n getStreamId(threadId?: string): string | null;\n setStreamId(id: string, threadId?: string): void;\n clear(threadId?: string): void;\n /** Subscribes to stream id changes so automatic resume can react after mount. */\n subscribe?(listener: () => void, threadId?: string): () => void;\n};\n\nconst getSessionStorage = (): Storage | null => {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n};\n\ntype StorageSlot = {\n value: string | null;\n owner: string | undefined;\n};\n\n/** `sessionStorage`-backed storage for the pending resumable stream id. See the [Resumable Streams](/docs/guides/resumable-streams) guide for end-to-end wiring. */\nexport function createResumableSessionStorage(options?: {\n /**\n * Storage key for the pending stream id. A static string namespaces per route\n * or chat surface. A getter is read lazily on every access, so the key can be\n * derived from the active thread's identity; while the getter returns\n * `undefined`, reads report no pending stream and writes are dropped, so a\n * thread whose identity is not known yet never touches another thread's key.\n *\n * Under a remote thread list with more than one thread, scope the key per\n * thread and create one storage instance per thread runtime rather than a\n * single shared one. A shared key is written and cleared by whichever thread\n * acts last, so one conversation's stream can resume inside another.\n */\n key?: string | (() => string | undefined);\n}): ResumableClientStorage {\n const keyOption = options?.key;\n const resolveKey = (): string | undefined => {\n if (typeof keyOption !== \"function\")\n return keyOption ?? DEFAULT_STORAGE_KEY;\n try {\n return keyOption();\n } catch {\n return undefined;\n }\n };\n const slots = new Map<string, StorageSlot>();\n const listeners = new Set<{\n listener: () => void;\n threadId: string | undefined;\n }>();\n const readSlot = (key: string): StorageSlot => {\n let slot = slots.get(key);\n if (slot) return slot;\n const storage = getSessionStorage();\n let value: string | null = null;\n if (storage) {\n try {\n value = storage.getItem(key);\n } catch {\n value = null;\n }\n }\n slot = { value, owner: undefined };\n slots.set(key, slot);\n return slot;\n };\n const notify = (threadId?: string) => {\n for (const subscription of listeners) {\n if (threadId && subscription.threadId !== threadId) continue;\n try {\n subscription.listener();\n } catch (error) {\n console.error(\n \"[assistant-ui] resumable storage listener failed\",\n error,\n );\n }\n }\n };\n\n return {\n getStreamId(threadId) {\n const key = resolveKey();\n if (!key) return null;\n const slot = readSlot(key);\n if (slot.value === null) return null;\n if (slot.owner && threadId && slot.owner !== threadId) return null;\n return slot.value;\n },\n setStreamId(id, threadId) {\n const key = resolveKey();\n const storage = getSessionStorage();\n if (!key || !storage) return;\n try {\n storage.setItem(key, id);\n } catch {\n // Ignore blocked or unavailable sessionStorage.\n return;\n }\n const slot = readSlot(key);\n slot.value = id;\n if (threadId) {\n slot.owner = threadId;\n } else if (!slot.owner) {\n slot.owner = Array.from(listeners).find(\n (subscription) => subscription.threadId !== undefined,\n )?.threadId;\n }\n notify(slot.owner);\n },\n clear(threadId) {\n const key = resolveKey();\n if (!key) return;\n const slot = readSlot(key);\n if (slot.owner && threadId && slot.owner !== threadId) return;\n const storage = getSessionStorage();\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n // Ignore blocked or unavailable sessionStorage.\n return;\n }\n slot.value = null;\n slot.owner = undefined;\n notify(threadId);\n },\n subscribe(listener, threadId) {\n const key = resolveKey();\n if (key && threadId) {\n const slot = readSlot(key);\n if (slot.value !== null) slot.owner ??= threadId;\n }\n const subscription = { listener, threadId };\n listeners.add(subscription);\n return () => listeners.delete(subscription);\n },\n };\n}\n\nexport type AssistantChatResumableOptions = {\n storage: ResumableClientStorage;\n resumeApi: string | ((streamId: string) => string);\n /**\n * Called for each chunk with the accumulated decoded text. Return true when\n * the stream has finished. The default detects complete SSE events whose\n * top-level JSON type is `finish`. Cancellation does not invoke this callback.\n */\n isFinishEvent?: (chunk: Uint8Array, accumulator: string) => boolean;\n};\n"],"mappings":";;;;AAKA,MAAa,6BAA6BA;AAE1C,MAAM,sBAAsB;AAU5B,MAAM,0BAA0C;CAC9C,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,IAAI;EACF,OAAO,OAAO;CAChB,QAAQ;EACN,OAAO;CACT;AACF;;AAQA,SAAgB,8BAA8B,SAcnB;CACzB,MAAM,YAAY,SAAS;CAC3B,MAAM,mBAAuC;EAC3C,IAAI,OAAO,cAAc,YACvB,OAAO,aAAa;EACtB,IAAI;GACF,OAAO,UAAU;EACnB,QAAQ;GACN;EACF;CACF;CACA,MAAM,wBAAQ,IAAI,IAAyB;CAC3C,MAAM,4BAAY,IAAI,IAGnB;CACH,MAAM,YAAY,QAA6B;EAC7C,IAAI,OAAO,MAAM,IAAI,GAAG;EACxB,IAAI,MAAM,OAAO;EACjB,MAAM,UAAU,kBAAkB;EAClC,IAAI,QAAuB;EAC3B,IAAI,SACF,IAAI;GACF,QAAQ,QAAQ,QAAQ,GAAG;EAC7B,QAAQ;GACN,QAAQ;EACV;EAEF,OAAO;GAAE;GAAO,OAAO,KAAA;EAAU;EACjC,MAAM,IAAI,KAAK,IAAI;EACnB,OAAO;CACT;CACA,MAAM,UAAU,aAAsB;EACpC,KAAK,MAAM,gBAAgB,WAAW;GACpC,IAAI,YAAY,aAAa,aAAa,UAAU;GACpD,IAAI;IACF,aAAa,SAAS;GACxB,SAAS,OAAO;IACd,QAAQ,MACN,oDACA,KACF;GACF;EACF;CACF;CAEA,OAAO;EACL,YAAY,UAAU;GACpB,MAAM,MAAM,WAAW;GACvB,IAAI,CAAC,KAAK,OAAO;GACjB,MAAM,OAAO,SAAS,GAAG;GACzB,IAAI,KAAK,UAAU,MAAM,OAAO;GAChC,IAAI,KAAK,SAAS,YAAY,KAAK,UAAU,UAAU,OAAO;GAC9D,OAAO,KAAK;EACd;EACA,YAAY,IAAI,UAAU;GACxB,MAAM,MAAM,WAAW;GACvB,MAAM,UAAU,kBAAkB;GAClC,IAAI,CAAC,OAAO,CAAC,SAAS;GACtB,IAAI;IACF,QAAQ,QAAQ,KAAK,EAAE;GACzB,QAAQ;IAEN;GACF;GACA,MAAM,OAAO,SAAS,GAAG;GACzB,KAAK,QAAQ;GACb,IAAI,UACF,KAAK,QAAQ;QACR,IAAI,CAAC,KAAK,OACf,KAAK,QAAQ,MAAM,KAAK,SAAS,CAAC,CAAC,MAChC,iBAAiB,aAAa,aAAa,KAAA,CAC9C,CAAC,EAAE;GAEL,OAAO,KAAK,KAAK;EACnB;EACA,MAAM,UAAU;GACd,MAAM,MAAM,WAAW;GACvB,IAAI,CAAC,KAAK;GACV,MAAM,OAAO,SAAS,GAAG;GACzB,IAAI,KAAK,SAAS,YAAY,KAAK,UAAU,UAAU;GACvD,MAAM,UAAU,kBAAkB;GAClC,IAAI,CAAC,SAAS;GACd,IAAI;IACF,QAAQ,WAAW,GAAG;GACxB,QAAQ;IAEN;GACF;GACA,KAAK,QAAQ;GACb,KAAK,QAAQ,KAAA;GACb,OAAO,QAAQ;EACjB;EACA,UAAU,UAAU,UAAU;GAC5B,MAAM,MAAM,WAAW;GACvB,IAAI,OAAO,UAAU;IACnB,MAAM,OAAO,SAAS,GAAG;IACzB,IAAI,KAAK,UAAU,MAAM,KAAK,UAAU;GAC1C;GACA,MAAM,eAAe;IAAE;IAAU;GAAS;GAC1C,UAAU,IAAI,YAAY;GAC1B,aAAa,UAAU,OAAO,YAAY;EAC5C;CACF;AACF"}
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="@ulearning/ai-core/vue" preserve="true" />
|
|
2
|
+
import { type ComputedRef } from "vue";
|
|
3
|
+
export type ThreadTokenUsage = {
|
|
4
|
+
totalTokens?: number;
|
|
5
|
+
inputTokens?: number;
|
|
6
|
+
outputTokens?: number;
|
|
7
|
+
reasoningTokens?: number;
|
|
8
|
+
cachedInputTokens?: number;
|
|
9
|
+
};
|
|
10
|
+
export interface TokenUsageExtractableMessage {
|
|
11
|
+
role?: string;
|
|
12
|
+
metadata?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessage | undefined): ThreadTokenUsage | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Reads token usage from the newest assistant message that reports any.
|
|
17
|
+
*
|
|
18
|
+
* A route attaches usage through the AI SDK's `messageMetadata` option. Because
|
|
19
|
+
* a thread message carries a fixed metadata shape, the converter moves every
|
|
20
|
+
* other key the route returns into `metadata.custom`, which is where this hook
|
|
21
|
+
* looks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useThreadTokenUsage(): ComputedRef<ThreadTokenUsage | undefined>;
|
|
24
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","mappings":";AACA,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,KAAK;AAGhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM;IACpB,WAAW,CAAC,EAAE,MAAM;IACpB,YAAY,CAAC,EAAE,MAAM;IACrB,eAAe,CAAC,EAAE,MAAM;IACxB,iBAAiB,CAAC,EAAE,MAAM;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,EAAE,MAAM;IACb,QAAQ,CAAC,EAAE,OAAO;;AAuHpB,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,4BAA4B,GAAG,SAAS,GAChD,gBAAgB,GAAG,SAAS;AA8B/B;;;;;;;;AAQA,wBAAgB,mBAAmB,IAAI,WAAW,CAChD,gBAAgB,GAAG,SAAS,CAC7B","names":[],"ignoreList":[],"sources":["../src/usage.ts"],"sourcesContent":[null]}
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useAuiState } from "@ulearning/ai-store";
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
// src/usage.ts
|
|
4
|
+
const USAGE_KEYS = [
|
|
5
|
+
"inputTokens",
|
|
6
|
+
"outputTokens",
|
|
7
|
+
"reasoningTokens",
|
|
8
|
+
"cachedInputTokens",
|
|
9
|
+
"totalTokens"
|
|
10
|
+
];
|
|
11
|
+
function asRecord(value) {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function asPositiveTokenCount(value) {
|
|
16
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function computeTotalTokens(usage) {
|
|
20
|
+
if (usage.totalTokens !== void 0) return usage.totalTokens;
|
|
21
|
+
if (usage.inputTokens !== void 0 && usage.outputTokens !== void 0) return usage.inputTokens + usage.outputTokens;
|
|
22
|
+
}
|
|
23
|
+
function normalizeUsage(value) {
|
|
24
|
+
const record = asRecord(value);
|
|
25
|
+
if (!record) return void 0;
|
|
26
|
+
const result = {};
|
|
27
|
+
let hasFields = false;
|
|
28
|
+
for (const key of USAGE_KEYS) {
|
|
29
|
+
const count = asPositiveTokenCount(record[key]);
|
|
30
|
+
if (count !== void 0) {
|
|
31
|
+
result[key] = count;
|
|
32
|
+
hasFields = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (result.reasoningTokens === void 0) {
|
|
36
|
+
const count = asPositiveTokenCount(asRecord(record.outputTokenDetails)?.reasoningTokens);
|
|
37
|
+
if (count !== void 0) {
|
|
38
|
+
result.reasoningTokens = count;
|
|
39
|
+
hasFields = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (result.cachedInputTokens === void 0) {
|
|
43
|
+
const count = asPositiveTokenCount(asRecord(record.inputTokenDetails)?.cacheReadTokens);
|
|
44
|
+
if (count !== void 0) {
|
|
45
|
+
result.cachedInputTokens = count;
|
|
46
|
+
hasFields = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return hasFields ? result : void 0;
|
|
50
|
+
}
|
|
51
|
+
function withComputedTotal(usage) {
|
|
52
|
+
const totalTokens = computeTotalTokens(usage);
|
|
53
|
+
return {
|
|
54
|
+
...usage,
|
|
55
|
+
...totalTokens !== void 0 && { totalTokens }
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function usageFromSteps(value) {
|
|
59
|
+
const steps = Array.isArray(value) ? value : [];
|
|
60
|
+
const sums = {};
|
|
61
|
+
const present = {};
|
|
62
|
+
let stepsWithUsage = 0;
|
|
63
|
+
let stepsWithComputableTotal = 0;
|
|
64
|
+
for (const step of steps) {
|
|
65
|
+
const usage = normalizeUsage(asRecord(step)?.usage);
|
|
66
|
+
if (!usage) continue;
|
|
67
|
+
stepsWithUsage++;
|
|
68
|
+
const stepTotal = computeTotalTokens(usage);
|
|
69
|
+
if (stepTotal !== void 0) {
|
|
70
|
+
sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;
|
|
71
|
+
stepsWithComputableTotal++;
|
|
72
|
+
}
|
|
73
|
+
for (const key of USAGE_KEYS) {
|
|
74
|
+
if (key === "totalTokens") continue;
|
|
75
|
+
if (usage[key] !== void 0) {
|
|
76
|
+
sums[key] = (sums[key] ?? 0) + usage[key];
|
|
77
|
+
present[key] = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (stepsWithUsage === 0) return void 0;
|
|
82
|
+
const result = {};
|
|
83
|
+
if (stepsWithComputableTotal === stepsWithUsage) result.totalTokens = sums.totalTokens;
|
|
84
|
+
for (const key of USAGE_KEYS) {
|
|
85
|
+
if (key === "totalTokens") continue;
|
|
86
|
+
if (present[key]) result[key] = sums[key];
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function getThreadMessageTokenUsage(message) {
|
|
91
|
+
if (!message || message.role !== "assistant") return void 0;
|
|
92
|
+
const metadata = asRecord(message.metadata);
|
|
93
|
+
if (!metadata) return void 0;
|
|
94
|
+
const topLevelUsage = normalizeUsage(metadata.usage);
|
|
95
|
+
if (topLevelUsage) return withComputedTotal(topLevelUsage);
|
|
96
|
+
const customUsage = normalizeUsage(asRecord(metadata.custom)?.usage);
|
|
97
|
+
if (customUsage) return withComputedTotal(customUsage);
|
|
98
|
+
return usageFromSteps(metadata.steps);
|
|
99
|
+
}
|
|
100
|
+
function findLatestMessageWithUsage(messages) {
|
|
101
|
+
if (!messages) return void 0;
|
|
102
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
103
|
+
const message = messages[idx];
|
|
104
|
+
if (getThreadMessageTokenUsage(message)) return message;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reads token usage from the newest assistant message that reports any.
|
|
109
|
+
*
|
|
110
|
+
* A route attaches usage through the AI SDK's `messageMetadata` option. Because
|
|
111
|
+
* a thread message carries a fixed metadata shape, the converter moves every
|
|
112
|
+
* other key the route returns into `metadata.custom`, which is where this hook
|
|
113
|
+
* looks.
|
|
114
|
+
*/
|
|
115
|
+
function useThreadTokenUsage() {
|
|
116
|
+
const msg = useAuiState((s) => findLatestMessageWithUsage(s.thread.messages));
|
|
117
|
+
return computed(() => getThreadMessageTokenUsage(msg.value));
|
|
118
|
+
}
|
|
119
|
+
export { getThreadMessageTokenUsage, useThreadTokenUsage };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","names":[],"sources":["../src/usage.ts"],"sourcesContent":["/// <reference types=\"@ulearning/ai-core/vue\" preserve=\"true\" />\nimport { computed, type ComputedRef } from \"vue\";\nimport { useAuiState } from \"@ulearning/ai-store\";\n\nexport type ThreadTokenUsage = {\n totalTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n};\n\nexport interface TokenUsageExtractableMessage {\n role?: string;\n metadata?: unknown;\n}\n\ntype UsageRecord = Record<string, unknown>;\n\nconst USAGE_KEYS = [\n \"inputTokens\",\n \"outputTokens\",\n \"reasoningTokens\",\n \"cachedInputTokens\",\n \"totalTokens\",\n] as const satisfies (keyof ThreadTokenUsage)[];\n\nfunction asRecord(value: unknown): UsageRecord | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n return undefined;\n return value as UsageRecord;\n}\n\nfunction asPositiveTokenCount(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return undefined;\n }\n return value;\n}\n\nfunction computeTotalTokens(usage: ThreadTokenUsage): number | undefined {\n if (usage.totalTokens !== undefined) return usage.totalTokens;\n if (usage.inputTokens !== undefined && usage.outputTokens !== undefined) {\n return usage.inputTokens + usage.outputTokens;\n }\n return undefined;\n}\n\nfunction normalizeUsage(value: unknown): ThreadTokenUsage | undefined {\n const record = asRecord(value);\n if (!record) return undefined;\n\n const result: ThreadTokenUsage = {};\n let hasFields = false;\n for (const key of USAGE_KEYS) {\n const count = asPositiveTokenCount(record[key]);\n if (count !== undefined) {\n result[key] = count;\n hasFields = true;\n }\n }\n // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n if (result.reasoningTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.outputTokenDetails)?.reasoningTokens,\n );\n if (count !== undefined) {\n result.reasoningTokens = count;\n hasFields = true;\n }\n }\n if (result.cachedInputTokens === undefined) {\n const count = asPositiveTokenCount(\n asRecord(record.inputTokenDetails)?.cacheReadTokens,\n );\n if (count !== undefined) {\n result.cachedInputTokens = count;\n hasFields = true;\n }\n }\n return hasFields ? result : undefined;\n}\n\nfunction withComputedTotal(\n usage: ThreadTokenUsage,\n): ThreadTokenUsage | undefined {\n const totalTokens = computeTotalTokens(usage);\n return { ...usage, ...(totalTokens !== undefined && { totalTokens }) };\n}\n\nfunction usageFromSteps(value: unknown): ThreadTokenUsage | undefined {\n const steps = Array.isArray(value) ? value : [];\n\n const sums: Record<string, number> = {};\n const present: Record<string, boolean> = {};\n let stepsWithUsage = 0;\n let stepsWithComputableTotal = 0;\n\n for (const step of steps) {\n const usage = normalizeUsage(asRecord(step)?.usage);\n if (!usage) continue;\n stepsWithUsage++;\n\n const stepTotal = computeTotalTokens(usage);\n if (stepTotal !== undefined) {\n sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;\n stepsWithComputableTotal++;\n }\n\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (usage[key] !== undefined) {\n sums[key] = (sums[key] ?? 0) + usage[key];\n present[key] = true;\n }\n }\n }\n\n if (stepsWithUsage === 0) return undefined;\n\n const result: ThreadTokenUsage = {};\n if (stepsWithComputableTotal === stepsWithUsage) {\n result.totalTokens = sums.totalTokens!;\n }\n for (const key of USAGE_KEYS) {\n if (key === \"totalTokens\") continue;\n if (present[key]) {\n result[key] = sums[key]!;\n }\n }\n return result;\n}\n\nexport function getThreadMessageTokenUsage(\n message: TokenUsageExtractableMessage | undefined,\n): ThreadTokenUsage | undefined {\n if (!message || message.role !== \"assistant\") return undefined;\n\n const metadata = asRecord(message.metadata);\n if (!metadata) return undefined;\n\n const topLevelUsage = normalizeUsage(metadata.usage);\n if (topLevelUsage) return withComputedTotal(topLevelUsage);\n\n const customUsage = normalizeUsage(asRecord(metadata.custom)?.usage);\n if (customUsage) return withComputedTotal(customUsage);\n\n return usageFromSteps(metadata.steps);\n}\n\nfunction findLatestMessageWithUsage(\n messages: readonly TokenUsageExtractableMessage[] | undefined,\n): TokenUsageExtractableMessage | undefined {\n if (!messages) return undefined;\n\n for (let idx = messages.length - 1; idx >= 0; idx -= 1) {\n const message = messages[idx];\n if (getThreadMessageTokenUsage(message)) {\n return message;\n }\n }\n\n return undefined;\n}\n\n/**\n * Reads token usage from the newest assistant message that reports any.\n *\n * A route attaches usage through the AI SDK's `messageMetadata` option. Because\n * a thread message carries a fixed metadata shape, the converter moves every\n * other key the route returns into `metadata.custom`, which is where this hook\n * looks.\n */\nexport function useThreadTokenUsage(): ComputedRef<\n ThreadTokenUsage | undefined\n> {\n const msg = useAuiState((s) => findLatestMessageWithUsage(s.thread.messages));\n return computed(() => getThreadMessageTokenUsage(msg.value));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ThreadMessage } from "@ulearning/ai-core";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
export declare const getVercelAIMessages: <UI_MESSAGE extends UIMessage = UIMessage>(message: ThreadMessage) => UI_MESSAGE[];
|
|
4
|
+
//# sourceMappingURL=getVercelAIMessages.d.ts.map
|