@tanstack/ai-client 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/esm/chat-client.d.ts +102 -0
- package/dist/esm/chat-client.js +375 -0
- package/dist/esm/chat-client.js.map +1 -0
- package/dist/esm/connection-adapters.d.ts +124 -0
- package/dist/esm/connection-adapters.js +149 -0
- package/dist/esm/connection-adapters.js.map +1 -0
- package/dist/esm/events.d.ts +82 -0
- package/dist/esm/events.js +180 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/tool-types.d.ts +20 -0
- package/dist/esm/types.d.ts +210 -0
- package/dist/esm/types.js +11 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +53 -0
- package/src/chat-client.ts +522 -0
- package/src/connection-adapters.ts +344 -0
- package/src/events.ts +252 -0
- package/src/index.ts +63 -0
- package/src/tool-types.ts +41 -0
- package/src/types.ts +276 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StreamProcessor,
|
|
3
|
+
generateMessageId,
|
|
4
|
+
normalizeToUIMessage,
|
|
5
|
+
} from '@tanstack/ai'
|
|
6
|
+
import { DefaultChatClientEventEmitter } from './events'
|
|
7
|
+
import type {
|
|
8
|
+
ChatClientOptions,
|
|
9
|
+
MessagePart,
|
|
10
|
+
ToolCallPart,
|
|
11
|
+
UIMessage,
|
|
12
|
+
} from './types'
|
|
13
|
+
import type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'
|
|
14
|
+
import type { ConnectionAdapter } from './connection-adapters'
|
|
15
|
+
import type { ChatClientEventEmitter } from './events'
|
|
16
|
+
|
|
17
|
+
export class ChatClient {
|
|
18
|
+
private processor: StreamProcessor
|
|
19
|
+
private connection: ConnectionAdapter
|
|
20
|
+
private uniqueId: string
|
|
21
|
+
private body: Record<string, any> = {}
|
|
22
|
+
private isLoading = false
|
|
23
|
+
private error: Error | undefined = undefined
|
|
24
|
+
private abortController: AbortController | null = null
|
|
25
|
+
private events: ChatClientEventEmitter
|
|
26
|
+
private clientToolsRef: { current: Map<string, AnyClientTool> }
|
|
27
|
+
private currentStreamId: string | null = null
|
|
28
|
+
private currentMessageId: string | null = null
|
|
29
|
+
|
|
30
|
+
private callbacksRef: {
|
|
31
|
+
current: {
|
|
32
|
+
onResponse: (response?: Response) => void | Promise<void>
|
|
33
|
+
onChunk: (chunk: StreamChunk) => void
|
|
34
|
+
onFinish: (message: UIMessage) => void
|
|
35
|
+
onError: (error: Error) => void
|
|
36
|
+
onMessagesChange: (messages: Array<UIMessage>) => void
|
|
37
|
+
onLoadingChange: (isLoading: boolean) => void
|
|
38
|
+
onErrorChange: (error: Error | undefined) => void
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
constructor(options: ChatClientOptions) {
|
|
43
|
+
this.uniqueId = options.id || this.generateUniqueId('chat')
|
|
44
|
+
this.body = options.body || {}
|
|
45
|
+
this.connection = options.connection
|
|
46
|
+
this.events = new DefaultChatClientEventEmitter(this.uniqueId)
|
|
47
|
+
|
|
48
|
+
// Build client tools map
|
|
49
|
+
this.clientToolsRef = { current: new Map() }
|
|
50
|
+
if (options.tools) {
|
|
51
|
+
for (const tool of options.tools) {
|
|
52
|
+
this.clientToolsRef.current.set(tool.name, tool)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.callbacksRef = {
|
|
57
|
+
current: {
|
|
58
|
+
onResponse: options.onResponse || (() => {}),
|
|
59
|
+
onChunk: options.onChunk || (() => {}),
|
|
60
|
+
onFinish: options.onFinish || (() => {}),
|
|
61
|
+
onError: options.onError || (() => {}),
|
|
62
|
+
onMessagesChange: options.onMessagesChange || (() => {}),
|
|
63
|
+
onLoadingChange: options.onLoadingChange || (() => {}),
|
|
64
|
+
onErrorChange: options.onErrorChange || (() => {}),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create StreamProcessor with event handlers
|
|
69
|
+
this.processor = new StreamProcessor({
|
|
70
|
+
chunkStrategy: options.streamProcessor?.chunkStrategy,
|
|
71
|
+
initialMessages: options.initialMessages,
|
|
72
|
+
events: {
|
|
73
|
+
onMessagesChange: (messages: Array<UIMessage>) => {
|
|
74
|
+
this.callbacksRef.current.onMessagesChange(messages)
|
|
75
|
+
},
|
|
76
|
+
onStreamStart: () => {
|
|
77
|
+
// Stream started
|
|
78
|
+
},
|
|
79
|
+
onStreamEnd: (message: UIMessage) => {
|
|
80
|
+
this.callbacksRef.current.onFinish(message)
|
|
81
|
+
},
|
|
82
|
+
onError: (error: Error) => {
|
|
83
|
+
this.setError(error)
|
|
84
|
+
this.callbacksRef.current.onError(error)
|
|
85
|
+
},
|
|
86
|
+
onTextUpdate: (messageId: string, content: string) => {
|
|
87
|
+
// Emit text update to devtools
|
|
88
|
+
if (this.currentStreamId) {
|
|
89
|
+
this.events.textUpdated(this.currentStreamId, messageId, content)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
onThinkingUpdate: (messageId: string, content: string) => {
|
|
93
|
+
// Emit thinking update to devtools
|
|
94
|
+
if (this.currentStreamId) {
|
|
95
|
+
this.events.thinkingUpdated(
|
|
96
|
+
this.currentStreamId,
|
|
97
|
+
messageId,
|
|
98
|
+
content,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
onToolCallStateChange: (
|
|
103
|
+
messageId: string,
|
|
104
|
+
toolCallId: string,
|
|
105
|
+
state: string,
|
|
106
|
+
args: string,
|
|
107
|
+
) => {
|
|
108
|
+
// Get the tool name from the messages
|
|
109
|
+
const messages = this.processor.getMessages()
|
|
110
|
+
const message = messages.find((m: UIMessage) => m.id === messageId)
|
|
111
|
+
const toolCallPart = message?.parts.find(
|
|
112
|
+
(p: MessagePart): p is ToolCallPart =>
|
|
113
|
+
p.type === 'tool-call' && p.id === toolCallId,
|
|
114
|
+
)
|
|
115
|
+
const toolName = toolCallPart?.name || 'unknown'
|
|
116
|
+
|
|
117
|
+
// Emit tool call state change to devtools
|
|
118
|
+
if (this.currentStreamId) {
|
|
119
|
+
this.events.toolCallStateChanged(
|
|
120
|
+
this.currentStreamId,
|
|
121
|
+
messageId,
|
|
122
|
+
toolCallId,
|
|
123
|
+
toolName,
|
|
124
|
+
state,
|
|
125
|
+
args,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
onToolCall: async (args: {
|
|
130
|
+
toolCallId: string
|
|
131
|
+
toolName: string
|
|
132
|
+
input: any
|
|
133
|
+
}) => {
|
|
134
|
+
// Handle client-side tool execution automatically
|
|
135
|
+
const clientTool = this.clientToolsRef.current.get(args.toolName)
|
|
136
|
+
if (clientTool?.execute) {
|
|
137
|
+
try {
|
|
138
|
+
const output = await clientTool.execute(args.input)
|
|
139
|
+
await this.addToolResult({
|
|
140
|
+
toolCallId: args.toolCallId,
|
|
141
|
+
tool: args.toolName,
|
|
142
|
+
output,
|
|
143
|
+
state: 'output-available',
|
|
144
|
+
})
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
await this.addToolResult({
|
|
147
|
+
toolCallId: args.toolCallId,
|
|
148
|
+
tool: args.toolName,
|
|
149
|
+
output: null,
|
|
150
|
+
state: 'output-error',
|
|
151
|
+
errorText: error.message,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
onApprovalRequest: (args: {
|
|
157
|
+
toolCallId: string
|
|
158
|
+
toolName: string
|
|
159
|
+
input: any
|
|
160
|
+
approvalId: string
|
|
161
|
+
}) => {
|
|
162
|
+
this.events.approvalRequested(
|
|
163
|
+
this.currentMessageId || '',
|
|
164
|
+
args.toolCallId,
|
|
165
|
+
args.toolName,
|
|
166
|
+
args.input,
|
|
167
|
+
args.approvalId,
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
this.events.clientCreated(this.processor.getMessages().length)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private generateUniqueId(prefix: string): string {
|
|
177
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private setIsLoading(isLoading: boolean): void {
|
|
181
|
+
this.isLoading = isLoading
|
|
182
|
+
this.callbacksRef.current.onLoadingChange(isLoading)
|
|
183
|
+
this.events.loadingChanged(isLoading)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private setError(error: Error | undefined): void {
|
|
187
|
+
this.error = error
|
|
188
|
+
this.callbacksRef.current.onErrorChange(error)
|
|
189
|
+
this.events.errorChanged(error?.message || null)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Process a stream through the StreamProcessor
|
|
194
|
+
*/
|
|
195
|
+
private async processStream(
|
|
196
|
+
source: AsyncIterable<StreamChunk>,
|
|
197
|
+
): Promise<UIMessage> {
|
|
198
|
+
// Generate a stream ID for this streaming operation
|
|
199
|
+
this.currentStreamId = this.generateUniqueId('stream')
|
|
200
|
+
|
|
201
|
+
// Start a new assistant message
|
|
202
|
+
const messageId = this.processor.startAssistantMessage()
|
|
203
|
+
this.currentMessageId = messageId
|
|
204
|
+
|
|
205
|
+
// Emit message appended event for the new assistant message
|
|
206
|
+
const assistantMessage: UIMessage = {
|
|
207
|
+
id: messageId,
|
|
208
|
+
role: 'assistant',
|
|
209
|
+
parts: [],
|
|
210
|
+
createdAt: new Date(),
|
|
211
|
+
}
|
|
212
|
+
this.events.messageAppended(assistantMessage)
|
|
213
|
+
|
|
214
|
+
// Process each chunk
|
|
215
|
+
for await (const chunk of source) {
|
|
216
|
+
this.callbacksRef.current.onChunk(chunk)
|
|
217
|
+
this.processor.processChunk(chunk)
|
|
218
|
+
|
|
219
|
+
// Yield control back to event loop to allow UI updates
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Finalize the stream
|
|
224
|
+
this.processor.finalizeStream()
|
|
225
|
+
|
|
226
|
+
// Clear the current stream and message IDs
|
|
227
|
+
this.currentStreamId = null
|
|
228
|
+
this.currentMessageId = null
|
|
229
|
+
|
|
230
|
+
// Return the assistant message
|
|
231
|
+
const messages = this.processor.getMessages()
|
|
232
|
+
const finalAssistantMessage = messages.find(
|
|
233
|
+
(m: UIMessage) => m.id === messageId,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
finalAssistantMessage || {
|
|
238
|
+
id: messageId,
|
|
239
|
+
role: 'assistant',
|
|
240
|
+
parts: [],
|
|
241
|
+
createdAt: new Date(),
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Send a message and stream the response
|
|
248
|
+
*/
|
|
249
|
+
async sendMessage(content: string): Promise<void> {
|
|
250
|
+
if (!content.trim() || this.isLoading) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Add user message via processor
|
|
255
|
+
const userMessage = this.processor.addUserMessage(content.trim())
|
|
256
|
+
this.events.messageSent(userMessage.id, content.trim())
|
|
257
|
+
|
|
258
|
+
await this.streamResponse()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Append a message and stream the response
|
|
263
|
+
*/
|
|
264
|
+
async append(message: UIMessage | ModelMessage): Promise<void> {
|
|
265
|
+
// Normalize the message to ensure it has id and createdAt
|
|
266
|
+
const normalizedMessage = normalizeToUIMessage(message, generateMessageId)
|
|
267
|
+
|
|
268
|
+
// Skip system messages - they're handled via systemPrompts, not UIMessages
|
|
269
|
+
if (normalizedMessage.role === 'system') {
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Type assertion: after checking for system, we know it's user or assistant
|
|
274
|
+
const uiMessage = normalizedMessage as UIMessage
|
|
275
|
+
|
|
276
|
+
// Emit message appended event
|
|
277
|
+
this.events.messageAppended(uiMessage)
|
|
278
|
+
|
|
279
|
+
// Add to messages
|
|
280
|
+
const messages = this.processor.getMessages()
|
|
281
|
+
this.processor.setMessages([...messages, uiMessage])
|
|
282
|
+
|
|
283
|
+
await this.streamResponse()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Stream a response from the LLM
|
|
288
|
+
*/
|
|
289
|
+
private async streamResponse(): Promise<void> {
|
|
290
|
+
this.setIsLoading(true)
|
|
291
|
+
this.setError(undefined)
|
|
292
|
+
this.abortController = new AbortController()
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
// Get model messages for the LLM
|
|
296
|
+
const modelMessages = this.processor.toModelMessages()
|
|
297
|
+
|
|
298
|
+
// Call onResponse callback
|
|
299
|
+
await this.callbacksRef.current.onResponse()
|
|
300
|
+
|
|
301
|
+
// Include conversationId in the body for server-side event correlation
|
|
302
|
+
const bodyWithConversationId = {
|
|
303
|
+
...this.body,
|
|
304
|
+
conversationId: this.uniqueId,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Connect and stream
|
|
308
|
+
const stream = this.connection.connect(
|
|
309
|
+
modelMessages,
|
|
310
|
+
bodyWithConversationId,
|
|
311
|
+
this.abortController.signal,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
await this.processStream(stream)
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (err instanceof Error) {
|
|
317
|
+
if (err.name === 'AbortError') {
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
this.setError(err)
|
|
321
|
+
this.callbacksRef.current.onError(err)
|
|
322
|
+
}
|
|
323
|
+
} finally {
|
|
324
|
+
this.abortController = null
|
|
325
|
+
this.setIsLoading(false)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Reload the last assistant message
|
|
331
|
+
*/
|
|
332
|
+
async reload(): Promise<void> {
|
|
333
|
+
const messages = this.processor.getMessages()
|
|
334
|
+
if (messages.length === 0) return
|
|
335
|
+
|
|
336
|
+
// Find the last user message
|
|
337
|
+
const lastUserMessageIndex = messages.findLastIndex(
|
|
338
|
+
(m: UIMessage) => m.role === 'user',
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if (lastUserMessageIndex === -1) return
|
|
342
|
+
|
|
343
|
+
this.events.reloaded(lastUserMessageIndex)
|
|
344
|
+
|
|
345
|
+
// Remove all messages after the last user message
|
|
346
|
+
this.processor.removeMessagesAfter(lastUserMessageIndex)
|
|
347
|
+
|
|
348
|
+
// Resend
|
|
349
|
+
await this.streamResponse()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Stop the current stream
|
|
354
|
+
*/
|
|
355
|
+
stop(): void {
|
|
356
|
+
if (this.abortController) {
|
|
357
|
+
this.abortController.abort()
|
|
358
|
+
this.abortController = null
|
|
359
|
+
}
|
|
360
|
+
this.setIsLoading(false)
|
|
361
|
+
this.events.stopped()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Clear all messages
|
|
366
|
+
*/
|
|
367
|
+
clear(): void {
|
|
368
|
+
this.processor.clearMessages()
|
|
369
|
+
this.setError(undefined)
|
|
370
|
+
this.events.messagesCleared()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Add the result of a client-side tool execution
|
|
375
|
+
*/
|
|
376
|
+
async addToolResult(result: {
|
|
377
|
+
toolCallId: string
|
|
378
|
+
tool: string
|
|
379
|
+
output: any
|
|
380
|
+
state?: 'output-available' | 'output-error'
|
|
381
|
+
errorText?: string
|
|
382
|
+
}): Promise<void> {
|
|
383
|
+
this.events.toolResultAdded(
|
|
384
|
+
result.toolCallId,
|
|
385
|
+
result.tool,
|
|
386
|
+
result.output,
|
|
387
|
+
result.state || 'output-available',
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
// Add result via processor
|
|
391
|
+
this.processor.addToolResult(
|
|
392
|
+
result.toolCallId,
|
|
393
|
+
result.output,
|
|
394
|
+
result.errorText,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
// Check if we should auto-send
|
|
398
|
+
if (this.shouldAutoSend()) {
|
|
399
|
+
await this.continueFlow()
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Respond to a tool approval request
|
|
405
|
+
*/
|
|
406
|
+
async addToolApprovalResponse(response: {
|
|
407
|
+
id: string // approval.id, not toolCallId
|
|
408
|
+
approved: boolean
|
|
409
|
+
}): Promise<void> {
|
|
410
|
+
// Find the tool call ID from the approval ID
|
|
411
|
+
const messages = this.processor.getMessages()
|
|
412
|
+
let foundToolCallId: string | undefined
|
|
413
|
+
|
|
414
|
+
for (const msg of messages) {
|
|
415
|
+
const toolCallPart = msg.parts.find(
|
|
416
|
+
(p: MessagePart): p is ToolCallPart =>
|
|
417
|
+
p.type === 'tool-call' && p.approval?.id === response.id,
|
|
418
|
+
)
|
|
419
|
+
if (toolCallPart) {
|
|
420
|
+
foundToolCallId = toolCallPart.id
|
|
421
|
+
break
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (foundToolCallId) {
|
|
426
|
+
this.events.toolApprovalResponded(
|
|
427
|
+
response.id,
|
|
428
|
+
foundToolCallId,
|
|
429
|
+
response.approved,
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Add response via processor
|
|
434
|
+
this.processor.addToolApprovalResponse(response.id, response.approved)
|
|
435
|
+
|
|
436
|
+
// Check if we should auto-send
|
|
437
|
+
if (this.shouldAutoSend()) {
|
|
438
|
+
await this.continueFlow()
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Continue the agent flow with current messages
|
|
444
|
+
*/
|
|
445
|
+
private async continueFlow(): Promise<void> {
|
|
446
|
+
if (this.isLoading) return
|
|
447
|
+
await this.streamResponse()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Check if all tool calls are complete and we should auto-send
|
|
452
|
+
*/
|
|
453
|
+
private shouldAutoSend(): boolean {
|
|
454
|
+
return this.processor.areAllToolsComplete()
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Get current messages
|
|
459
|
+
*/
|
|
460
|
+
getMessages(): Array<UIMessage> {
|
|
461
|
+
return this.processor.getMessages()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get loading state
|
|
466
|
+
*/
|
|
467
|
+
getIsLoading(): boolean {
|
|
468
|
+
return this.isLoading
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get current error
|
|
473
|
+
*/
|
|
474
|
+
getError(): Error | undefined {
|
|
475
|
+
return this.error
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Manually set messages
|
|
480
|
+
*/
|
|
481
|
+
setMessagesManually(messages: Array<UIMessage>): void {
|
|
482
|
+
this.processor.setMessages(messages)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Update options refs (for use in React hooks to avoid recreating client)
|
|
487
|
+
*/
|
|
488
|
+
updateOptions(options: {
|
|
489
|
+
connection?: ConnectionAdapter
|
|
490
|
+
body?: Record<string, any>
|
|
491
|
+
tools?: ReadonlyArray<AnyClientTool>
|
|
492
|
+
onResponse?: (response?: Response) => void | Promise<void>
|
|
493
|
+
onChunk?: (chunk: StreamChunk) => void
|
|
494
|
+
onFinish?: (message: UIMessage) => void
|
|
495
|
+
onError?: (error: Error) => void
|
|
496
|
+
}): void {
|
|
497
|
+
if (options.connection !== undefined) {
|
|
498
|
+
this.connection = options.connection
|
|
499
|
+
}
|
|
500
|
+
if (options.body !== undefined) {
|
|
501
|
+
this.body = options.body
|
|
502
|
+
}
|
|
503
|
+
if (options.tools !== undefined) {
|
|
504
|
+
this.clientToolsRef.current = new Map()
|
|
505
|
+
for (const tool of options.tools) {
|
|
506
|
+
this.clientToolsRef.current.set(tool.name, tool)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (options.onResponse !== undefined) {
|
|
510
|
+
this.callbacksRef.current.onResponse = options.onResponse
|
|
511
|
+
}
|
|
512
|
+
if (options.onChunk !== undefined) {
|
|
513
|
+
this.callbacksRef.current.onChunk = options.onChunk
|
|
514
|
+
}
|
|
515
|
+
if (options.onFinish !== undefined) {
|
|
516
|
+
this.callbacksRef.current.onFinish = options.onFinish
|
|
517
|
+
}
|
|
518
|
+
if (options.onError !== undefined) {
|
|
519
|
+
this.callbacksRef.current.onError = options.onError
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|