@tanstack/ai-client 0.2.2 → 0.4.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.
@@ -1,4 +1,3 @@
1
- import { convertMessagesToModelMessages } from '@tanstack/ai'
2
1
  import type { ModelMessage, StreamChunk, UIMessage } from '@tanstack/ai'
3
2
 
4
3
  /**
@@ -138,16 +137,15 @@ export function fetchServerSentEvents(
138
137
  const resolvedOptions =
139
138
  typeof options === 'function' ? await options() : options
140
139
 
141
- const modelMessages = convertMessagesToModelMessages(messages)
142
-
143
140
  const requestHeaders: Record<string, string> = {
144
141
  'Content-Type': 'application/json',
145
142
  ...mergeHeaders(resolvedOptions.headers),
146
143
  }
147
144
 
148
- // Merge body from options with messages and data
145
+ // Send messages as-is (UIMessages with parts preserved)
146
+ // Server-side TextEngine handles conversion to ModelMessages
149
147
  const requestBody = {
150
- messages: modelMessages,
148
+ messages,
151
149
  data,
152
150
  ...resolvedOptions.body,
153
151
  }
@@ -238,17 +236,15 @@ export function fetchHttpStream(
238
236
  const resolvedOptions =
239
237
  typeof options === 'function' ? await options() : options
240
238
 
241
- // Convert UIMessages to ModelMessages if needed
242
- const modelMessages = convertMessagesToModelMessages(messages)
243
-
244
239
  const requestHeaders: Record<string, string> = {
245
240
  'Content-Type': 'application/json',
246
241
  ...mergeHeaders(resolvedOptions.headers),
247
242
  }
248
243
 
249
- // Merge body from options with messages and data
244
+ // Send messages as-is (UIMessages with parts preserved)
245
+ // Server-side TextEngine handles conversion to ModelMessages
250
246
  const requestBody = {
251
- messages: modelMessages,
247
+ messages,
252
248
  data,
253
249
  ...resolvedOptions.body,
254
250
  }
@@ -302,14 +298,15 @@ export function fetchHttpStream(
302
298
  */
303
299
  export function stream(
304
300
  streamFactory: (
305
- messages: Array<ModelMessage>,
301
+ messages: Array<UIMessage> | Array<ModelMessage>,
306
302
  data?: Record<string, any>,
307
303
  ) => AsyncIterable<StreamChunk>,
308
304
  ): ConnectionAdapter {
309
305
  return {
310
306
  async *connect(messages, data) {
311
- const modelMessages = convertMessagesToModelMessages(messages)
312
- yield* streamFactory(modelMessages, data)
307
+ // Pass messages as-is (UIMessages with parts preserved)
308
+ // Server-side chat() handles conversion to ModelMessages
309
+ yield* streamFactory(messages, data)
313
310
  },
314
311
  }
315
312
  }
@@ -332,16 +329,15 @@ export function stream(
332
329
  */
333
330
  export function rpcStream(
334
331
  rpcCall: (
335
- messages: Array<ModelMessage>,
332
+ messages: Array<UIMessage> | Array<ModelMessage>,
336
333
  data?: Record<string, any>,
337
334
  ) => AsyncIterable<StreamChunk>,
338
335
  ): ConnectionAdapter {
339
336
  return {
340
337
  async *connect(messages, data) {
341
- const modelMessages = convertMessagesToModelMessages(messages)
342
- // Simply yield from the RPC call
343
- // The RPC layer handles WebSocket transport
344
- yield* rpcCall(modelMessages, data)
338
+ // Pass messages as-is (UIMessages with parts preserved)
339
+ // Server-side chat() handles conversion to ModelMessages
340
+ yield* rpcCall(messages, data)
345
341
  },
346
342
  }
347
343
  }
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
@@ -11,6 +11,7 @@ export type {
11
11
  ChatClientOptions,
12
12
  ChatRequestBody,
13
13
  InferChatMessages,
14
+ ChatClientState,
14
15
  } from './types'
15
16
  export { clientTools, createChatClientOptions } from './types'
16
17
  export type {
@@ -51,7 +52,6 @@ export {
51
52
  defaultJSONParser,
52
53
  type ChunkStrategy,
53
54
  type StreamProcessorOptions,
54
- type StreamProcessorHandlers,
55
55
  type StreamProcessorEvents,
56
56
  type InternalToolCallState,
57
57
  type ToolCallState,
package/src/types.ts CHANGED
@@ -26,6 +26,11 @@ export type ToolResultState =
26
26
  | 'complete' // Result is complete
27
27
  | 'error' // Error occurred
28
28
 
29
+ /**
30
+ * ChatClient state - track the lifecycle of a chat
31
+ */
32
+ export type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error'
33
+
29
34
  /**
30
35
  * Message parts - building blocks of UIMessage
31
36
  */
@@ -191,6 +196,11 @@ export interface ChatClientOptions<
191
196
  */
192
197
  onErrorChange?: (error: Error | undefined) => void
193
198
 
199
+ /**
200
+ * Callback when chat status changes
201
+ */
202
+ onStatusChange?: (status: ChatClientState) => void
203
+
194
204
  /**
195
205
  * Client-side tools with execution logic
196
206
  * When provided, tools with execute functions will be called automatically