@assistant-ui/react-a2a 0.2.15 → 0.2.16

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,483 +1,404 @@
1
1
  "use client";
2
- import { generateId, fromThreadMessageLike } from "@assistant-ui/core/internal";
3
- import { a2aMessageToContent, isTerminalTaskState, taskStateToMessageStatus, } from "./conversions.js";
2
+ import { a2aMessageToContent, isTerminalTaskState, taskStateToMessageStatus } from "./conversions.js";
3
+ import { fromThreadMessageLike, generateId } from "@assistant-ui/core/internal";
4
+ //#region src/A2AThreadRuntimeCore.ts
4
5
  const FALLBACK_USER_STATUS = {
5
- type: "complete",
6
- reason: "unknown",
6
+ type: "complete",
7
+ reason: "unknown"
7
8
  };
8
- export class A2AThreadRuntimeCore {
9
- client;
10
- contextId;
11
- configuration;
12
- onError;
13
- onCancel;
14
- onArtifactComplete;
15
- history;
16
- notifyUpdate;
17
- runtime;
18
- messages = [];
19
- isRunningFlag = false;
20
- abortController = null;
21
- pendingError = null;
22
- // A2A-specific state
23
- currentTask;
24
- currentArtifacts = [];
25
- agentCardValue;
26
- // History tracking
27
- assistantHistoryParents = new Map();
28
- recordedHistoryIds = new Set();
29
- _isLoading = false;
30
- _loadPromise;
31
- constructor(options) {
32
- this.client = options.client;
33
- this.contextId = options.contextId;
34
- this.configuration = options.configuration;
35
- this.onError = options.onError;
36
- this.onCancel = options.onCancel;
37
- this.onArtifactComplete = options.onArtifactComplete;
38
- this.history = options.history;
39
- this.notifyUpdate = options.notifyUpdate;
40
- }
41
- updateOptions(options) {
42
- this.client = options.client;
43
- this.contextId = options.contextId;
44
- this.configuration = options.configuration;
45
- this.onError = options.onError;
46
- this.onCancel = options.onCancel;
47
- this.onArtifactComplete = options.onArtifactComplete;
48
- this.history = options.history;
49
- }
50
- attachRuntime(runtime) {
51
- this.runtime = runtime;
52
- }
53
- detachRuntime() {
54
- this.runtime = undefined;
55
- // Abort in-flight requests on unmount
56
- if (this.abortController) {
57
- this.abortController.abort();
58
- this.abortController = null;
59
- }
60
- }
61
- getRuntime() {
62
- return this.runtime;
63
- }
64
- getMessages() {
65
- return this.messages;
66
- }
67
- getTask() {
68
- return this.currentTask;
69
- }
70
- getArtifacts() {
71
- return this.currentArtifacts;
72
- }
73
- getAgentCard() {
74
- return this.agentCardValue;
75
- }
76
- isRunning() {
77
- return this.isRunningFlag;
78
- }
79
- get isLoading() {
80
- return this._isLoading;
81
- }
82
- __internal_load() {
83
- if (this._loadPromise)
84
- return this._loadPromise;
85
- this._isLoading = true;
86
- const historyPromise = this.history?.load() ?? Promise.resolve(null);
87
- const agentCardPromise = this.client.getAgentCard().catch(() => undefined);
88
- this._loadPromise = Promise.all([historyPromise, agentCardPromise])
89
- .then(([repo, agentCard]) => {
90
- if (agentCard) {
91
- this.agentCardValue = agentCard;
92
- }
93
- if (repo) {
94
- const messages = repo.messages.map((item) => item.message);
95
- this.applyExternalMessages(messages);
96
- }
97
- })
98
- .catch((error) => {
99
- this.onError?.(error instanceof Error ? error : new Error(String(error)));
100
- })
101
- .finally(() => {
102
- this._isLoading = false;
103
- this.notifyUpdate();
104
- });
105
- this.notifyUpdate();
106
- return this._loadPromise;
107
- }
108
- async append(message) {
109
- const startRun = message.startRun ?? message.role === "user";
110
- if (message.sourceId) {
111
- this.messages = this.messages.filter((entry) => entry.id !== message.sourceId);
112
- }
113
- this.resetHead(message.parentId);
114
- const threadMessage = fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS);
115
- this.messages = [...this.messages, threadMessage];
116
- this.notifyUpdate();
117
- this.recordHistoryEntry(message.parentId ?? null, threadMessage);
118
- if (!startRun)
119
- return;
120
- await this.startRun(threadMessage);
121
- }
122
- async edit(message) {
123
- await this.append(message);
124
- }
125
- async reload(parentId, _config = {}) {
126
- this.resetHead(parentId);
127
- this.notifyUpdate();
128
- // Find the last user message to re-run
129
- for (let i = this.messages.length - 1; i >= 0; i--) {
130
- if (this.messages[i].role === "user") {
131
- await this.startRun(this.messages[i]);
132
- return;
133
- }
134
- }
135
- }
136
- async cancel() {
137
- if (!this.abortController)
138
- return;
139
- // Abort locally first so the stream stops immediately
140
- this.abortController.abort();
141
- // Then try to cancel the task on the server
142
- if (this.currentTask?.id) {
143
- try {
144
- const updated = await this.client.cancelTask(this.currentTask.id);
145
- this.currentTask = updated;
146
- }
147
- catch {
148
- // Server cancel failed; local abort already handled
149
- }
150
- }
151
- }
152
- applyExternalMessages(messages) {
153
- this.assistantHistoryParents.clear();
154
- this.messages = [...messages];
155
- this.recordedHistoryIds.clear();
156
- for (const message of this.messages) {
157
- this.recordedHistoryIds.add(message.id);
158
- }
159
- // Reset task-specific state to prevent leaking into new thread
160
- this.currentTask = undefined;
161
- this.currentArtifacts = [];
162
- this.notifyUpdate();
163
- }
164
- // --- Run logic ---
165
- async startRun(userThreadMessage) {
166
- // Cancel any in-progress run before starting a new one
167
- if (this.abortController) {
168
- this.abortController.abort();
169
- this.abortController = null;
170
- }
171
- const a2aMessage = this.threadMessageToA2AMessage(userThreadMessage);
172
- // Clear task if previous task reached terminal state
173
- if (this.currentTask &&
174
- isTerminalTaskState(this.currentTask.status.state)) {
175
- this.currentTask = undefined;
176
- }
177
- this.currentArtifacts = [];
178
- const assistantParentId = userThreadMessage.id;
179
- const assistantId = this.insertAssistantPlaceholder();
180
- this.markPendingAssistantHistory(assistantId, assistantParentId);
181
- const abortController = new AbortController();
182
- this.abortController = abortController;
183
- abortController.signal.addEventListener("abort", () => {
184
- this.updateAssistantStatus(assistantId, {
185
- type: "incomplete",
186
- reason: "cancelled",
187
- });
188
- this.finishRun(abortController);
189
- this.onCancel?.();
190
- }, { once: true });
191
- this.setRunning(true);
192
- // Check if agent supports streaming; fall back to sync sendMessage if not
193
- const supportsStreaming = this.agentCardValue?.capabilities?.streaming !== false;
194
- try {
195
- if (supportsStreaming) {
196
- await this.runStreaming(a2aMessage, assistantId, abortController);
197
- }
198
- else {
199
- await this.runSync(a2aMessage, assistantId, abortController);
200
- }
201
- }
202
- catch (error) {
203
- if (!abortController.signal.aborted) {
204
- const err = error instanceof Error ? error : new Error(String(error));
205
- this.updateAssistantStatus(assistantId, {
206
- type: "incomplete",
207
- reason: "error",
208
- });
209
- this.onError?.(err);
210
- this.pendingError = this.pendingError ?? err;
211
- }
212
- }
213
- finally {
214
- this.finishRun(abortController);
215
- }
216
- if (this.pendingError) {
217
- const err = this.pendingError;
218
- this.pendingError = null;
219
- throw err;
220
- }
221
- }
222
- async runStreaming(a2aMessage, assistantId, abortController) {
223
- const stream = this.client.streamMessage(a2aMessage, this.configuration, undefined, // metadata
224
- abortController.signal);
225
- for await (const event of stream) {
226
- if (abortController.signal.aborted)
227
- break;
228
- this.handleStreamEvent(assistantId, event);
229
- }
230
- if (!abortController.signal.aborted) {
231
- const lastStatus = this.getAssistantStatus(assistantId);
232
- if (lastStatus?.type === "running") {
233
- this.updateAssistantStatus(assistantId, {
234
- type: "complete",
235
- reason: "stop",
236
- });
237
- }
238
- }
239
- }
240
- async runSync(a2aMessage, assistantId, abortController) {
241
- const result = await this.client.sendMessage(a2aMessage, this.configuration, undefined, // metadata
242
- abortController.signal);
243
- if (abortController.signal.aborted)
244
- return;
245
- // Result is either A2ATask or A2AMessage
246
- if ("id" in result && "status" in result) {
247
- // It's a Task
248
- this.handleTaskSnapshot(assistantId, result);
249
- }
250
- else if ("messageId" in result && "parts" in result) {
251
- // It's a Message
252
- this.handleMessage(assistantId, result);
253
- this.updateAssistantStatus(assistantId, {
254
- type: "complete",
255
- reason: "stop",
256
- });
257
- }
258
- }
259
- handleStreamEvent(assistantId, event) {
260
- switch (event.type) {
261
- case "statusUpdate":
262
- this.handleStatusUpdate(assistantId, event.event);
263
- break;
264
- case "artifactUpdate":
265
- this.handleArtifactUpdate(event.event);
266
- break;
267
- case "message":
268
- this.handleMessage(assistantId, event.message);
269
- break;
270
- case "task":
271
- this.handleTaskSnapshot(assistantId, event.task);
272
- break;
273
- }
274
- }
275
- handleStatusUpdate(assistantId, event) {
276
- if (!this.currentTask) {
277
- this.currentTask = {
278
- id: event.taskId,
279
- contextId: event.contextId,
280
- status: event.status,
281
- };
282
- }
283
- else {
284
- this.currentTask = { ...this.currentTask, status: event.status };
285
- }
286
- if (event.contextId) {
287
- this.contextId = event.contextId;
288
- }
289
- if (event.status.message) {
290
- const content = a2aMessageToContent(event.status.message);
291
- this.updateAssistantContent(assistantId, content);
292
- }
293
- const status = taskStateToMessageStatus(event.status.state);
294
- this.updateAssistantStatus(assistantId, status);
295
- this.notifyUpdate();
296
- }
297
- handleArtifactUpdate(event) {
298
- const { artifact, append, lastChunk } = event;
299
- const existingIdx = this.currentArtifacts.findIndex((a) => a.artifactId === artifact.artifactId);
300
- let updated;
301
- if (existingIdx >= 0 && append) {
302
- const existing = this.currentArtifacts[existingIdx];
303
- updated = {
304
- ...existing,
305
- parts: [...existing.parts, ...artifact.parts],
306
- };
307
- this.currentArtifacts = [
308
- ...this.currentArtifacts.slice(0, existingIdx),
309
- updated,
310
- ...this.currentArtifacts.slice(existingIdx + 1),
311
- ];
312
- }
313
- else if (existingIdx >= 0) {
314
- updated = artifact;
315
- this.currentArtifacts = [
316
- ...this.currentArtifacts.slice(0, existingIdx),
317
- updated,
318
- ...this.currentArtifacts.slice(existingIdx + 1),
319
- ];
320
- }
321
- else {
322
- updated = artifact;
323
- this.currentArtifacts = [...this.currentArtifacts, updated];
324
- }
325
- if (lastChunk) {
326
- this.onArtifactComplete?.(updated);
327
- }
328
- this.notifyUpdate();
329
- }
330
- handleMessage(assistantId, message) {
331
- if (message.role !== "agent")
332
- return;
333
- const content = a2aMessageToContent(message);
334
- this.updateAssistantContent(assistantId, content);
335
- this.notifyUpdate();
336
- }
337
- handleTaskSnapshot(assistantId, task) {
338
- this.currentTask = task;
339
- if (task.contextId) {
340
- this.contextId = task.contextId;
341
- }
342
- if (task.artifacts) {
343
- this.currentArtifacts = task.artifacts;
344
- }
345
- if (task.status.message) {
346
- const content = a2aMessageToContent(task.status.message);
347
- this.updateAssistantContent(assistantId, content);
348
- }
349
- const status = taskStateToMessageStatus(task.status.state);
350
- this.updateAssistantStatus(assistantId, status);
351
- this.notifyUpdate();
352
- }
353
- // --- Message helpers ---
354
- threadMessageToA2AMessage(message) {
355
- const parts = [];
356
- if (message.role === "user") {
357
- for (const part of message.content) {
358
- if (part.type === "text") {
359
- parts.push({ text: part.text });
360
- }
361
- else if (part.type === "image") {
362
- parts.push({ url: part.image, mediaType: "image/*" });
363
- }
364
- }
365
- }
366
- const a2aMsg = {
367
- messageId: message.id,
368
- role: "user",
369
- parts,
370
- };
371
- if (this.contextId) {
372
- a2aMsg.contextId = this.contextId;
373
- }
374
- // Only attach taskId if current task is NOT in terminal state
375
- if (this.currentTask?.id &&
376
- !isTerminalTaskState(this.currentTask.status.state)) {
377
- a2aMsg.taskId = this.currentTask.id;
378
- }
379
- return a2aMsg;
380
- }
381
- insertAssistantPlaceholder() {
382
- const id = generateId();
383
- const assistant = {
384
- id,
385
- role: "assistant",
386
- createdAt: new Date(),
387
- status: { type: "running" },
388
- content: [],
389
- metadata: {
390
- unstable_state: null,
391
- unstable_annotations: [],
392
- unstable_data: [],
393
- steps: [],
394
- custom: {},
395
- },
396
- };
397
- this.messages = [...this.messages, assistant];
398
- this.notifyUpdate();
399
- return id;
400
- }
401
- updateAssistantContent(messageId, content) {
402
- this.messages = this.messages.map((message) => {
403
- if (message.id !== messageId || message.role !== "assistant")
404
- return message;
405
- return { ...message, content };
406
- });
407
- }
408
- updateAssistantStatus(messageId, status) {
409
- let touched = false;
410
- this.messages = this.messages.map((message) => {
411
- if (message.id !== messageId || message.role !== "assistant")
412
- return message;
413
- touched = true;
414
- return { ...message, status };
415
- });
416
- if (touched) {
417
- this.notifyUpdate();
418
- if (status.type === "complete" || status.type === "incomplete") {
419
- this.persistAssistantHistory(messageId);
420
- }
421
- }
422
- }
423
- getAssistantStatus(messageId) {
424
- const msg = this.messages.find((m) => m.id === messageId && m.role === "assistant");
425
- return msg?.status;
426
- }
427
- // --- Lifecycle helpers ---
428
- setRunning(running) {
429
- this.isRunningFlag = running;
430
- this.notifyUpdate();
431
- }
432
- finishRun(controller) {
433
- if (this.abortController === controller) {
434
- this.abortController = null;
435
- }
436
- this.setRunning(false);
437
- }
438
- resetHead(parentId) {
439
- if (!parentId) {
440
- if (this.messages.length) {
441
- this.messages = [];
442
- }
443
- return;
444
- }
445
- const idx = this.messages.findIndex((message) => message.id === parentId);
446
- if (idx === -1)
447
- return;
448
- this.messages = this.messages.slice(0, idx + 1);
449
- }
450
- // --- History persistence ---
451
- recordHistoryEntry(parentId, message) {
452
- this.appendHistoryItem(parentId, message);
453
- }
454
- markPendingAssistantHistory(messageId, parentId) {
455
- if (!this.history)
456
- return;
457
- this.assistantHistoryParents.set(messageId, parentId);
458
- }
459
- persistAssistantHistory(messageId) {
460
- if (!this.history)
461
- return;
462
- const parentId = this.assistantHistoryParents.get(messageId);
463
- if (parentId === undefined)
464
- return;
465
- const message = this.messages.find((m) => m.id === messageId);
466
- if (!message || message.role !== "assistant")
467
- return;
468
- if (message.status?.type !== "complete" &&
469
- message.status?.type !== "incomplete")
470
- return;
471
- this.assistantHistoryParents.delete(messageId);
472
- this.appendHistoryItem(parentId, message);
473
- }
474
- appendHistoryItem(parentId, message) {
475
- if (!this.history || this.recordedHistoryIds.has(message.id))
476
- return;
477
- this.recordedHistoryIds.add(message.id);
478
- void this.history.append({ parentId, message }).catch(() => {
479
- this.recordedHistoryIds.delete(message.id);
480
- });
481
- }
482
- }
9
+ var A2AThreadRuntimeCore = class {
10
+ client;
11
+ contextId;
12
+ configuration;
13
+ onError;
14
+ onCancel;
15
+ onArtifactComplete;
16
+ history;
17
+ notifyUpdate;
18
+ runtime;
19
+ messages = [];
20
+ isRunningFlag = false;
21
+ abortController = null;
22
+ pendingError = null;
23
+ currentTask;
24
+ currentArtifacts = [];
25
+ agentCardValue;
26
+ assistantHistoryParents = /* @__PURE__ */ new Map();
27
+ recordedHistoryIds = /* @__PURE__ */ new Set();
28
+ _isLoading = false;
29
+ _loadPromise;
30
+ constructor(options) {
31
+ this.client = options.client;
32
+ this.contextId = options.contextId;
33
+ this.configuration = options.configuration;
34
+ this.onError = options.onError;
35
+ this.onCancel = options.onCancel;
36
+ this.onArtifactComplete = options.onArtifactComplete;
37
+ this.history = options.history;
38
+ this.notifyUpdate = options.notifyUpdate;
39
+ }
40
+ updateOptions(options) {
41
+ this.client = options.client;
42
+ this.contextId = options.contextId;
43
+ this.configuration = options.configuration;
44
+ this.onError = options.onError;
45
+ this.onCancel = options.onCancel;
46
+ this.onArtifactComplete = options.onArtifactComplete;
47
+ this.history = options.history;
48
+ }
49
+ attachRuntime(runtime) {
50
+ this.runtime = runtime;
51
+ }
52
+ detachRuntime() {
53
+ this.runtime = void 0;
54
+ if (this.abortController) {
55
+ this.abortController.abort();
56
+ this.abortController = null;
57
+ }
58
+ }
59
+ getRuntime() {
60
+ return this.runtime;
61
+ }
62
+ getMessages() {
63
+ return this.messages;
64
+ }
65
+ getTask() {
66
+ return this.currentTask;
67
+ }
68
+ getArtifacts() {
69
+ return this.currentArtifacts;
70
+ }
71
+ getAgentCard() {
72
+ return this.agentCardValue;
73
+ }
74
+ isRunning() {
75
+ return this.isRunningFlag;
76
+ }
77
+ get isLoading() {
78
+ return this._isLoading;
79
+ }
80
+ __internal_load() {
81
+ if (this._loadPromise) return this._loadPromise;
82
+ this._isLoading = true;
83
+ const historyPromise = this.history?.load() ?? Promise.resolve(null);
84
+ const agentCardPromise = this.client.getAgentCard().catch(() => void 0);
85
+ this._loadPromise = Promise.all([historyPromise, agentCardPromise]).then(([repo, agentCard]) => {
86
+ if (agentCard) this.agentCardValue = agentCard;
87
+ if (repo) {
88
+ const messages = repo.messages.map((item) => item.message);
89
+ this.applyExternalMessages(messages);
90
+ }
91
+ }).catch((error) => {
92
+ this.onError?.(error instanceof Error ? error : new Error(String(error)));
93
+ }).finally(() => {
94
+ this._isLoading = false;
95
+ this.notifyUpdate();
96
+ });
97
+ this.notifyUpdate();
98
+ return this._loadPromise;
99
+ }
100
+ async append(message) {
101
+ const startRun = message.startRun ?? message.role === "user";
102
+ if (message.sourceId) this.messages = this.messages.filter((entry) => entry.id !== message.sourceId);
103
+ this.resetHead(message.parentId);
104
+ const threadMessage = fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS);
105
+ this.messages = [...this.messages, threadMessage];
106
+ this.notifyUpdate();
107
+ this.recordHistoryEntry(message.parentId ?? null, threadMessage);
108
+ if (!startRun) return;
109
+ await this.startRun(threadMessage);
110
+ }
111
+ async edit(message) {
112
+ await this.append(message);
113
+ }
114
+ async reload(parentId, _config = {}) {
115
+ this.resetHead(parentId);
116
+ this.notifyUpdate();
117
+ for (let i = this.messages.length - 1; i >= 0; i--) if (this.messages[i].role === "user") {
118
+ await this.startRun(this.messages[i]);
119
+ return;
120
+ }
121
+ }
122
+ async cancel() {
123
+ if (!this.abortController) return;
124
+ this.abortController.abort();
125
+ if (this.currentTask?.id) try {
126
+ const updated = await this.client.cancelTask(this.currentTask.id);
127
+ this.currentTask = updated;
128
+ } catch {}
129
+ }
130
+ applyExternalMessages(messages) {
131
+ this.assistantHistoryParents.clear();
132
+ this.messages = [...messages];
133
+ this.recordedHistoryIds.clear();
134
+ for (const message of this.messages) this.recordedHistoryIds.add(message.id);
135
+ this.currentTask = void 0;
136
+ this.currentArtifacts = [];
137
+ this.notifyUpdate();
138
+ }
139
+ async startRun(userThreadMessage) {
140
+ if (this.abortController) {
141
+ this.abortController.abort();
142
+ this.abortController = null;
143
+ }
144
+ const a2aMessage = this.threadMessageToA2AMessage(userThreadMessage);
145
+ if (this.currentTask && isTerminalTaskState(this.currentTask.status.state)) this.currentTask = void 0;
146
+ this.currentArtifacts = [];
147
+ const assistantParentId = userThreadMessage.id;
148
+ const assistantId = this.insertAssistantPlaceholder();
149
+ this.markPendingAssistantHistory(assistantId, assistantParentId);
150
+ const abortController = new AbortController();
151
+ this.abortController = abortController;
152
+ abortController.signal.addEventListener("abort", () => {
153
+ this.updateAssistantStatus(assistantId, {
154
+ type: "incomplete",
155
+ reason: "cancelled"
156
+ });
157
+ this.finishRun(abortController);
158
+ this.onCancel?.();
159
+ }, { once: true });
160
+ this.setRunning(true);
161
+ const supportsStreaming = this.agentCardValue?.capabilities?.streaming !== false;
162
+ try {
163
+ if (supportsStreaming) await this.runStreaming(a2aMessage, assistantId, abortController);
164
+ else await this.runSync(a2aMessage, assistantId, abortController);
165
+ } catch (error) {
166
+ if (!abortController.signal.aborted) {
167
+ const err = error instanceof Error ? error : new Error(String(error));
168
+ this.updateAssistantStatus(assistantId, {
169
+ type: "incomplete",
170
+ reason: "error"
171
+ });
172
+ this.onError?.(err);
173
+ this.pendingError = this.pendingError ?? err;
174
+ }
175
+ } finally {
176
+ this.finishRun(abortController);
177
+ }
178
+ if (this.pendingError) {
179
+ const err = this.pendingError;
180
+ this.pendingError = null;
181
+ throw err;
182
+ }
183
+ }
184
+ async runStreaming(a2aMessage, assistantId, abortController) {
185
+ const stream = this.client.streamMessage(a2aMessage, this.configuration, void 0, abortController.signal);
186
+ for await (const event of stream) {
187
+ if (abortController.signal.aborted) break;
188
+ this.handleStreamEvent(assistantId, event);
189
+ }
190
+ if (!abortController.signal.aborted) {
191
+ if (this.getAssistantStatus(assistantId)?.type === "running") this.updateAssistantStatus(assistantId, {
192
+ type: "complete",
193
+ reason: "stop"
194
+ });
195
+ }
196
+ }
197
+ async runSync(a2aMessage, assistantId, abortController) {
198
+ const result = await this.client.sendMessage(a2aMessage, this.configuration, void 0, abortController.signal);
199
+ if (abortController.signal.aborted) return;
200
+ if ("id" in result && "status" in result) this.handleTaskSnapshot(assistantId, result);
201
+ else if ("messageId" in result && "parts" in result) {
202
+ this.handleMessage(assistantId, result);
203
+ this.updateAssistantStatus(assistantId, {
204
+ type: "complete",
205
+ reason: "stop"
206
+ });
207
+ }
208
+ }
209
+ handleStreamEvent(assistantId, event) {
210
+ switch (event.type) {
211
+ case "statusUpdate":
212
+ this.handleStatusUpdate(assistantId, event.event);
213
+ break;
214
+ case "artifactUpdate":
215
+ this.handleArtifactUpdate(event.event);
216
+ break;
217
+ case "message":
218
+ this.handleMessage(assistantId, event.message);
219
+ break;
220
+ case "task":
221
+ this.handleTaskSnapshot(assistantId, event.task);
222
+ break;
223
+ }
224
+ }
225
+ handleStatusUpdate(assistantId, event) {
226
+ if (!this.currentTask) this.currentTask = {
227
+ id: event.taskId,
228
+ contextId: event.contextId,
229
+ status: event.status
230
+ };
231
+ else this.currentTask = {
232
+ ...this.currentTask,
233
+ status: event.status
234
+ };
235
+ if (event.contextId) this.contextId = event.contextId;
236
+ if (event.status.message) {
237
+ const content = a2aMessageToContent(event.status.message);
238
+ this.updateAssistantContent(assistantId, content);
239
+ }
240
+ const status = taskStateToMessageStatus(event.status.state);
241
+ this.updateAssistantStatus(assistantId, status);
242
+ this.notifyUpdate();
243
+ }
244
+ handleArtifactUpdate(event) {
245
+ const { artifact, append, lastChunk } = event;
246
+ const existingIdx = this.currentArtifacts.findIndex((a) => a.artifactId === artifact.artifactId);
247
+ let updated;
248
+ if (existingIdx >= 0 && append) {
249
+ const existing = this.currentArtifacts[existingIdx];
250
+ updated = {
251
+ ...existing,
252
+ parts: [...existing.parts, ...artifact.parts]
253
+ };
254
+ this.currentArtifacts = [
255
+ ...this.currentArtifacts.slice(0, existingIdx),
256
+ updated,
257
+ ...this.currentArtifacts.slice(existingIdx + 1)
258
+ ];
259
+ } else if (existingIdx >= 0) {
260
+ updated = artifact;
261
+ this.currentArtifacts = [
262
+ ...this.currentArtifacts.slice(0, existingIdx),
263
+ updated,
264
+ ...this.currentArtifacts.slice(existingIdx + 1)
265
+ ];
266
+ } else {
267
+ updated = artifact;
268
+ this.currentArtifacts = [...this.currentArtifacts, updated];
269
+ }
270
+ if (lastChunk) this.onArtifactComplete?.(updated);
271
+ this.notifyUpdate();
272
+ }
273
+ handleMessage(assistantId, message) {
274
+ if (message.role !== "agent") return;
275
+ const content = a2aMessageToContent(message);
276
+ this.updateAssistantContent(assistantId, content);
277
+ this.notifyUpdate();
278
+ }
279
+ handleTaskSnapshot(assistantId, task) {
280
+ this.currentTask = task;
281
+ if (task.contextId) this.contextId = task.contextId;
282
+ if (task.artifacts) this.currentArtifacts = task.artifacts;
283
+ if (task.status.message) {
284
+ const content = a2aMessageToContent(task.status.message);
285
+ this.updateAssistantContent(assistantId, content);
286
+ }
287
+ const status = taskStateToMessageStatus(task.status.state);
288
+ this.updateAssistantStatus(assistantId, status);
289
+ this.notifyUpdate();
290
+ }
291
+ threadMessageToA2AMessage(message) {
292
+ const parts = [];
293
+ if (message.role === "user") {
294
+ for (const part of message.content) if (part.type === "text") parts.push({ text: part.text });
295
+ else if (part.type === "image") parts.push({
296
+ url: part.image,
297
+ mediaType: "image/*"
298
+ });
299
+ }
300
+ const a2aMsg = {
301
+ messageId: message.id,
302
+ role: "user",
303
+ parts
304
+ };
305
+ if (this.contextId) a2aMsg.contextId = this.contextId;
306
+ if (this.currentTask?.id && !isTerminalTaskState(this.currentTask.status.state)) a2aMsg.taskId = this.currentTask.id;
307
+ return a2aMsg;
308
+ }
309
+ insertAssistantPlaceholder() {
310
+ const id = generateId();
311
+ const assistant = {
312
+ id,
313
+ role: "assistant",
314
+ createdAt: /* @__PURE__ */ new Date(),
315
+ status: { type: "running" },
316
+ content: [],
317
+ metadata: {
318
+ unstable_state: null,
319
+ unstable_annotations: [],
320
+ unstable_data: [],
321
+ steps: [],
322
+ custom: {}
323
+ }
324
+ };
325
+ this.messages = [...this.messages, assistant];
326
+ this.notifyUpdate();
327
+ return id;
328
+ }
329
+ updateAssistantContent(messageId, content) {
330
+ this.messages = this.messages.map((message) => {
331
+ if (message.id !== messageId || message.role !== "assistant") return message;
332
+ return {
333
+ ...message,
334
+ content
335
+ };
336
+ });
337
+ }
338
+ updateAssistantStatus(messageId, status) {
339
+ let touched = false;
340
+ this.messages = this.messages.map((message) => {
341
+ if (message.id !== messageId || message.role !== "assistant") return message;
342
+ touched = true;
343
+ return {
344
+ ...message,
345
+ status
346
+ };
347
+ });
348
+ if (touched) {
349
+ this.notifyUpdate();
350
+ if (status.type === "complete" || status.type === "incomplete") this.persistAssistantHistory(messageId);
351
+ }
352
+ }
353
+ getAssistantStatus(messageId) {
354
+ return this.messages.find((m) => m.id === messageId && m.role === "assistant")?.status;
355
+ }
356
+ setRunning(running) {
357
+ this.isRunningFlag = running;
358
+ this.notifyUpdate();
359
+ }
360
+ finishRun(controller) {
361
+ if (this.abortController === controller) this.abortController = null;
362
+ this.setRunning(false);
363
+ }
364
+ resetHead(parentId) {
365
+ if (!parentId) {
366
+ if (this.messages.length) this.messages = [];
367
+ return;
368
+ }
369
+ const idx = this.messages.findIndex((message) => message.id === parentId);
370
+ if (idx === -1) return;
371
+ this.messages = this.messages.slice(0, idx + 1);
372
+ }
373
+ recordHistoryEntry(parentId, message) {
374
+ this.appendHistoryItem(parentId, message);
375
+ }
376
+ markPendingAssistantHistory(messageId, parentId) {
377
+ if (!this.history) return;
378
+ this.assistantHistoryParents.set(messageId, parentId);
379
+ }
380
+ persistAssistantHistory(messageId) {
381
+ if (!this.history) return;
382
+ const parentId = this.assistantHistoryParents.get(messageId);
383
+ if (parentId === void 0) return;
384
+ const message = this.messages.find((m) => m.id === messageId);
385
+ if (!message || message.role !== "assistant") return;
386
+ if (message.status?.type !== "complete" && message.status?.type !== "incomplete") return;
387
+ this.assistantHistoryParents.delete(messageId);
388
+ this.appendHistoryItem(parentId, message);
389
+ }
390
+ appendHistoryItem(parentId, message) {
391
+ if (!this.history || this.recordedHistoryIds.has(message.id)) return;
392
+ this.recordedHistoryIds.add(message.id);
393
+ this.history.append({
394
+ parentId,
395
+ message
396
+ }).catch(() => {
397
+ this.recordedHistoryIds.delete(message.id);
398
+ });
399
+ }
400
+ };
401
+ //#endregion
402
+ export { A2AThreadRuntimeCore };
403
+
483
404
  //# sourceMappingURL=A2AThreadRuntimeCore.js.map