@tanstack/ai-client 0.2.2 → 0.3.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/README.md CHANGED
@@ -45,6 +45,7 @@ A powerful, type-safe AI SDK for building AI-powered applications.
45
45
  - Headless chat state management with adapters (SSE, HTTP stream, custom)
46
46
  - Isomorphic type-safe tools with server/client execution
47
47
  - **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions
48
+ - **Observability events** - Structured, typed events for text, tools, image, speech, transcription, and video ([docs](./docs/guides/observability.md))
48
49
 
49
50
  ### <a href="https://tanstack.com/ai">Read the docs →</b></a>
50
51
 
@@ -108,13 +108,16 @@ class ChatClient {
108
108
  }
109
109
  },
110
110
  onApprovalRequest: (args) => {
111
- this.events.approvalRequested(
112
- this.currentMessageId || "",
113
- args.toolCallId,
114
- args.toolName,
115
- args.input,
116
- args.approvalId
117
- );
111
+ if (this.currentStreamId) {
112
+ this.events.approvalRequested(
113
+ this.currentStreamId,
114
+ this.currentMessageId || "",
115
+ args.toolCallId,
116
+ args.toolName,
117
+ args.input,
118
+ args.approvalId
119
+ );
120
+ }
118
121
  }
119
122
  }
120
123
  });
@@ -146,7 +149,10 @@ class ChatClient {
146
149
  parts: [],
147
150
  createdAt: /* @__PURE__ */ new Date()
148
151
  };
