@nextclaw/agent-chat 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,990 @@
1
+ // src/services/agent-chat-controller.ts
2
+ import { BehaviorSubject, Subject } from "rxjs";
3
+ import { v4 as v42 } from "uuid";
4
+
5
+ // src/types/agent.ts
6
+ var ToolInvocationStatus = /* @__PURE__ */ ((ToolInvocationStatus2) => {
7
+ ToolInvocationStatus2["CALL"] = "call";
8
+ ToolInvocationStatus2["RESULT"] = "result";
9
+ ToolInvocationStatus2["PARTIAL_CALL"] = "partial-call";
10
+ ToolInvocationStatus2["ERROR"] = "error";
11
+ ToolInvocationStatus2["CANCELLED"] = "cancelled";
12
+ return ToolInvocationStatus2;
13
+ })(ToolInvocationStatus || {});
14
+
15
+ // src/types/agent-event.ts
16
+ var EventType = /* @__PURE__ */ ((EventType2) => {
17
+ EventType2["TEXT_START"] = "TEXT_START";
18
+ EventType2["TEXT_DELTA"] = "TEXT_DELTA";
19
+ EventType2["TEXT_END"] = "TEXT_END";
20
+ EventType2["TOOL_CALL_START"] = "TOOL_CALL_START";
21
+ EventType2["TOOL_CALL_ARGS"] = "TOOL_CALL_ARGS";
22
+ EventType2["TOOL_CALL_ARGS_DELTA"] = "TOOL_CALL_ARGS_DELTA";
23
+ EventType2["TOOL_CALL_END"] = "TOOL_CALL_END";
24
+ EventType2["TOOL_CALL_RESULT"] = "TOOL_CALL_RESULT";
25
+ EventType2["RUN_STARTED"] = "RUN_STARTED";
26
+ EventType2["RUN_FINISHED"] = "RUN_FINISHED";
27
+ EventType2["RUN_ERROR"] = "RUN_ERROR";
28
+ EventType2["RUN_METADATA"] = "RUN_METADATA";
29
+ EventType2["REASONING_START"] = "REASONING_START";
30
+ EventType2["REASONING_DELTA"] = "REASONING_DELTA";
31
+ EventType2["REASONING_END"] = "REASONING_END";
32
+ return EventType2;
33
+ })(EventType || {});
34
+
35
+ // src/types/run-lifecycle.ts
36
+ function getStopDisabledReason(run) {
37
+ if (!run) return null;
38
+ if (run.remoteStopCapable && run.remoteRunId) return null;
39
+ if (run.remoteStopCapable) return "__preparing__";
40
+ return run.remoteStopReason ?? "";
41
+ }
42
+
43
+ // src/utils/ui-message.ts
44
+ var tryParseJson = (jsonString) => {
45
+ try {
46
+ return JSON.parse(jsonString);
47
+ } catch {
48
+ return void 0;
49
+ }
50
+ };
51
+ var toolCallToToolInvocation = (toolCall) => {
52
+ return {
53
+ toolCallId: toolCall.id,
54
+ toolName: toolCall.function.name,
55
+ args: toolCall.function.arguments,
56
+ parsedArgs: tryParseJson(toolCall.function.arguments),
57
+ status: "call" /* CALL */
58
+ };
59
+ };
60
+ function finalizePendingToolInvocations(messages, options) {
61
+ const stub = options?.stubResult ?? {
62
+ error: "tool_call_interrupted",
63
+ note: "User continued before tool produced a result."
64
+ };
65
+ return messages.map((msg) => {
66
+ if (!msg.parts?.length) return msg;
67
+ return {
68
+ ...msg,
69
+ parts: msg.parts.map((part) => {
70
+ if (part.type !== "tool-invocation") return part;
71
+ if (!["call" /* CALL */, "partial-call" /* PARTIAL_CALL */].includes(part.toolInvocation.status)) return part;
72
+ let parsedArgs;
73
+ if (typeof part.toolInvocation.args === "string") {
74
+ try {
75
+ parsedArgs = JSON.parse(part.toolInvocation.args);
76
+ } catch {
77
+ return {
78
+ ...part,
79
+ toolInvocation: {
80
+ ...part.toolInvocation,
81
+ args: JSON.stringify({}),
82
+ parsedArgs,
83
+ status: "result" /* RESULT */,
84
+ result: { error: "invalid_args", raw: part.toolInvocation.args }
85
+ }
86
+ };
87
+ }
88
+ }
89
+ return {
90
+ ...part,
91
+ toolInvocation: {
92
+ ...part.toolInvocation,
93
+ args: part.toolInvocation.args,
94
+ parsedArgs,
95
+ status: "result" /* RESULT */,
96
+ result: stub
97
+ }
98
+ };
99
+ })
100
+ };
101
+ });
102
+ }
103
+
104
+ // src/utils/run-utils.ts
105
+ function isAbortLikeError(error) {
106
+ if (error instanceof DOMException && error.name === "AbortError") {
107
+ return true;
108
+ }
109
+ if (error instanceof Error) {
110
+ if (error.name === "AbortError") {
111
+ return true;
112
+ }
113
+ const lower = error.message.toLowerCase();
114
+ return lower.includes("aborted") || lower.includes("abort");
115
+ }
116
+ return false;
117
+ }
118
+ function formatSendError(error) {
119
+ if (error instanceof Error) {
120
+ const message = error.message.trim();
121
+ if (message) {
122
+ return message;
123
+ }
124
+ }
125
+ const raw = String(error ?? "").trim();
126
+ return raw || "Failed to send message";
127
+ }
128
+ function buildLocalAssistantMessage(text, options) {
129
+ return {
130
+ id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
131
+ role: "assistant",
132
+ parts: [{ type: "text", text }],
133
+ meta: {
134
+ source: "local",
135
+ status: options?.status ?? "final",
136
+ sessionKey: options?.sessionKey,
137
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
138
+ }
139
+ };
140
+ }
141
+
142
+ // src/services/agent-event-handler.ts
143
+ import { v4 } from "uuid";
144
+
145
+ // src/utils/message-utils.ts
146
+ function extractTextFromMessage(message) {
147
+ const textParts = [];
148
+ for (const part of message.parts) {
149
+ if (part.type === "text") {
150
+ textParts.push(part.text);
151
+ }
152
+ }
153
+ return textParts.join("\n\n");
154
+ }
155
+ function hasCopyableText(message) {
156
+ return message.parts.some((part) => part.type === "text" && part.text.trim().length > 0);
157
+ }
158
+ function getMessagePreview(message, maxLength = 100) {
159
+ const text = extractTextFromMessage(message);
160
+ if (text.length <= maxLength) {
161
+ return text;
162
+ }
163
+ return text.substring(0, maxLength) + "...";
164
+ }
165
+
166
+ // src/utils/tool.ts
167
+ var getToolDefFromTool = (tool) => {
168
+ return {
169
+ name: tool.name,
170
+ description: tool.description,
171
+ parameters: tool.parameters
172
+ };
173
+ };
174
+ var toolInvocationToToolCall = (toolInvocation) => {
175
+ return {
176
+ id: toolInvocation.toolCallId,
177
+ type: "function",
178
+ function: {
179
+ name: toolInvocation.toolName,
180
+ arguments: JSON.stringify(toolInvocation.args)
181
+ }
182
+ };
183
+ };
184
+
185
+ // src/services/agent-event-handler.ts
186
+ var AgentEventHandler = class {
187
+ constructor(sessionManager) {
188
+ this.sessionManager = sessionManager;
189
+ }
190
+ currentMessageId;
191
+ currentReasoningMessageId;
192
+ currentReasoningContent = "";
193
+ currentToolCallId;
194
+ currentToolCallMessageId;
195
+ currentToolCallName;
196
+ currentToolCallArgs = "";
197
+ emittedToolCallIds = /* @__PURE__ */ new Set();
198
+ findAssistantMessageById(messageId) {
199
+ const targetId = messageId?.trim();
200
+ if (!targetId) {
201
+ return null;
202
+ }
203
+ const currentMessages = this.sessionManager.getMessages();
204
+ return currentMessages.find((message) => message.role === "assistant" && message.id === targetId) ?? null;
205
+ }
206
+ findAssistantMessageByToolCallId(toolCallId) {
207
+ if (!toolCallId) {
208
+ return null;
209
+ }
210
+ const currentMessages = this.sessionManager.getMessages();
211
+ for (let i = currentMessages.length - 1; i >= 0; i -= 1) {
212
+ const message = currentMessages[i];
213
+ if (message.role !== "assistant") {
214
+ continue;
215
+ }
216
+ const hasTargetTool = message.parts?.some(
217
+ (part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === toolCallId
218
+ );
219
+ if (hasTargetTool) {
220
+ return message;
221
+ }
222
+ }
223
+ return null;
224
+ }
225
+ findAssistantMessageForCurrentTool(toolCallId) {
226
+ const exactMessage = this.findAssistantMessageById(this.currentToolCallMessageId);
227
+ if (exactMessage?.parts?.some((part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === toolCallId)) {
228
+ return exactMessage;
229
+ }
230
+ return this.findAssistantMessageByToolCallId(toolCallId);
231
+ }
232
+ reset() {
233
+ this.currentMessageId = void 0;
234
+ this.currentReasoningMessageId = void 0;
235
+ this.currentReasoningContent = "";
236
+ this.currentToolCallId = void 0;
237
+ this.currentToolCallMessageId = void 0;
238
+ this.currentToolCallName = void 0;
239
+ this.currentToolCallArgs = "";
240
+ this.emittedToolCallIds.clear();
241
+ }
242
+ emitToolCallEvents() {
243
+ const currentMessages = this.sessionManager.getMessages();
244
+ for (const message of currentMessages) {
245
+ if (message.role !== "assistant" || !message.parts) {
246
+ continue;
247
+ }
248
+ for (const part of message.parts) {
249
+ if (part.type !== "tool-invocation") continue;
250
+ const inv = part.toolInvocation;
251
+ if (inv.status === "call" /* CALL */ && !this.emittedToolCallIds.has(inv.toolCallId)) {
252
+ this.emittedToolCallIds.add(inv.toolCallId);
253
+ this.sessionManager.toolCall$.next({
254
+ toolCall: {
255
+ id: inv.toolCallId,
256
+ type: "function",
257
+ function: {
258
+ name: inv.toolName,
259
+ arguments: inv.args
260
+ }
261
+ }
262
+ });
263
+ }
264
+ }
265
+ }
266
+ }
267
+ handleEvent(event) {
268
+ switch (event.type) {
269
+ case "RUN_STARTED" /* RUN_STARTED */:
270
+ break;
271
+ case "TEXT_START" /* TEXT_START */:
272
+ this.handleTextStart(event);
273
+ break;
274
+ case "TEXT_DELTA" /* TEXT_DELTA */:
275
+ this.handleTextContent(event);
276
+ break;
277
+ case "TEXT_END" /* TEXT_END */:
278
+ this.handleTextEnd();
279
+ break;
280
+ case "REASONING_START" /* REASONING_START */:
281
+ this.handleReasoningStart(event);
282
+ break;
283
+ case "REASONING_DELTA" /* REASONING_DELTA */:
284
+ this.handleReasoningContent(event);
285
+ break;
286
+ case "REASONING_END" /* REASONING_END */:
287
+ this.handleReasoningEnd();
288
+ break;
289
+ case "TOOL_CALL_START" /* TOOL_CALL_START */:
290
+ this.handleToolCallStart(event);
291
+ break;
292
+ case "TOOL_CALL_ARGS_DELTA" /* TOOL_CALL_ARGS_DELTA */:
293
+ this.handleToolCallArgsDelta(event);
294
+ break;
295
+ case "TOOL_CALL_ARGS" /* TOOL_CALL_ARGS */:
296
+ this.handleToolCallArgs(event);
297
+ break;
298
+ case "TOOL_CALL_END" /* TOOL_CALL_END */:
299
+ this.handleToolCallEnd();
300
+ this.emitToolCallEvents();
301
+ break;
302
+ case "TOOL_CALL_RESULT" /* TOOL_CALL_RESULT */:
303
+ this.handleToolCallResult(event);
304
+ break;
305
+ default:
306
+ break;
307
+ }
308
+ }
309
+ handleTextStart(event) {
310
+ this.currentMessageId = event.messageId;
311
+ }
312
+ appendTextDelta(message, delta) {
313
+ const parts = [...message.parts || []];
314
+ const lastPart = parts[parts.length - 1];
315
+ if (lastPart && lastPart.type === "text") {
316
+ return {
317
+ ...message,
318
+ parts: [
319
+ ...parts.slice(0, -1),
320
+ { type: "text", text: `${lastPart.text}${delta}` }
321
+ ]
322
+ };
323
+ }
324
+ return {
325
+ ...message,
326
+ parts: [...parts, { type: "text", text: delta }]
327
+ };
328
+ }
329
+ handleTextContent(event) {
330
+ if (!event.delta || this.currentMessageId !== event.messageId) return;
331
+ const currentMessages = this.sessionManager.getMessages();
332
+ const existingMessage = currentMessages.find(
333
+ (message) => message.role === "assistant" && message.id === this.currentMessageId
334
+ );
335
+ if (existingMessage) {
336
+ this.sessionManager.updateMessage(this.appendTextDelta(existingMessage, event.delta));
337
+ } else {
338
+ this.sessionManager.addMessages([
339
+ {
340
+ id: this.currentMessageId,
341
+ role: "assistant",
342
+ parts: [
343
+ {
344
+ type: "text",
345
+ text: event.delta
346
+ }
347
+ ]
348
+ }
349
+ ]);
350
+ }
351
+ }
352
+ handleTextEnd() {
353
+ this.currentMessageId = void 0;
354
+ }
355
+ updateReasoningPart(message, reasoning) {
356
+ const parts = [...message.parts || []];
357
+ let reasoningPartIndex = -1;
358
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
359
+ if (parts[index]?.type === "reasoning") {
360
+ reasoningPartIndex = index;
361
+ break;
362
+ }
363
+ }
364
+ if (reasoningPartIndex >= 0) {
365
+ return {
366
+ ...message,
367
+ parts: [
368
+ ...parts.slice(0, reasoningPartIndex),
369
+ { type: "reasoning", reasoning, details: [] },
370
+ ...parts.slice(reasoningPartIndex + 1)
371
+ ]
372
+ };
373
+ }
374
+ return {
375
+ ...message,
376
+ parts: [...parts, { type: "reasoning", reasoning, details: [] }]
377
+ };
378
+ }
379
+ handleReasoningStart(event) {
380
+ this.currentReasoningMessageId = event.messageId;
381
+ this.currentReasoningContent = "";
382
+ }
383
+ handleReasoningContent(event) {
384
+ if (!event.delta || this.currentReasoningMessageId !== event.messageId) return;
385
+ this.currentReasoningContent += event.delta;
386
+ const reasoningContent = this.currentReasoningContent;
387
+ const messageId = this.currentReasoningMessageId;
388
+ const currentMessages = this.sessionManager.getMessages();
389
+ const existingMessage = currentMessages.find((message) => message.role === "assistant" && message.id === messageId);
390
+ if (existingMessage) {
391
+ this.sessionManager.updateMessage(this.updateReasoningPart(existingMessage, reasoningContent));
392
+ } else {
393
+ this.sessionManager.addMessages([
394
+ {
395
+ id: messageId,
396
+ role: "assistant",
397
+ parts: [
398
+ {
399
+ type: "reasoning",
400
+ reasoning: reasoningContent,
401
+ details: []
402
+ }
403
+ ]
404
+ }
405
+ ]);
406
+ }
407
+ }
408
+ handleReasoningEnd() {
409
+ this.currentReasoningMessageId = void 0;
410
+ this.currentReasoningContent = "";
411
+ }
412
+ handleToolCallStart(event) {
413
+ this.currentToolCallId = event.toolCallId;
414
+ this.currentToolCallName = event.toolName;
415
+ this.currentToolCallArgs = "";
416
+ this.currentToolCallMessageId = event.messageId;
417
+ const invocationPart = {
418
+ type: "tool-invocation",
419
+ toolInvocation: {
420
+ status: "partial-call" /* PARTIAL_CALL */,
421
+ toolCallId: this.currentToolCallId,
422
+ toolName: this.currentToolCallName,
423
+ args: ""
424
+ }
425
+ };
426
+ const currentMessages = this.sessionManager.getMessages();
427
+ const messageId = event.messageId?.trim();
428
+ const exactMessage = this.findAssistantMessageById(messageId);
429
+ if (exactMessage) {
430
+ this.currentToolCallMessageId = exactMessage.id;
431
+ const existingPart = exactMessage.parts.find(
432
+ (part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === this.currentToolCallId
433
+ );
434
+ if (existingPart && existingPart.type === "tool-invocation") {
435
+ this.currentToolCallArgs = existingPart.toolInvocation.args;
436
+ return;
437
+ }
438
+ this.sessionManager.updateMessage({
439
+ ...exactMessage,
440
+ parts: [...exactMessage.parts || [], invocationPart]
441
+ });
442
+ return;
443
+ }
444
+ if (messageId) {
445
+ this.currentToolCallMessageId = messageId;
446
+ this.sessionManager.addMessages([
447
+ {
448
+ id: messageId,
449
+ role: "assistant",
450
+ parts: [invocationPart]
451
+ }
452
+ ]);
453
+ return;
454
+ }
455
+ const existingMessage = this.findAssistantMessageByToolCallId(this.currentToolCallId);
456
+ if (existingMessage) {
457
+ this.currentToolCallMessageId = existingMessage.id;
458
+ const existingPart = existingMessage.parts.find(
459
+ (part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === this.currentToolCallId
460
+ );
461
+ this.currentToolCallArgs = existingPart?.type === "tool-invocation" ? existingPart.toolInvocation.args : "";
462
+ return;
463
+ }
464
+ const lastMessage = currentMessages[currentMessages.length - 1];
465
+ if (lastMessage && lastMessage.role === "assistant") {
466
+ this.currentToolCallMessageId = lastMessage.id;
467
+ this.sessionManager.updateMessage({
468
+ ...lastMessage,
469
+ parts: [...lastMessage.parts || [], invocationPart]
470
+ });
471
+ } else {
472
+ const fallbackMessageId = v4();
473
+ this.currentToolCallMessageId = fallbackMessageId;
474
+ this.sessionManager.addMessages([
475
+ {
476
+ id: fallbackMessageId,
477
+ role: "assistant",
478
+ parts: [invocationPart]
479
+ }
480
+ ]);
481
+ }
482
+ }
483
+ handleToolCallArgsDelta(event) {
484
+ if (this.currentToolCallId !== event.toolCallId) return;
485
+ this.currentToolCallArgs += event.argsDelta;
486
+ const targetMessage = this.findAssistantMessageForCurrentTool(event.toolCallId);
487
+ if (!targetMessage || targetMessage.role !== "assistant" || !targetMessage.parts?.length) return;
488
+ const updatedParts = [...targetMessage.parts];
489
+ for (let i = updatedParts.length - 1; i >= 0; i--) {
490
+ const part = updatedParts[i];
491
+ if (part.type === "tool-invocation" && part.toolInvocation.toolCallId === event.toolCallId) {
492
+ let parsed;
493
+ try {
494
+ parsed = JSON.parse(this.currentToolCallArgs);
495
+ } catch {
496
+ }
497
+ updatedParts[i] = {
498
+ ...part,
499
+ toolInvocation: {
500
+ ...part.toolInvocation,
501
+ status: "partial-call" /* PARTIAL_CALL */,
502
+ args: this.currentToolCallArgs,
503
+ parsedArgs: parsed
504
+ }
505
+ };
506
+ break;
507
+ }
508
+ }
509
+ this.sessionManager.updateMessage({
510
+ ...targetMessage,
511
+ parts: updatedParts
512
+ });
513
+ }
514
+ handleToolCallArgs(event) {
515
+ if (this.currentToolCallId !== event.toolCallId) return;
516
+ this.currentToolCallArgs = event.args;
517
+ this.handleToolCallArgsDelta({
518
+ type: "TOOL_CALL_ARGS_DELTA" /* TOOL_CALL_ARGS_DELTA */,
519
+ toolCallId: event.toolCallId,
520
+ argsDelta: ""
521
+ });
522
+ }
523
+ handleToolCallEnd() {
524
+ if (!this.currentToolCallId || !this.currentToolCallName) return;
525
+ try {
526
+ const toolCall = {
527
+ id: this.currentToolCallId,
528
+ type: "function",
529
+ function: {
530
+ name: this.currentToolCallName,
531
+ arguments: this.currentToolCallArgs
532
+ }
533
+ };
534
+ const targetMessage = this.findAssistantMessageForCurrentTool(this.currentToolCallId);
535
+ if (targetMessage && targetMessage.role === "assistant") {
536
+ const updatedParts = [...targetMessage.parts || []];
537
+ for (let i = updatedParts.length - 1; i >= 0; i--) {
538
+ const part = updatedParts[i];
539
+ if (part.type === "tool-invocation" && part.toolInvocation.toolCallId === this.currentToolCallId) {
540
+ updatedParts[i] = {
541
+ ...part,
542
+ toolInvocation: {
543
+ ...toolCallToToolInvocation(toolCall),
544
+ status: "call" /* CALL */
545
+ }
546
+ };
547
+ break;
548
+ }
549
+ }
550
+ this.sessionManager.updateMessage({
551
+ ...targetMessage,
552
+ parts: updatedParts
553
+ });
554
+ } else {
555
+ const fallbackMessageId = this.currentToolCallMessageId?.trim() || v4();
556
+ this.sessionManager.addMessages([
557
+ {
558
+ id: fallbackMessageId,
559
+ role: "assistant",
560
+ parts: [
561
+ {
562
+ type: "tool-invocation",
563
+ toolInvocation: {
564
+ ...toolCallToToolInvocation(toolCall),
565
+ status: "call" /* CALL */
566
+ }
567
+ }
568
+ ]
569
+ }
570
+ ]);
571
+ }
572
+ } catch {
573
+ }
574
+ this.currentToolCallId = void 0;
575
+ this.currentToolCallMessageId = void 0;
576
+ this.currentToolCallName = void 0;
577
+ this.currentToolCallArgs = "";
578
+ }
579
+ handleToolCallResult(event) {
580
+ this.sessionManager.addToolResult({
581
+ toolCallId: event.toolCallId,
582
+ result: event.content,
583
+ status: "result" /* RESULT */
584
+ });
585
+ }
586
+ };
587
+
588
+ // src/services/agent-chat-controller.ts
589
+ var Disposable = class {
590
+ disposables = [];
591
+ addDisposable = (disposable) => {
592
+ this.disposables.push(disposable);
593
+ };
594
+ dispose = () => {
595
+ this.disposables.forEach((disposable) => disposable());
596
+ this.disposables = [];
597
+ };
598
+ };
599
+ var AgentChatController = class extends Disposable {
600
+ constructor(agentProvider = null, options) {
601
+ super();
602
+ this.agentProvider = agentProvider;
603
+ const { initialMessages = [], metadataParsers, callbacks = {} } = options || {};
604
+ this.metadataParsers = metadataParsers ?? null;
605
+ this.callbacks = callbacks;
606
+ this._messages$.next(initialMessages);
607
+ this.messages$ = this._messages$.asObservable();
608
+ if (this.agentProvider) {
609
+ this.addDisposable(this.connectToolExecutor());
610
+ }
611
+ }
612
+ _messages$ = new BehaviorSubject([]);
613
+ messages$;
614
+ threadId$ = new BehaviorSubject(null);
615
+ isAgentResponding$ = new BehaviorSubject(false);
616
+ activeRun$ = new BehaviorSubject(null);
617
+ lastError$ = new BehaviorSubject(null);
618
+ isAwaitingResponse$ = new BehaviorSubject(false);
619
+ runCompleted$ = new Subject();
620
+ runError$ = new Subject();
621
+ runMetadata$ = new Subject();
622
+ activeSubscription = null;
623
+ addMessagesEvent$ = new Subject();
624
+ updateMessageEvent$ = new Subject();
625
+ setMessagesEvent$ = new Subject();
626
+ toolCall$ = new Subject();
627
+ eventHandler = new AgentEventHandler(this);
628
+ runIdCounter = 0;
629
+ metadataParsers;
630
+ callbacks;
631
+ getMessages = () => {
632
+ return this._messages$.getValue();
633
+ };
634
+ setMessages = (messages) => {
635
+ this._messages$.next(messages);
636
+ this.setMessagesEvent$.next({ messages });
637
+ };
638
+ handleEvent = (event) => {
639
+ this.eventHandler.handleEvent(event);
640
+ };
641
+ setCallbacks = (callbacks) => {
642
+ this.callbacks = callbacks;
643
+ };
644
+ reset = () => {
645
+ this.abortAgentRun();
646
+ this._messages$.next([]);
647
+ this.eventHandler.reset();
648
+ this.threadId$.next(null);
649
+ this.isAgentResponding$.next(false);
650
+ this.activeRun$.next(null);
651
+ this.lastError$.next(null);
652
+ this.isAwaitingResponse$.next(false);
653
+ this.runIdCounter += 1;
654
+ };
655
+ addMessages = (messages) => {
656
+ const current = this.getMessages();
657
+ const next = [...current];
658
+ for (const message of messages) {
659
+ const existingIndex = next.findIndex((item) => item.id === message.id);
660
+ if (existingIndex >= 0) {
661
+ next[existingIndex] = message;
662
+ } else {
663
+ next.push(message);
664
+ }
665
+ }
666
+ this._messages$.next(next);
667
+ this.addMessagesEvent$.next({ messages });
668
+ if (this.isAwaitingResponse$.getValue() && messages.some((m) => m.role === "assistant")) {
669
+ this.isAwaitingResponse$.next(false);
670
+ }
671
+ };
672
+ removeMessages = (messageIds) => {
673
+ if (messageIds.length === 0) return;
674
+ this._messages$.next(this.getMessages().filter((msg) => !messageIds.includes(msg.id)));
675
+ };
676
+ updateMessage = (message) => {
677
+ this._messages$.next(this.getMessages().map((msg) => msg.id === message.id ? message : msg));
678
+ this.updateMessageEvent$.next({ message });
679
+ if (this.isAwaitingResponse$.getValue() && message.role === "assistant") {
680
+ this.isAwaitingResponse$.next(false);
681
+ }
682
+ };
683
+ addToolResult = (result, _options) => {
684
+ const targetMessage = this.getMessages().find(
685
+ (msg) => msg.parts.find(
686
+ (part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === result.toolCallId
687
+ )
688
+ );
689
+ if (!targetMessage) {
690
+ return;
691
+ }
692
+ const newMessage = {
693
+ ...targetMessage,
694
+ parts: targetMessage.parts.map((part) => {
695
+ if (part.type === "tool-invocation" && part.toolInvocation.toolCallId === result.toolCallId) {
696
+ return {
697
+ ...part,
698
+ toolInvocation: {
699
+ ...part.toolInvocation,
700
+ result: result.result ?? void 0,
701
+ status: result.status,
702
+ error: result.error ?? void 0,
703
+ cancelled: result.cancelled ?? void 0
704
+ }
705
+ };
706
+ }
707
+ return part;
708
+ })
709
+ };
710
+ this.updateMessage(newMessage);
711
+ };
712
+ send = async (options) => {
713
+ if (this.activeRun$.getValue() || this.activeSubscription) {
714
+ this.abortAgentRun();
715
+ }
716
+ this.lastError$.next(null);
717
+ const message = options.message.trim();
718
+ if (!message) return;
719
+ this.addMessages([
720
+ {
721
+ id: v42(),
722
+ role: "user",
723
+ parts: [{ type: "text", text: message }]
724
+ }
725
+ ]);
726
+ await this.runAgent({
727
+ metadata: options.metadata,
728
+ sessionId: options.sessionId,
729
+ agentId: options.agentId,
730
+ stopCapable: options.stopCapable,
731
+ stopReason: options.stopReason,
732
+ sourceMessage: message,
733
+ restoreDraftOnError: options.restoreDraftOnError
734
+ });
735
+ };
736
+ resume = async (options) => {
737
+ const remoteRunId = options.remoteRunId?.trim();
738
+ const sessionId = options.sessionId?.trim();
739
+ if (!remoteRunId) return;
740
+ const currentRun = this.activeRun$.getValue();
741
+ if (currentRun?.remoteRunId === remoteRunId) return;
742
+ if (currentRun) return;
743
+ this.lastError$.next(null);
744
+ await this.runAgent({
745
+ metadata: options.metadata,
746
+ sessionId,
747
+ agentId: options.agentId,
748
+ stopCapable: options.stopCapable,
749
+ stopReason: options.stopReason
750
+ });
751
+ };
752
+ stop = async () => {
753
+ const activeRun = this.activeRun$.getValue();
754
+ if (!activeRun) return;
755
+ const sourceSessionId = activeRun.sessionId;
756
+ this.abortAgentRun();
757
+ if (sourceSessionId) {
758
+ await this.callbacks.onRunSettled?.({ sourceSessionId });
759
+ }
760
+ };
761
+ handleAgentResponse = (response) => {
762
+ if (this.activeSubscription) {
763
+ this.activeSubscription.unsubscribe();
764
+ }
765
+ this.activeSubscription = response.subscribe((event) => {
766
+ if (event.type === "RUN_METADATA" /* RUN_METADATA */) {
767
+ const metaEvent = event;
768
+ const activeRun = this.activeRun$.getValue();
769
+ const runId = metaEvent.runId?.trim() || (activeRun ? `ui-${activeRun.localRunId}` : "");
770
+ if (this.metadataParsers && activeRun && this.isMatchingRun(activeRun, runId)) {
771
+ this.processRunMetadata(activeRun, metaEvent.metadata);
772
+ }
773
+ this.runMetadata$.next({ runId, metadata: metaEvent.metadata });
774
+ return;
775
+ }
776
+ this.handleEvent(event);
777
+ if (event.type === "RUN_FINISHED" /* RUN_FINISHED */) {
778
+ this.isAgentResponding$.next(false);
779
+ this.isAwaitingResponse$.next(false);
780
+ this.activeSubscription = null;
781
+ const activeRun = this.activeRun$.getValue();
782
+ const runId = event.runId?.trim() || (activeRun ? `ui-${activeRun.localRunId}` : "");
783
+ if (!activeRun || !this.isMatchingRun(activeRun, runId)) {
784
+ return;
785
+ }
786
+ const sourceSessionId = activeRun.sessionId;
787
+ this.activeRun$.next(null);
788
+ if (sourceSessionId) {
789
+ void this.callbacks.onRunSettled?.({ sourceSessionId });
790
+ }
791
+ } else if (event.type === "RUN_ERROR" /* RUN_ERROR */) {
792
+ this.isAgentResponding$.next(false);
793
+ this.isAwaitingResponse$.next(false);
794
+ this.activeSubscription = null;
795
+ const activeRun = this.activeRun$.getValue();
796
+ const runId = event.runId?.trim() || (activeRun ? `ui-${activeRun.localRunId}` : "");
797
+ const isAbort = isAbortLikeError(event.error);
798
+ this.runError$.next({ runId, error: event.error, isAbort });
799
+ if (!activeRun || !this.isMatchingRun(activeRun, runId)) {
800
+ this.activeRun$.next(null);
801
+ return;
802
+ }
803
+ const sourceSessionId = activeRun.sessionId;
804
+ if (isAbort) {
805
+ this.activeRun$.next(null);
806
+ if (sourceSessionId) {
807
+ void this.callbacks.onRunSettled?.({ sourceSessionId });
808
+ }
809
+ return;
810
+ }
811
+ const sendError = formatSendError(event.error);
812
+ this.lastError$.next(sendError);
813
+ this.activeRun$.next(null);
814
+ if (sourceSessionId) {
815
+ this.addMessages([
816
+ buildLocalAssistantMessage(sendError, {
817
+ sessionKey: sourceSessionId,
818
+ status: "error"
819
+ })
820
+ ]);
821
+ }
822
+ this.callbacks.onRunError?.({
823
+ error: sendError,
824
+ sourceMessage: activeRun.sourceMessage,
825
+ restoreDraft: activeRun.restoreDraftOnError
826
+ });
827
+ }
828
+ });
829
+ };
830
+ abortAgentRun = () => {
831
+ this.agentProvider?.agent.abortRun?.();
832
+ if (this.activeSubscription) {
833
+ this.activeSubscription.unsubscribe();
834
+ this.activeSubscription = null;
835
+ this.isAgentResponding$.next(false);
836
+ this.isAwaitingResponse$.next(false);
837
+ }
838
+ this.activeRun$.next(null);
839
+ };
840
+ runAgent = async (options) => {
841
+ if (!this.agentProvider) {
842
+ return;
843
+ }
844
+ if (this.activeRun$.getValue() || this.activeSubscription) {
845
+ this.abortAgentRun();
846
+ }
847
+ const localRunId = ++this.runIdCounter;
848
+ const runId = `ui-${localRunId}`;
849
+ const activeRun = {
850
+ localRunId,
851
+ sessionId: options?.sessionId,
852
+ ...options?.agentId ? { agentId: options.agentId } : {},
853
+ remoteStopCapable: Boolean(options?.stopCapable),
854
+ ...options?.stopReason ? { remoteStopReason: options.stopReason } : {},
855
+ sourceMessage: options?.sourceMessage,
856
+ restoreDraftOnError: options?.restoreDraftOnError
857
+ };
858
+ this.activeRun$.next(activeRun);
859
+ this.isAgentResponding$.next(true);
860
+ this.isAwaitingResponse$.next(true);
861
+ try {
862
+ const safeMessages = finalizePendingToolInvocations(this.getMessages());
863
+ const threadId = typeof options?.threadId === "string" && options.threadId.trim() ? options.threadId : this.threadId$.getValue() ?? "";
864
+ const response = await this.agentProvider.agent.run({
865
+ threadId,
866
+ runId,
867
+ messages: safeMessages,
868
+ tools: this.agentProvider.getToolDefs(),
869
+ context: this.agentProvider.getContexts(),
870
+ ...options?.metadata ? { metadata: options.metadata } : {}
871
+ });
872
+ this.handleAgentResponse(response);
873
+ } catch (error) {
874
+ const isAbort = isAbortLikeError(error);
875
+ if (isAbort) {
876
+ console.info("Agent run aborted");
877
+ } else {
878
+ console.error("Error running agent:", error);
879
+ }
880
+ this.isAgentResponding$.next(false);
881
+ this.isAwaitingResponse$.next(false);
882
+ const currentActiveRun = this.activeRun$.getValue();
883
+ this.activeRun$.next(null);
884
+ this.runError$.next({ runId, error, isAbort });
885
+ if (!isAbort && currentActiveRun) {
886
+ const sendError = formatSendError(error);
887
+ this.lastError$.next(sendError);
888
+ if (currentActiveRun.sessionId) {
889
+ this.addMessages([
890
+ buildLocalAssistantMessage(sendError, {
891
+ sessionKey: currentActiveRun.sessionId,
892
+ status: "error"
893
+ })
894
+ ]);
895
+ }
896
+ this.callbacks.onRunError?.({
897
+ error: sendError,
898
+ sourceMessage: currentActiveRun.sourceMessage,
899
+ restoreDraft: currentActiveRun.restoreDraftOnError
900
+ });
901
+ } else if (isAbort && currentActiveRun?.sessionId) {
902
+ void this.callbacks.onRunSettled?.({ sourceSessionId: currentActiveRun.sessionId });
903
+ }
904
+ }
905
+ };
906
+ processRunMetadata = (activeRun, metadata) => {
907
+ if (!this.metadataParsers) return;
908
+ const ready = this.metadataParsers.parseReady(metadata);
909
+ if (ready) {
910
+ this.applyReadyMetadata(activeRun, ready);
911
+ return;
912
+ }
913
+ const final = this.metadataParsers.parseFinal(metadata);
914
+ if (final) {
915
+ this.applyFinalMetadata(activeRun, final);
916
+ }
917
+ };
918
+ applyReadyMetadata = (activeRun, ready) => {
919
+ const updatedRun = { ...activeRun };
920
+ if (ready.remoteRunId?.trim()) {
921
+ updatedRun.remoteRunId = ready.remoteRunId.trim();
922
+ }
923
+ if (typeof ready.stopCapable === "boolean") {
924
+ updatedRun.remoteStopCapable = ready.stopCapable;
925
+ }
926
+ if (ready.stopReason?.trim()) {
927
+ updatedRun.remoteStopReason = ready.stopReason.trim();
928
+ }
929
+ if (ready.sessionId?.trim()) {
930
+ updatedRun.sessionId = ready.sessionId.trim();
931
+ this.callbacks.onSessionChanged?.(updatedRun.sessionId);
932
+ }
933
+ this.activeRun$.next(updatedRun);
934
+ };
935
+ applyFinalMetadata = (activeRun, final) => {
936
+ const sourceSessionId = activeRun.sessionId;
937
+ const resultSessionId = final.sessionId;
938
+ if (resultSessionId && resultSessionId !== sourceSessionId) {
939
+ this.callbacks.onSessionChanged?.(resultSessionId);
940
+ }
941
+ this.activeRun$.next(null);
942
+ if (sourceSessionId) {
943
+ void this.callbacks.onRunSettled?.({ sourceSessionId, resultSessionId });
944
+ }
945
+ };
946
+ isMatchingRun = (activeRun, runId) => {
947
+ if (!runId.trim()) return false;
948
+ return runId === `ui-${activeRun.localRunId}`;
949
+ };
950
+ connectToolExecutor = () => {
951
+ const sub = this.toolCall$.subscribe(async ({ toolCall }) => {
952
+ const executor = this.agentProvider?.getToolExecutor(toolCall.function.name);
953
+ if (executor) {
954
+ try {
955
+ const toolCallArgs = JSON.parse(toolCall.function.arguments);
956
+ const result = await executor(toolCallArgs);
957
+ this.addToolResult({ toolCallId: toolCall.id, result, status: "result" /* RESULT */ });
958
+ this.runAgent();
959
+ } catch (err) {
960
+ console.error("[AgentChatController] handleAddToolResult error", err);
961
+ this.addToolResult({
962
+ toolCallId: toolCall.id,
963
+ error: err instanceof Error ? err.message : String(err),
964
+ status: "error" /* ERROR */
965
+ });
966
+ this.runAgent();
967
+ }
968
+ }
969
+ });
970
+ return () => sub.unsubscribe();
971
+ };
972
+ };
973
+ export {
974
+ AgentChatController,
975
+ AgentEventHandler,
976
+ Disposable,
977
+ EventType,
978
+ ToolInvocationStatus,
979
+ buildLocalAssistantMessage,
980
+ extractTextFromMessage,
981
+ finalizePendingToolInvocations,
982
+ formatSendError,
983
+ getMessagePreview,
984
+ getStopDisabledReason,
985
+ getToolDefFromTool,
986
+ hasCopyableText,
987
+ isAbortLikeError,
988
+ toolCallToToolInvocation,
989
+ toolInvocationToToolCall
990
+ };