149
- this.events.messageAppended(assistantMessage);
152
+ this.events.messageAppended(
153
+ assistantMessage,
154
+ this.currentStreamId || void 0
155
+ );
150
156
  for await (const chunk of source) {
151
157
  this.callbacksRef.current.onChunk(chunk);
152
158
  this.processor.processChunk(chunk);
@@ -1 +1 @@
1
- {"version":3,"file":"chat-client.js","sources":["../../src/chat-client.ts"],"sourcesContent":["import {\n StreamProcessor,\n generateMessageId,\n normalizeToUIMessage,\n} from '@tanstack/ai'\nimport { DefaultChatClientEventEmitter } from './events'\nimport type {\n ChatClientOptions,\n MessagePart,\n ToolCallPart,\n UIMessage,\n} from './types'\nimport type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\nimport type { ChatClientEventEmitter } from './events'\n\nexport class ChatClient {\n private processor: StreamProcessor\n private connection: ConnectionAdapter\n private uniqueId: string\n private body: Record<string, any> = {}\n private isLoading = false\n private error: Error | undefined = undefined\n private abortController: AbortController | null = null\n private events: ChatClientEventEmitter\n private clientToolsRef: { current: Map<string, AnyClientTool> }\n private currentStreamId: string | null = null\n private currentMessageId: string | null = null\n private postStreamActions: Array<() => Promise<void>> = []\n\n private callbacksRef: {\n current: {\n onResponse: (response?: Response) => void | Promise<void>\n onChunk: (chunk: StreamChunk) => void\n onFinish: (message: UIMessage) => void\n onError: (error: Error) => void\n onMessagesChange: (messages: Array<UIMessage>) => void\n onLoadingChange: (isLoading: boolean) => void\n onErrorChange: (error: Error | undefined) => void\n }\n }\n\n constructor(options: ChatClientOptions) {\n this.uniqueId = options.id || this.generateUniqueId('chat')\n this.body = options.body || {}\n this.connection = options.connection\n this.events = new DefaultChatClientEventEmitter(this.uniqueId)\n\n // Build client tools map\n this.clientToolsRef = { current: new Map() }\n if (options.tools) {\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n\n this.callbacksRef = {\n current: {\n onResponse: options.onResponse || (() => {}),\n onChunk: options.onChunk || (() => {}),\n onFinish: options.onFinish || (() => {}),\n onError: options.onError || (() => {}),\n onMessagesChange: options.onMessagesChange || (() => {}),\n onLoadingChange: options.onLoadingChange || (() => {}),\n onErrorChange: options.onErrorChange || (() => {}),\n },\n }\n\n // Create StreamProcessor with event handlers\n this.processor = new StreamProcessor({\n chunkStrategy: options.streamProcessor?.chunkStrategy,\n initialMessages: options.initialMessages,\n events: {\n onMessagesChange: (messages: Array<UIMessage>) => {\n this.callbacksRef.current.onMessagesChange(messages)\n },\n onStreamStart: () => {\n // Stream started\n },\n onStreamEnd: (message: UIMessage) => {\n this.callbacksRef.current.onFinish(message)\n },\n onError: (error: Error) => {\n this.setError(error)\n this.callbacksRef.current.onError(error)\n },\n onTextUpdate: (messageId: string, content: string) => {\n // Emit text update to devtools\n if (this.currentStreamId) {\n this.events.textUpdated(this.currentStreamId, messageId, content)\n }\n },\n onThinkingUpdate: (messageId: string, content: string) => {\n // Emit thinking update to devtools\n if (this.currentStreamId) {\n this.events.thinkingUpdated(\n this.currentStreamId,\n messageId,\n content,\n )\n }\n },\n onToolCallStateChange: (\n messageId: string,\n toolCallId: string,\n state: string,\n args: string,\n ) => {\n // Get the tool name from the messages\n const messages = this.processor.getMessages()\n const message = messages.find((m: UIMessage) => m.id === messageId)\n const toolCallPart = message?.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.id === toolCallId,\n )\n const toolName = toolCallPart?.name || 'unknown'\n\n // Emit tool call state change to devtools\n if (this.currentStreamId) {\n this.events.toolCallStateChanged(\n this.currentStreamId,\n messageId,\n toolCallId,\n toolName,\n state,\n args,\n )\n }\n },\n onToolCall: async (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => {\n // Handle client-side tool execution automatically\n const clientTool = this.clientToolsRef.current.get(args.toolName)\n if (clientTool?.execute) {\n try {\n const output = await clientTool.execute(args.input)\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output,\n state: 'output-available',\n })\n } catch (error: any) {\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output: null,\n state: 'output-error',\n errorText: error.message,\n })\n }\n }\n },\n onApprovalRequest: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => {\n this.events.approvalRequested(\n this.currentMessageId || '',\n args.toolCallId,\n args.toolName,\n args.input,\n args.approvalId,\n )\n },\n },\n })\n\n this.events.clientCreated(this.processor.getMessages().length)\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.current.onLoadingChange(isLoading)\n this.events.loadingChanged(isLoading)\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.current.onErrorChange(error)\n this.events.errorChanged(error?.message || null)\n }\n\n /**\n * Process a stream through the StreamProcessor\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n ): Promise<UIMessage> {\n // Generate a stream ID for this streaming operation\n this.currentStreamId = this.generateUniqueId('stream')\n\n // Start a new assistant message\n const messageId = this.processor.startAssistantMessage()\n this.currentMessageId = messageId\n\n // Emit message appended event for the new assistant message\n const assistantMessage: UIMessage = {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.events.messageAppended(assistantMessage)\n\n // Process each chunk\n for await (const chunk of source) {\n this.callbacksRef.current.onChunk(chunk)\n this.processor.processChunk(chunk)\n\n // Yield control back to event loop to allow UI updates\n await new Promise((resolve) => setTimeout(resolve, 0))\n }\n\n // Finalize the stream\n this.processor.finalizeStream()\n\n // Clear the current stream and message IDs\n this.currentStreamId = null\n this.currentMessageId = null\n\n // Return the assistant message\n const messages = this.processor.getMessages()\n const finalAssistantMessage = messages.find(\n (m: UIMessage) => m.id === messageId,\n )\n\n return (\n finalAssistantMessage || {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n )\n }\n\n /**\n * Send a message and stream the response\n */\n async sendMessage(content: string): Promise<void> {\n if (!content.trim() || this.isLoading) {\n return\n }\n\n // Add user message via processor\n const userMessage = this.processor.addUserMessage(content.trim())\n this.events.messageSent(userMessage.id, content.trim())\n\n await this.streamResponse()\n }\n\n /**\n * Append a message and stream the response\n */\n async append(message: UIMessage | ModelMessage): Promise<void> {\n // Normalize the message to ensure it has id and createdAt\n const normalizedMessage = normalizeToUIMessage(message, generateMessageId)\n\n // Skip system messages - they're handled via systemPrompts, not UIMessages\n if (normalizedMessage.role === 'system') {\n return\n }\n\n // Type assertion: after checking for system, we know it's user or assistant\n const uiMessage = normalizedMessage as UIMessage\n\n // Emit message appended event\n this.events.messageAppended(uiMessage)\n\n // Add to messages\n const messages = this.processor.getMessages()\n this.processor.setMessages([...messages, uiMessage])\n\n await this.streamResponse()\n }\n\n /**\n * Stream a response from the LLM\n */\n private async streamResponse(): Promise<void> {\n this.setIsLoading(true)\n this.setError(undefined)\n this.abortController = new AbortController()\n\n try {\n // Get model messages for the LLM\n const modelMessages = this.processor.toModelMessages()\n\n // Call onResponse callback\n await this.callbacksRef.current.onResponse()\n\n // Include conversationId in the body for server-side event correlation\n const bodyWithConversationId = {\n ...this.body,\n conversationId: this.uniqueId,\n }\n\n // Connect and stream\n const stream = this.connection.connect(\n modelMessages,\n bodyWithConversationId,\n this.abortController.signal,\n )\n\n await this.processStream(stream)\n } catch (err) {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return\n }\n this.setError(err)\n this.callbacksRef.current.onError(err)\n }\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n\n // Drain any actions that were queued while the stream was in progress\n await this.drainPostStreamActions()\n }\n }\n\n /**\n * Reload the last assistant message\n */\n async reload(): Promise<void> {\n const messages = this.processor.getMessages()\n if (messages.length === 0) return\n\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m: UIMessage) => m.role === 'user',\n )\n\n if (lastUserMessageIndex === -1) return\n\n this.events.reloaded(lastUserMessageIndex)\n\n // Remove all messages after the last user message\n this.processor.removeMessagesAfter(lastUserMessageIndex)\n\n // Resend\n await this.streamResponse()\n }\n\n /**\n * Stop the current stream\n */\n stop(): void {\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n this.events.stopped()\n }\n\n /**\n * Clear all messages\n */\n clear(): void {\n this.processor.clearMessages()\n this.setError(undefined)\n this.events.messagesCleared()\n }\n\n /**\n * Add the result of a client-side tool execution\n */\n async addToolResult(result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }): Promise<void> {\n this.events.toolResultAdded(\n result.toolCallId,\n result.tool,\n result.output,\n result.state || 'output-available',\n )\n\n // Add result via processor\n this.processor.addToolResult(\n result.toolCallId,\n result.output,\n result.errorText,\n )\n\n // If stream is in progress, queue continuation check for after it ends\n if (this.isLoading) {\n this.queuePostStreamAction(() => this.checkForContinuation())\n return\n }\n\n await this.checkForContinuation()\n }\n\n /**\n * Respond to a tool approval request\n */\n async addToolApprovalResponse(response: {\n id: string // approval.id, not toolCallId\n approved: boolean\n }): Promise<void> {\n // Find the tool call ID from the approval ID\n const messages = this.processor.getMessages()\n let foundToolCallId: string | undefined\n\n for (const msg of messages) {\n const toolCallPart = msg.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === response.id,\n )\n if (toolCallPart) {\n foundToolCallId = toolCallPart.id\n break\n }\n }\n\n if (foundToolCallId) {\n this.events.toolApprovalResponded(\n response.id,\n foundToolCallId,\n response.approved,\n )\n }\n\n // Add response via processor\n this.processor.addToolApprovalResponse(response.id, response.approved)\n\n // If stream is in progress, queue continuation check for after it ends\n if (this.isLoading) {\n this.queuePostStreamAction(() => this.checkForContinuation())\n return\n }\n\n await this.checkForContinuation()\n }\n\n /**\n * Queue an action to be executed after the current stream ends\n */\n private queuePostStreamAction(action: () => Promise<void>): void {\n this.postStreamActions.push(action)\n }\n\n /**\n * Drain and execute all queued post-stream actions\n */\n private async drainPostStreamActions(): Promise<void> {\n while (this.postStreamActions.length > 0) {\n const action = this.postStreamActions.shift()!\n await action()\n }\n }\n\n /**\n * Check if we should continue the flow and do so if needed\n */\n private async checkForContinuation(): Promise<void> {\n if (this.shouldAutoSend()) {\n await this.streamResponse()\n }\n }\n\n /**\n * Check if all tool calls are complete and we should auto-send\n */\n private shouldAutoSend(): boolean {\n return this.processor.areAllToolsComplete()\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.processor.getMessages()\n }\n\n /**\n * Get loading state\n */\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n /**\n * Get current error\n */\n getError(): Error | undefined {\n return this.error\n }\n\n /**\n * Manually set messages\n */\n setMessagesManually(messages: Array<UIMessage>): void {\n this.processor.setMessages(messages)\n }\n\n /**\n * Update options refs (for use in React hooks to avoid recreating client)\n */\n updateOptions(options: {\n connection?: ConnectionAdapter\n body?: Record<string, any>\n tools?: ReadonlyArray<AnyClientTool>\n onResponse?: (response?: Response) => void | Promise<void>\n onChunk?: (chunk: StreamChunk) => void\n onFinish?: (message: UIMessage) => void\n onError?: (error: Error) => void\n }): void {\n if (options.connection !== undefined) {\n this.connection = options.connection\n }\n if (options.body !== undefined) {\n this.body = options.body\n }\n if (options.tools !== undefined) {\n this.clientToolsRef.current = new Map()\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n if (options.onResponse !== undefined) {\n this.callbacksRef.current.onResponse = options.onResponse\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.current.onChunk = options.onChunk\n }\n if (options.onFinish !== undefined) {\n this.callbacksRef.current.onFinish = options.onFinish\n }\n if (options.onError !== undefined) {\n this.callbacksRef.current.onError = options.onError\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-client.js","sources":["../../src/chat-client.ts"],"sourcesContent":["import {\n StreamProcessor,\n generateMessageId,\n normalizeToUIMessage,\n} from '@tanstack/ai'\nimport { DefaultChatClientEventEmitter } from './events'\nimport type {\n ChatClientOptions,\n MessagePart,\n ToolCallPart,\n UIMessage,\n} from './types'\nimport type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\nimport type { ChatClientEventEmitter } from './events'\n\nexport class ChatClient {\n private processor: StreamProcessor\n private connection: ConnectionAdapter\n private uniqueId: string\n private body: Record<string, any> = {}\n private isLoading = false\n private error: Error | undefined = undefined\n private abortController: AbortController | null = null\n private events: ChatClientEventEmitter\n private clientToolsRef: { current: Map<string, AnyClientTool> }\n private currentStreamId: string | null = null\n private currentMessageId: string | null = null\n private postStreamActions: Array<() => Promise<void>> = []\n\n private callbacksRef: {\n current: {\n onResponse: (response?: Response) => void | Promise<void>\n onChunk: (chunk: StreamChunk) => void\n onFinish: (message: UIMessage) => void\n onError: (error: Error) => void\n onMessagesChange: (messages: Array<UIMessage>) => void\n onLoadingChange: (isLoading: boolean) => void\n onErrorChange: (error: Error | undefined) => void\n }\n }\n\n constructor(options: ChatClientOptions) {\n this.uniqueId = options.id || this.generateUniqueId('chat')\n this.body = options.body || {}\n this.connection = options.connection\n this.events = new DefaultChatClientEventEmitter(this.uniqueId)\n\n // Build client tools map\n this.clientToolsRef = { current: new Map() }\n if (options.tools) {\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n\n this.callbacksRef = {\n current: {\n onResponse: options.onResponse || (() => {}),\n onChunk: options.onChunk || (() => {}),\n onFinish: options.onFinish || (() => {}),\n onError: options.onError || (() => {}),\n onMessagesChange: options.onMessagesChange || (() => {}),\n onLoadingChange: options.onLoadingChange || (() => {}),\n onErrorChange: options.onErrorChange || (() => {}),\n },\n }\n\n // Create StreamProcessor with event handlers\n this.processor = new StreamProcessor({\n chunkStrategy: options.streamProcessor?.chunkStrategy,\n initialMessages: options.initialMessages,\n events: {\n onMessagesChange: (messages: Array<UIMessage>) => {\n this.callbacksRef.current.onMessagesChange(messages)\n },\n onStreamStart: () => {\n // Stream started\n },\n onStreamEnd: (message: UIMessage) => {\n this.callbacksRef.current.onFinish(message)\n },\n onError: (error: Error) => {\n this.setError(error)\n this.callbacksRef.current.onError(error)\n },\n onTextUpdate: (messageId: string, content: string) => {\n // Emit text update to devtools\n if (this.currentStreamId) {\n this.events.textUpdated(this.currentStreamId, messageId, content)\n }\n },\n onThinkingUpdate: (messageId: string, content: string) => {\n // Emit thinking update to devtools\n if (this.currentStreamId) {\n this.events.thinkingUpdated(\n this.currentStreamId,\n messageId,\n content,\n )\n }\n },\n onToolCallStateChange: (\n messageId: string,\n toolCallId: string,\n state: string,\n args: string,\n ) => {\n // Get the tool name from the messages\n const messages = this.processor.getMessages()\n const message = messages.find((m: UIMessage) => m.id === messageId)\n const toolCallPart = message?.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.id === toolCallId,\n )\n const toolName = toolCallPart?.name || 'unknown'\n\n // Emit tool call state change to devtools\n if (this.currentStreamId) {\n this.events.toolCallStateChanged(\n this.currentStreamId,\n messageId,\n toolCallId,\n toolName,\n state,\n args,\n )\n }\n },\n onToolCall: async (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => {\n // Handle client-side tool execution automatically\n const clientTool = this.clientToolsRef.current.get(args.toolName)\n if (clientTool?.execute) {\n try {\n const output = await clientTool.execute(args.input)\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output,\n state: 'output-available',\n })\n } catch (error: any) {\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output: null,\n state: 'output-error',\n errorText: error.message,\n })\n }\n }\n },\n onApprovalRequest: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => {\n if (this.currentStreamId) {\n this.events.approvalRequested(\n this.currentStreamId,\n this.currentMessageId || '',\n args.toolCallId,\n args.toolName,\n args.input,\n args.approvalId,\n )\n }\n },\n },\n })\n\n this.events.clientCreated(this.processor.getMessages().length)\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.current.onLoadingChange(isLoading)\n this.events.loadingChanged(isLoading)\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.current.onErrorChange(error)\n this.events.errorChanged(error?.message || null)\n }\n\n /**\n * Process a stream through the StreamProcessor\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n ): Promise<UIMessage> {\n // Generate a stream ID for this streaming operation\n this.currentStreamId = this.generateUniqueId('stream')\n\n // Start a new assistant message\n const messageId = this.processor.startAssistantMessage()\n this.currentMessageId = messageId\n\n // Emit message appended event for the new assistant message\n const assistantMessage: UIMessage = {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.events.messageAppended(\n assistantMessage,\n this.currentStreamId || undefined,\n )\n\n // Process each chunk\n for await (const chunk of source) {\n this.callbacksRef.current.onChunk(chunk)\n this.processor.processChunk(chunk)\n\n // Yield control back to event loop to allow UI updates\n await new Promise((resolve) => setTimeout(resolve, 0))\n }\n\n // Finalize the stream\n this.processor.finalizeStream()\n\n // Clear the current stream and message IDs\n this.currentStreamId = null\n this.currentMessageId = null\n\n // Return the assistant message\n const messages = this.processor.getMessages()\n const finalAssistantMessage = messages.find(\n (m: UIMessage) => m.id === messageId,\n )\n\n return (\n finalAssistantMessage || {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n )\n }\n\n /**\n * Send a message and stream the response\n */\n async sendMessage(content: string): Promise<void> {\n if (!content.trim() || this.isLoading) {\n return\n }\n\n // Add user message via processor\n const userMessage = this.processor.addUserMessage(content.trim())\n this.events.messageSent(userMessage.id, content.trim())\n\n await this.streamResponse()\n }\n\n /**\n * Append a message and stream the response\n */\n async append(message: UIMessage | ModelMessage): Promise<void> {\n // Normalize the message to ensure it has id and createdAt\n const normalizedMessage = normalizeToUIMessage(message, generateMessageId)\n\n // Skip system messages - they're handled via systemPrompts, not UIMessages\n if (normalizedMessage.role === 'system') {\n return\n }\n\n // Type assertion: after checking for system, we know it's user or assistant\n const uiMessage = normalizedMessage as UIMessage\n\n // Emit message appended event\n this.events.messageAppended(uiMessage)\n\n // Add to messages\n const messages = this.processor.getMessages()\n this.processor.setMessages([...messages, uiMessage])\n\n await this.streamResponse()\n }\n\n /**\n * Stream a response from the LLM\n */\n private async streamResponse(): Promise<void> {\n this.setIsLoading(true)\n this.setError(undefined)\n this.abortController = new AbortController()\n\n try {\n // Get model messages for the LLM\n const modelMessages = this.processor.toModelMessages()\n\n // Call onResponse callback\n await this.callbacksRef.current.onResponse()\n\n // Include conversationId in the body for server-side event correlation\n const bodyWithConversationId = {\n ...this.body,\n conversationId: this.uniqueId,\n }\n\n // Connect and stream\n const stream = this.connection.connect(\n modelMessages,\n bodyWithConversationId,\n this.abortController.signal,\n )\n\n await this.processStream(stream)\n } catch (err) {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return\n }\n this.setError(err)\n this.callbacksRef.current.onError(err)\n }\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n\n // Drain any actions that were queued while the stream was in progress\n await this.drainPostStreamActions()\n }\n }\n\n /**\n * Reload the last assistant message\n */\n async reload(): Promise<void> {\n const messages = this.processor.getMessages()\n if (messages.length === 0) return\n\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m: UIMessage) => m.role === 'user',\n )\n\n if (lastUserMessageIndex === -1) return\n\n this.events.reloaded(lastUserMessageIndex)\n\n // Remove all messages after the last user message\n this.processor.removeMessagesAfter(lastUserMessageIndex)\n\n // Resend\n await this.streamResponse()\n }\n\n /**\n * Stop the current stream\n */\n stop(): void {\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n this.events.stopped()\n }\n\n /**\n * Clear all messages\n */\n clear(): void {\n this.processor.clearMessages()\n this.setError(undefined)\n this.events.messagesCleared()\n }\n\n /**\n * Add the result of a client-side tool execution\n */\n async addToolResult(result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }): Promise<void> {\n this.events.toolResultAdded(\n result.toolCallId,\n result.tool,\n result.output,\n result.state || 'output-available',\n )\n\n // Add result via processor\n this.processor.addToolResult(\n result.toolCallId,\n result.output,\n result.errorText,\n )\n\n // If stream is in progress, queue continuation check for after it ends\n if (this.isLoading) {\n this.queuePostStreamAction(() => this.checkForContinuation())\n return\n }\n\n await this.checkForContinuation()\n }\n\n /**\n * Respond to a tool approval request\n */\n async addToolApprovalResponse(response: {\n id: string // approval.id, not toolCallId\n approved: boolean\n }): Promise<void> {\n // Find the tool call ID from the approval ID\n const messages = this.processor.getMessages()\n let foundToolCallId: string | undefined\n\n for (const msg of messages) {\n const toolCallPart = msg.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === response.id,\n )\n if (toolCallPart) {\n foundToolCallId = toolCallPart.id\n break\n }\n }\n\n if (foundToolCallId) {\n this.events.toolApprovalResponded(\n response.id,\n foundToolCallId,\n response.approved,\n )\n }\n\n // Add response via processor\n this.processor.addToolApprovalResponse(response.id, response.approved)\n\n // If stream is in progress, queue continuation check for after it ends\n if (this.isLoading) {\n this.queuePostStreamAction(() => this.checkForContinuation())\n return\n }\n\n await this.checkForContinuation()\n }\n\n /**\n * Queue an action to be executed after the current stream ends\n */\n private queuePostStreamAction(action: () => Promise<void>): void {\n this.postStreamActions.push(action)\n }\n\n /**\n * Drain and execute all queued post-stream actions\n */\n private async drainPostStreamActions(): Promise<void> {\n while (this.postStreamActions.length > 0) {\n const action = this.postStreamActions.shift()!\n await action()\n }\n }\n\n /**\n * Check if we should continue the flow and do so if needed\n */\n private async checkForContinuation(): Promise<void> {\n if (this.shouldAutoSend()) {\n await this.streamResponse()\n }\n }\n\n /**\n * Check if all tool calls are complete and we should auto-send\n */\n private shouldAutoSend(): boolean {\n return this.processor.areAllToolsComplete()\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.processor.getMessages()\n }\n\n /**\n * Get loading state\n */\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n /**\n * Get current error\n */\n getError(): Error | undefined {\n return this.error\n }\n\n /**\n * Manually set messages\n */\n setMessagesManually(messages: Array<UIMessage>): void {\n this.processor.setMessages(messages)\n }\n\n /**\n * Update options refs (for use in React hooks to avoid recreating client)\n */\n updateOptions(options: {\n connection?: ConnectionAdapter\n body?: Record<string, any>\n tools?: ReadonlyArray<AnyClientTool>\n onResponse?: (response?: Response) => void | Promise<void>\n onChunk?: (chunk: StreamChunk) => void\n onFinish?: (message: UIMessage) => void\n onError?: (error: Error) => void\n }): void {\n if (options.connection !== undefined) {\n this.connection = options.connection\n }\n if (options.body !== undefined) {\n this.body = options.body\n }\n if (options.tools !== undefined) {\n this.clientToolsRef.current = new Map()\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n if (options.onResponse !== undefined) {\n this.callbacksRef.current.onResponse = options.onResponse\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.current.onChunk = options.onChunk\n }\n if (options.onFinish !== undefined) {\n this.callbacksRef.current.onFinish = options.onFinish\n }\n if (options.onError !== undefined) {\n this.callbacksRef.current.onError = options.onError\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ export declare abstract class ChatClientEventEmitter {
9
9
  * Protected abstract method for emitting events
10
10
  * Implementations should handle adding clientId and timestamp
11
11
  */
12
- protected abstract emitEvent(eventName: string, data?: Record<string, any>): void;
12
+ protected abstract emitEvent(eventName: string, data?: Record<string, unknown>): void;
13
13
  /**
14
14
  * Emit client created event
15
15
  */
@@ -33,7 +33,6 @@ export declare abstract class ChatClientEventEmitter {
33
33
  /**
34
34
  * Emit tool result state change event
35
35
  */
36
- toolResultStateChanged(streamId: string, toolCallId: string, content: string, state: string, error?: string): void;
37
36
  /**
38
37
  * Emit thinking update event
39
38
  */
@@ -41,11 +40,11 @@ export declare abstract class ChatClientEventEmitter {
41
40
  /**
42
41
  * Emit approval requested event
43
42
  */
44
- approvalRequested(messageId: string, toolCallId: string, toolName: string, input: any, approvalId: string): void;
43
+ approvalRequested(streamId: string, messageId: string, toolCallId: string, toolName: string, input: unknown, approvalId: string): void;
45
44
  /**
46
45
  * Emit message appended event
47
46
  */
48
- messageAppended(uiMessage: UIMessage): void;
47
+ messageAppended(uiMessage: UIMessage, streamId?: string): void;
49
48
  /**
50
49
  * Emit message sent event
51
50
  */
@@ -65,7 +64,7 @@ export declare abstract class ChatClientEventEmitter {
65
64
  /**
66
65
  * Emit tool result added event
67
66
  */
68
- toolResultAdded(toolCallId: string, toolName: string, output: any, state: string): void;
67
+ toolResultAdded(toolCallId: string, toolName: string, output: unknown, state: string): void;
69
68
  /**
70
69
  * Emit tool approval responded event
71
70
  */
@@ -15,13 +15,13 @@ class ChatClientEventEmitter {
15
15
  * Emit loading state changed event
16
16
  */
17
17
  loadingChanged(isLoading) {
18
- this.emitEvent("client:loading-changed", { isLoading });
18
+ this.emitEvent("client:loading:changed", { isLoading });
19
19
  }
20
20
  /**
21
21
  * Emit error state changed event
22
22
  */
23
23
  errorChanged(error) {
24
- this.emitEvent("client:error-changed", {
24
+ this.emitEvent("client:error:changed", {
25
25
  error
26
26
  });
27
27
  }
@@ -29,11 +29,8 @@ class ChatClientEventEmitter {
29
29
  * Emit text update events (combines processor and client events)
30
30
  */
31
31
  textUpdated(streamId, messageId, content) {
32
- this.emitEvent("processor:text-updated", {
32
+ this.emitEvent("text:chunk:content", {
33
33
  streamId,
34
- content
35
- });
36
- this.emitEvent("client:assistant-message-updated", {
37
34
  messageId,
38
35
  content
39
36
  });
@@ -42,14 +39,8 @@ class ChatClientEventEmitter {
42
39
  * Emit tool call state change events (combines processor and client events)
43
40
  */
44
41
  toolCallStateChanged(streamId, messageId, toolCallId, toolName, state, args) {
45
- this.emitEvent("processor:tool-call-state-changed", {
42
+ this.emitEvent("tools:call:updated", {
46
43
  streamId,
47
- toolCallId,
48
- toolName,
49
- state,
50
- arguments: args
51
- });
52
- this.emitEvent("client:tool-call-updated", {
53
44
  messageId,
54
45
  toolCallId,
55
46
  toolName,
@@ -60,20 +51,11 @@ class ChatClientEventEmitter {
60
51
  /**
61
52
  * Emit tool result state change event
62
53
  */
63
- toolResultStateChanged(streamId, toolCallId, content, state, error) {
64
- this.emitEvent("processor:tool-result-state-changed", {
65
- streamId,
66
- toolCallId,
67
- content,
68
- state,
69
- error
70
- });
71
- }
72
54
  /**
73
55
  * Emit thinking update event
74
56
  */
75
57
  thinkingUpdated(streamId, messageId, content, delta) {
76
- this.emitEvent("stream:chunk:thinking", {
58
+ this.emitEvent("text:chunk:thinking", {
77
59
  streamId,
78
60
  messageId,
79
61
  content,
@@ -83,8 +65,9 @@ class ChatClientEventEmitter {
83
65
  /**
84
66
  * Emit approval requested event
85
67
  */
86
- approvalRequested(messageId, toolCallId, toolName, input, approvalId) {
87
- this.emitEvent("client:approval-requested", {
68
+ approvalRequested(streamId, messageId, toolCallId, toolName, input, approvalId) {
69
+ this.emitEvent("tools:approval:requested", {
70
+ streamId,
88
71
  messageId,
89
72
  toolCallId,
90
73
  toolName,
@@ -95,20 +78,28 @@ class ChatClientEventEmitter {
95
78
  /**
96
79
  * Emit message appended event
97
80
  */
98
- messageAppended(uiMessage) {
99
- const contentPreview = uiMessage.parts.filter((p) => p.type === "text").map((p) => p.content).join(" ").substring(0, 100);
100
- this.emitEvent("client:message-appended", {
81
+ messageAppended(uiMessage, streamId) {
82
+ const content = uiMessage.parts.filter((part) => part.type === "text").map((part) => part.content).join(" ");
83
+ this.emitEvent("text:message:created", {
84
+ streamId,
101
85
  messageId: uiMessage.id,
102
86
  role: uiMessage.role,
103
- contentPreview
87
+ content,
88
+ parts: uiMessage.parts
104
89
  });
105
90
  }
106
91
  /**
107
92
  * Emit message sent event
108
93
  */
109
94
  messageSent(messageId, content) {
110
- this.emitEvent("client:message-sent", {
95
+ this.emitEvent("text:message:created", {
96
+ messageId,
97
+ role: "user",
98
+ content
99
+ });
100
+ this.emitEvent("text:message:user", {
111
101
  messageId,
102
+ role: "user",
112
103
  content
113
104
  });
114
105
  }
@@ -130,13 +121,13 @@ class ChatClientEventEmitter {
130
121
  * Emit messages cleared event
131
122
  */
132
123
  messagesCleared() {
133
- this.emitEvent("client:messages-cleared");
124
+ this.emitEvent("client:messages:cleared");
134
125
  }
135
126
  /**
136
127
  * Emit tool result added event
137
128
  */
138
129
  toolResultAdded(toolCallId, toolName, output, state) {
139
- this.emitEvent("tool:result-added", {
130
+ this.emitEvent("tools:result:added", {
140
131
  toolCallId,
141
132
  toolName,
142
133
  output,
@@ -147,7 +138,7 @@ class ChatClientEventEmitter {
147
138
  * Emit tool approval responded event
148
139
  */
149
140
  toolApprovalResponded(approvalId, toolCallId, approved) {
150
- this.emitEvent("tool:approval-responded", {
141
+ this.emitEvent("tools:approval:responded", {
151
142
  approvalId,
152
143
  toolCallId,
153
144
  approved
@@ -159,10 +150,11 @@ class DefaultChatClientEventEmitter extends ChatClientEventEmitter {
159
150
  * Emit an event with automatic clientId and timestamp for client/tool events
160
151
  */
161
152
  emitEvent(eventName, data) {
162
- if (eventName.startsWith("client:") || eventName.startsWith("tool:")) {
153
+ if (eventName.startsWith("client:") || eventName.startsWith("tools:") || eventName.startsWith("text:")) {
163
154
  aiEventClient.emit(eventName, {
164
155
  ...data,
165
156
  clientId: this.clientId,
157
+ source: "client",
166
158
  timestamp: Date.now()
167
159
  });
168
160
  } else {
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sources":["../../src/events.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai/event-client'\nimport type { UIMessage } from './types'\n\n/**\n * Abstract base class for ChatClient event emission\n */\nexport abstract class ChatClientEventEmitter {\n protected clientId: string\n\n constructor(clientId: string) {\n this.clientId = clientId\n }\n\n /**\n * Protected abstract method for emitting events\n * Implementations should handle adding clientId and timestamp\n */\n protected abstract emitEvent(\n eventName: string,\n data?: Record<string, any>,\n ): void\n\n /**\n * Emit client created event\n */\n clientCreated(initialMessageCount: number): void {\n this.emitEvent('client:created', {\n initialMessageCount,\n })\n }\n\n /**\n * Emit loading state changed event\n */\n loadingChanged(isLoading: boolean): void {\n this.emitEvent('client:loading-changed', { isLoading })\n }\n\n /**\n * Emit error state changed event\n */\n errorChanged(error: string | null): void {\n this.emitEvent('client:error-changed', {\n error,\n })\n }\n\n /**\n * Emit text update events (combines processor and client events)\n */\n textUpdated(streamId: string, messageId: string, content: string): void {\n this.emitEvent('processor:text-updated', {\n streamId,\n content,\n })\n\n this.emitEvent('client:assistant-message-updated', {\n messageId,\n content,\n })\n }\n\n /**\n * Emit tool call state change events (combines processor and client events)\n */\n toolCallStateChanged(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n state: string,\n args: string,\n ): void {\n this.emitEvent('processor:tool-call-state-changed', {\n streamId,\n toolCallId,\n toolName,\n state,\n arguments: args,\n })\n\n this.emitEvent('client:tool-call-updated', {\n messageId,\n toolCallId,\n toolName,\n state,\n arguments: args,\n })\n }\n\n /**\n * Emit tool result state change event\n */\n toolResultStateChanged(\n streamId: string,\n toolCallId: string,\n content: string,\n state: string,\n error?: string,\n ): void {\n this.emitEvent('processor:tool-result-state-changed', {\n streamId,\n toolCallId,\n content,\n state,\n error,\n })\n }\n\n /**\n * Emit thinking update event\n */\n thinkingUpdated(\n streamId: string,\n messageId: string,\n content: string,\n delta?: string,\n ): void {\n this.emitEvent('stream:chunk:thinking', {\n streamId,\n messageId,\n content,\n delta,\n })\n }\n\n /**\n * Emit approval requested event\n */\n approvalRequested(\n messageId: string,\n toolCallId: string,\n toolName: string,\n input: any,\n approvalId: string,\n ): void {\n this.emitEvent('client:approval-requested', {\n messageId,\n toolCallId,\n toolName,\n input,\n approvalId,\n })\n }\n\n /**\n * Emit message appended event\n */\n messageAppended(uiMessage: UIMessage): void {\n const contentPreview = uiMessage.parts\n .filter((p) => p.type === 'text')\n .map((p) => (p as any).content)\n .join(' ')\n .substring(0, 100)\n\n this.emitEvent('client:message-appended', {\n messageId: uiMessage.id,\n role: uiMessage.role,\n contentPreview,\n })\n }\n\n /**\n * Emit message sent event\n */\n messageSent(messageId: string, content: string): void {\n this.emitEvent('client:message-sent', {\n messageId,\n content,\n })\n }\n\n /**\n * Emit reloaded event\n */\n reloaded(fromMessageIndex: number): void {\n this.emitEvent('client:reloaded', {\n fromMessageIndex,\n })\n }\n\n /**\n * Emit stopped event\n */\n stopped(): void {\n this.emitEvent('client:stopped')\n }\n\n /**\n * Emit messages cleared event\n */\n messagesCleared(): void {\n this.emitEvent('client:messages-cleared')\n }\n\n /**\n * Emit tool result added event\n */\n toolResultAdded(\n toolCallId: string,\n toolName: string,\n output: any,\n state: string,\n ): void {\n this.emitEvent('tool:result-added', {\n toolCallId,\n toolName,\n output,\n state,\n })\n }\n\n /**\n * Emit tool approval responded event\n */\n toolApprovalResponded(\n approvalId: string,\n toolCallId: string,\n approved: boolean,\n ): void {\n this.emitEvent('tool:approval-responded', {\n approvalId,\n toolCallId,\n approved,\n })\n }\n}\n\n/**\n * Default implementation of ChatClientEventEmitter\n */\nexport class DefaultChatClientEventEmitter extends ChatClientEventEmitter {\n /**\n * Emit an event with automatic clientId and timestamp for client/tool events\n */\n protected emitEvent(eventName: string, data?: Record<string, any>): void {\n // For client:* and tool:* events, automatically add clientId and timestamp\n if (eventName.startsWith('client:') || eventName.startsWith('tool:')) {\n aiEventClient.emit(eventName as any, {\n ...data,\n clientId: this.clientId,\n timestamp: Date.now(),\n })\n } else {\n // For other events (e.g., processor:*), just add timestamp\n aiEventClient.emit(eventName as any, {\n ...data,\n timestamp: Date.now(),\n })\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.js","sources":["../../src/events.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai/event-client'\nimport type { UIMessage } from './types'\n\n/**\n * Abstract base class for ChatClient event emission\n */\nexport abstract class ChatClientEventEmitter {\n protected clientId: string\n\n constructor(clientId: string) {\n this.clientId = clientId\n }\n\n /**\n * Protected abstract method for emitting events\n * Implementations should handle adding clientId and timestamp\n */\n protected abstract emitEvent(\n eventName: string,\n data?: Record<string, unknown>,\n ): void\n\n /**\n * Emit client created event\n */\n clientCreated(initialMessageCount: number): void {\n this.emitEvent('client:created', {\n initialMessageCount,\n })\n }\n\n /**\n * Emit loading state changed event\n */\n loadingChanged(isLoading: boolean): void {\n this.emitEvent('client:loading:changed', { isLoading })\n }\n\n /**\n * Emit error state changed event\n */\n errorChanged(error: string | null): void {\n this.emitEvent('client:error:changed', {\n error,\n })\n }\n\n /**\n * Emit text update events (combines processor and client events)\n */\n textUpdated(streamId: string, messageId: string, content: string): void {\n this.emitEvent('text:chunk:content', {\n streamId,\n messageId,\n content,\n })\n }\n\n /**\n * Emit tool call state change events (combines processor and client events)\n */\n toolCallStateChanged(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n state: string,\n args: string,\n ): void {\n this.emitEvent('tools:call:updated', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n state,\n arguments: args,\n })\n }\n\n /**\n * Emit tool result state change event\n */\n /**\n * Emit thinking update event\n */\n thinkingUpdated(\n streamId: string,\n messageId: string,\n content: string,\n delta?: string,\n ): void {\n this.emitEvent('text:chunk:thinking', {\n streamId,\n messageId,\n content,\n delta,\n })\n }\n\n /**\n * Emit approval requested event\n */\n approvalRequested(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n input: unknown,\n approvalId: string,\n ): void {\n this.emitEvent('tools:approval:requested', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n input,\n approvalId,\n })\n }\n\n /**\n * Emit message appended event\n */\n messageAppended(uiMessage: UIMessage, streamId?: string): void {\n const content = uiMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => part.content)\n .join(' ')\n\n this.emitEvent('text:message:created', {\n streamId,\n messageId: uiMessage.id,\n role: uiMessage.role,\n content,\n parts: uiMessage.parts,\n })\n }\n\n /**\n * Emit message sent event\n */\n messageSent(messageId: string, content: string): void {\n this.emitEvent('text:message:created', {\n messageId,\n role: 'user',\n content,\n })\n\n this.emitEvent('text:message:user', {\n messageId,\n role: 'user',\n content,\n })\n }\n\n /**\n * Emit reloaded event\n */\n reloaded(fromMessageIndex: number): void {\n this.emitEvent('client:reloaded', {\n fromMessageIndex,\n })\n }\n\n /**\n * Emit stopped event\n */\n stopped(): void {\n this.emitEvent('client:stopped')\n }\n\n /**\n * Emit messages cleared event\n */\n messagesCleared(): void {\n this.emitEvent('client:messages:cleared')\n }\n\n /**\n * Emit tool result added event\n */\n toolResultAdded(\n toolCallId: string,\n toolName: string,\n output: unknown,\n state: string,\n ): void {\n this.emitEvent('tools:result:added', {\n toolCallId,\n toolName,\n output,\n state,\n })\n }\n\n /**\n * Emit tool approval responded event\n */\n toolApprovalResponded(\n approvalId: string,\n toolCallId: string,\n approved: boolean,\n ): void {\n this.emitEvent('tools:approval:responded', {\n approvalId,\n toolCallId,\n approved,\n })\n }\n}\n\n/**\n * Default implementation of ChatClientEventEmitter\n */\nexport class DefaultChatClientEventEmitter extends ChatClientEventEmitter {\n /**\n * Emit an event with automatic clientId and timestamp for client/tool events\n */\n protected emitEvent(eventName: string, data?: Record<string, any>): void {\n // For client:* and tool:* events, automatically add clientId and timestamp\n if (\n eventName.startsWith('client:') ||\n eventName.startsWith('tools:') ||\n eventName.startsWith('text:')\n ) {\n aiEventClient.emit(eventName as any, {\n ...data,\n clientId: this.clientId,\n source: 'client',\n timestamp: Date.now(),\n })\n } else {\n // For other events, just add timestamp\n aiEventClient.emit(eventName as any, {\n ...data,\n timestamp: Date.now(),\n })\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -5,4 +5,4 @@ export type { ExtractToolNames, ExtractToolInput, ExtractToolOutput, } from './t
5
5
  export type { AnyClientTool } from '@tanstack/ai';
6
6
  export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, type ConnectionAdapter, type FetchConnectionOptions, } from './connection-adapters.js';
7
7
  export { uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, } from '@tanstack/ai';
8
- export { StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, type ChunkStrategy, type StreamProcessorOptions, type StreamProcessorHandlers, type StreamProcessorEvents, type InternalToolCallState, type ToolCallState, type ToolResultState, type JSONParser, type ChunkRecording, type ProcessorResult, type ProcessorState, } from '@tanstack/ai';
8
+ export { StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, type ChunkStrategy, type StreamProcessorOptions, type StreamProcessorEvents, type InternalToolCallState, type ToolCallState, type ToolResultState, type JSONParser, type ChunkRecording, type ProcessorResult, type ProcessorState, } from '@tanstack/ai';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-client",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic headless client for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "src"
32
32
  ],
33
33
  "dependencies": {
34
- "@tanstack/ai": "0.2.2"
34
+ "@tanstack/ai": "0.3.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitest/coverage-v8": "4.0.14",
@@ -160,13 +160,16 @@ export class ChatClient {
160
160
  input: any
161
161
  approvalId: string
162
162
  }) => {
163
- this.events.approvalRequested(
164
- this.currentMessageId || '',
165
- args.toolCallId,
166
- args.toolName,
167
- args.input,
168
- args.approvalId,
169
- )
163
+ if (this.currentStreamId) {
164
+ this.events.approvalRequested(
165
+ this.currentStreamId,
166
+ this.currentMessageId || '',
167
+ args.toolCallId,
168
+ args.toolName,
169
+ args.input,
170
+ args.approvalId,
171
+ )
172
+ }
170
173
  },
171
174
  },
172
175
  })
@@ -210,7 +213,10 @@ export class ChatClient {
210
213
  parts: [],
211
214
  createdAt: new Date(),
212
215
  }
213
- this.events.messageAppended(assistantMessage)
216
+ this.events.messageAppended(
217
+ assistantMessage,
218
+ this.currentStreamId || undefined,
219
+ )
214
220
 
215
221
  // Process each chunk
216
222
  for await (const chunk of source) {
package/src/events.ts CHANGED
@@ -17,7 +17,7 @@ export abstract class ChatClientEventEmitter {
17
17
  */
18
18
  protected abstract emitEvent(
19
19
  eventName: string,
20
- data?: Record<string, any>,
20
+ data?: Record<string, unknown>,
21
21
  ): void
22
22
 
23
23
  /**
@@ -33,14 +33,14 @@ export abstract class ChatClientEventEmitter {
33
33
  * Emit loading state changed event
34
34
  */
35
35
  loadingChanged(isLoading: boolean): void {
36
- this.emitEvent('client:loading-changed', { isLoading })
36
+ this.emitEvent('client:loading:changed', { isLoading })
37
37
  }
38
38
 
39
39
  /**
40
40
  * Emit error state changed event
41
41
  */
42
42
  errorChanged(error: string | null): void {
43
- this.emitEvent('client:error-changed', {
43
+ this.emitEvent('client:error:changed', {
44
44
  error,
45
45
  })
46
46
  }
@@ -49,12 +49,8 @@ export abstract class ChatClientEventEmitter {
49
49
  * Emit text update events (combines processor and client events)
50
50
  */
51
51
  textUpdated(streamId: string, messageId: string, content: string): void {
52
- this.emitEvent('processor:text-updated', {
52
+ this.emitEvent('text:chunk:content', {
53
53
  streamId,
54
- content,
55
- })
56
-
57
- this.emitEvent('client:assistant-message-updated', {
58
54
  messageId,
59
55
  content,
60
56
  })
@@ -71,15 +67,8 @@ export abstract class ChatClientEventEmitter {
71
67
  state: string,
72
68
  args: string,
73
69
  ): void {
74
- this.emitEvent('processor:tool-call-state-changed', {
70
+ this.emitEvent('tools:call:updated', {
75
71
  streamId,
76
- toolCallId,
77
- toolName,
78
- state,
79
- arguments: args,
80
- })
81
-
82
- this.emitEvent('client:tool-call-updated', {
83
72
  messageId,
84
73
  toolCallId,
85
74
  toolName,
@@ -91,22 +80,6 @@ export abstract class ChatClientEventEmitter {
91
80
  /**
92
81
  * Emit tool result state change event
93
82
  */
94
- toolResultStateChanged(
95
- streamId: string,
96
- toolCallId: string,
97
- content: string,
98
- state: string,
99
- error?: string,
100
- ): void {
101
- this.emitEvent('processor:tool-result-state-changed', {
102
- streamId,
103
- toolCallId,
104
- content,
105
- state,
106
- error,
107
- })
108
- }
109
-
110
83
  /**
111
84
  * Emit thinking update event
112
85
  */
@@ -116,7 +89,7 @@ export abstract class ChatClientEventEmitter {
116
89
  content: string,
117
90
  delta?: string,
118
91
  ): void {
119
- this.emitEvent('stream:chunk:thinking', {
92
+ this.emitEvent('text:chunk:thinking', {
120
93
  streamId,
121
94
  messageId,
122
95
  content,
@@ -128,13 +101,15 @@ export abstract class ChatClientEventEmitter {
128
101
  * Emit approval requested event
129
102
  */
130
103
  approvalRequested(
104
+ streamId: string,
131
105
  messageId: string,
132
106
  toolCallId: string,
133
107
  toolName: string,
134
- input: any,
108
+ input: unknown,
135
109
  approvalId: string,
136
110
  ): void {
137
- this.emitEvent('client:approval-requested', {
111
+ this.emitEvent('tools:approval:requested', {
112
+ streamId,
138
113
  messageId,
139
114
  toolCallId,
140
115
  toolName,
@@ -146,17 +121,18 @@ export abstract class ChatClientEventEmitter {
146
121
  /**
147
122
  * Emit message appended event
148
123
  */
149
- messageAppended(uiMessage: UIMessage): void {
150
- const contentPreview = uiMessage.parts
151
- .filter((p) => p.type === 'text')
152
- .map((p) => (p as any).content)
124
+ messageAppended(uiMessage: UIMessage, streamId?: string): void {
125
+ const content = uiMessage.parts
126
+ .filter((part) => part.type === 'text')
127
+ .map((part) => part.content)
153
128
  .join(' ')
154
- .substring(0, 100)
155
129
 
156
- this.emitEvent('client:message-appended', {
130
+ this.emitEvent('text:message:created', {
131
+ streamId,
157
132
  messageId: uiMessage.id,
158
133
  role: uiMessage.role,
159
- contentPreview,
134
+ content,
135
+ parts: uiMessage.parts,
160
136
  })
161
137
  }
162
138
 
@@ -164,8 +140,15 @@ export abstract class ChatClientEventEmitter {
164
140
  * Emit message sent event
165
141
  */
166
142
  messageSent(messageId: string, content: string): void {
167
- this.emitEvent('client:message-sent', {
143
+ this.emitEvent('text:message:created', {
144
+ messageId,
145
+ role: 'user',
146
+ content,
147
+ })
148
+
149
+ this.emitEvent('text:message:user', {
168
150
  messageId,
151
+ role: 'user',
169
152
  content,
170
153
  })
171
154
  }
@@ -190,7 +173,7 @@ export abstract class ChatClientEventEmitter {
190
173
  * Emit messages cleared event
191
174
  */
192
175
  messagesCleared(): void {
193
- this.emitEvent('client:messages-cleared')
176
+ this.emitEvent('client:messages:cleared')
194
177
  }
195
178
 
196
179
  /**
@@ -199,10 +182,10 @@ export abstract class ChatClientEventEmitter {
199
182
  toolResultAdded(
200
183
  toolCallId: string,
201
184
  toolName: string,
202
- output: any,
185
+ output: unknown,
203
186
  state: string,
204
187
  ): void {
205
- this.emitEvent('tool:result-added', {
188
+ this.emitEvent('tools:result:added', {
206
189
  toolCallId,
207
190
  toolName,
208
191
  output,
@@ -218,7 +201,7 @@ export abstract class ChatClientEventEmitter {
218
201
  toolCallId: string,
219
202
  approved: boolean,
220
203
  ): void {
221
- this.emitEvent('tool:approval-responded', {
204
+ this.emitEvent('tools:approval:responded', {
222
205
  approvalId,
223
206
  toolCallId,
224
207
  approved,
@@ -235,14 +218,19 @@ export class DefaultChatClientEventEmitter extends ChatClientEventEmitter {
235
218
  */
236
219
  protected emitEvent(eventName: string, data?: Record<string, any>): void {
237
220
  // For client:* and tool:* events, automatically add clientId and timestamp
238
- if (eventName.startsWith('client:') || eventName.startsWith('tool:')) {
221
+ if (
222
+ eventName.startsWith('client:') ||
223
+ eventName.startsWith('tools:') ||
224
+ eventName.startsWith('text:')
225
+ ) {
239
226
  aiEventClient.emit(eventName as any, {
240
227
  ...data,
241
228
  clientId: this.clientId,
229
+ source: 'client',
242
230
  timestamp: Date.now(),
243
231
  })
244
232
  } else {
245
- // For other events (e.g., processor:*), just add timestamp
233
+ // For other events, just add timestamp
246
234
  aiEventClient.emit(eventName as any, {
247
235
  ...data,
248
236
  timestamp: Date.now(),
package/src/index.ts CHANGED
@@ -51,7 +51,6 @@ export {
51
51
  defaultJSONParser,
52
52
  type ChunkStrategy,
53
53
  type StreamProcessorOptions,
54
- type StreamProcessorHandlers,
55
54
  type StreamProcessorEvents,
56
55
  type InternalToolCallState,
57
56
  type ToolCallState,