@assistant-ui/react-a2a 0.2.47 → 0.2.48
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/{A2AThreadRuntimeCore.d.ts → A2AThreadController.d.ts} +7 -17
- package/dist/A2AThreadController.d.ts.map +1 -0
- package/dist/{A2AThreadRuntimeCore.js → A2AThreadController.js} +79 -152
- package/dist/A2AThreadController.js.map +1 -0
- package/dist/a2aThreadState.d.ts +56 -0
- package/dist/a2aThreadState.d.ts.map +1 -0
- package/dist/a2aThreadState.js +152 -0
- package/dist/a2aThreadState.js.map +1 -0
- package/dist/useA2ARuntime.js +2 -2
- package/dist/useA2ARuntime.js.map +1 -1
- package/package.json +9 -9
- package/src/{A2AThreadRuntimeCore.test.ts → A2AThreadController.test.ts} +48 -20
- package/src/{A2AThreadRuntimeCore.ts → A2AThreadController.ts} +93 -220
- package/src/a2aThreadState.test.ts +244 -0
- package/src/a2aThreadState.ts +242 -0
- package/src/useA2ARuntime.fast-refresh.test.tsx +6 -6
- package/src/useA2ARuntime.ts +2 -2
- package/dist/A2AThreadRuntimeCore.d.ts.map +0 -1
- package/dist/A2AThreadRuntimeCore.js.map +0 -1
|
@@ -15,15 +15,12 @@ import type {
|
|
|
15
15
|
} from "@assistant-ui/core";
|
|
16
16
|
import {
|
|
17
17
|
appendToolInteraction,
|
|
18
|
+
createHistoryWriteChain,
|
|
18
19
|
createMessageRepositorySession,
|
|
19
20
|
invokeUserCallback,
|
|
20
21
|
RunLeases,
|
|
21
22
|
} from "@assistant-ui/core/internal";
|
|
22
|
-
import {
|
|
23
|
-
applyA2uiOperations,
|
|
24
|
-
surfaceToPresentToolCall,
|
|
25
|
-
type A2uiState,
|
|
26
|
-
} from "@assistant-ui/generative-ui/a2ui";
|
|
23
|
+
import { surfaceToPresentToolCall } from "@assistant-ui/generative-ui/a2ui";
|
|
27
24
|
import type { A2AClient } from "./A2AClient";
|
|
28
25
|
import type {
|
|
29
26
|
A2AArtifact,
|
|
@@ -38,11 +35,16 @@ import type {
|
|
|
38
35
|
|
|
39
36
|
import {
|
|
40
37
|
a2aMessageToContent,
|
|
41
|
-
a2uiPartsToOperations,
|
|
42
38
|
isTerminalTaskState,
|
|
43
39
|
threadMessageToA2AMessage,
|
|
44
40
|
taskStateToMessageStatus,
|
|
45
41
|
} from "./conversions";
|
|
42
|
+
import {
|
|
43
|
+
createA2AThreadState,
|
|
44
|
+
reduceA2AThreadState,
|
|
45
|
+
type A2AThreadAction,
|
|
46
|
+
type A2AThreadState,
|
|
47
|
+
} from "./a2aThreadState";
|
|
46
48
|
|
|
47
49
|
const INITIAL_AGENT_CARD_RETRY_DELAY_MS = 5_000;
|
|
48
50
|
const MAX_AGENT_CARD_RETRY_DELAY_MS = 5 * 60_000;
|
|
@@ -55,7 +57,7 @@ const storedArtifact = (artifact: A2AArtifact): A2AArtifact => ({
|
|
|
55
57
|
parts: artifact.parts.map(({ raw: _raw, ...part }) => part),
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
export type
|
|
60
|
+
export type A2AThreadControllerOptions = {
|
|
59
61
|
client: A2AClient;
|
|
60
62
|
contextId?: string | undefined;
|
|
61
63
|
configuration?: A2ASendMessageConfiguration | undefined;
|
|
@@ -80,16 +82,9 @@ const invokeRuntimeCallback = <TArgs extends readonly unknown[]>(
|
|
|
80
82
|
void invokeUserCallback("react-a2a", name, callback, ...args);
|
|
81
83
|
};
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
...artifact,
|
|
86
|
-
parts: Array.isArray(artifact.parts) ? artifact.parts : [],
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export class A2AThreadRuntimeCore {
|
|
85
|
+
export class A2AThreadController {
|
|
91
86
|
private client: A2AClient;
|
|
92
|
-
private
|
|
87
|
+
private state: A2AThreadState;
|
|
93
88
|
private configuration: A2ASendMessageConfiguration | undefined;
|
|
94
89
|
private onError: ((error: Error) => void) | undefined;
|
|
95
90
|
private onCancel: (() => void) | undefined;
|
|
@@ -109,26 +104,16 @@ export class A2AThreadRuntimeCore {
|
|
|
109
104
|
|
|
110
105
|
private runtime: AssistantRuntime | undefined;
|
|
111
106
|
private readonly session = createMessageRepositorySession();
|
|
112
|
-
private isRunningFlag = false;
|
|
113
107
|
private abortController: AbortController | null = null;
|
|
114
108
|
private readonly runLeases = new RunLeases();
|
|
115
109
|
private pendingError: Error | null = null;
|
|
116
110
|
|
|
117
|
-
// A2A-specific state
|
|
118
|
-
private currentTask: A2ATask | undefined;
|
|
119
|
-
private currentArtifacts: A2AArtifact[] = [];
|
|
120
|
-
private a2uiState: A2uiState = new Map();
|
|
121
|
-
private readonly a2uiMessageIds = new Set<string>();
|
|
122
|
-
private agentCardValue: A2AAgentCard | undefined;
|
|
123
|
-
|
|
124
111
|
// History tracking
|
|
125
112
|
private readonly assistantHistoryParents = new Map<string, string | null>();
|
|
126
113
|
private readonly recordedHistoryIds = new Set<string>();
|
|
127
|
-
private readonly historyWrites =
|
|
128
|
-
private _isLoading = false;
|
|
114
|
+
private readonly historyWrites = createHistoryWriteChain();
|
|
129
115
|
private _loadPromise: Promise<void> | undefined;
|
|
130
116
|
private _historyLoadGeneration = 0;
|
|
131
|
-
private _loadRequested = false;
|
|
132
117
|
private _agentCardPromise: Promise<void> | undefined;
|
|
133
118
|
private _agentCardRetryAfter = 0;
|
|
134
119
|
private _agentCardRetryDelay = INITIAL_AGENT_CARD_RETRY_DELAY_MS;
|
|
@@ -136,9 +121,9 @@ export class A2AThreadRuntimeCore {
|
|
|
136
121
|
|
|
137
122
|
private lastOptionsContextId: string | undefined;
|
|
138
123
|
|
|
139
|
-
constructor(options:
|
|
124
|
+
constructor(options: A2AThreadControllerOptions) {
|
|
140
125
|
this.client = options.client;
|
|
141
|
-
this.
|
|
126
|
+
this.state = createA2AThreadState(options.contextId);
|
|
142
127
|
this.lastOptionsContextId = options.contextId;
|
|
143
128
|
this.configuration = options.configuration;
|
|
144
129
|
this.onError = options.onError;
|
|
@@ -154,25 +139,32 @@ export class A2AThreadRuntimeCore {
|
|
|
154
139
|
|
|
155
140
|
getSnapshot = () =>
|
|
156
141
|
(this.snapshot ??= {
|
|
157
|
-
isLoading: this.
|
|
158
|
-
isRunning: this.
|
|
142
|
+
isLoading: this.state.isLoading,
|
|
143
|
+
isRunning: this.state.isRunning,
|
|
159
144
|
messageRepository: this.session.export(),
|
|
160
|
-
task: this.currentTask,
|
|
161
|
-
artifacts: this.currentArtifacts,
|
|
162
|
-
agentCard: this.
|
|
145
|
+
task: this.state.currentTask,
|
|
146
|
+
artifacts: this.state.currentArtifacts,
|
|
147
|
+
agentCard: this.state.agentCard,
|
|
163
148
|
});
|
|
164
149
|
|
|
150
|
+
private dispatch(action: A2AThreadAction): void {
|
|
151
|
+
this.state = reduceA2AThreadState(this.state, action);
|
|
152
|
+
}
|
|
153
|
+
|
|
165
154
|
private publishUpdate(): void {
|
|
166
155
|
this.snapshot = undefined;
|
|
167
156
|
for (const listener of this.listeners) listener();
|
|
168
157
|
}
|
|
169
158
|
|
|
170
|
-
updateOptions(options:
|
|
159
|
+
updateOptions(options: A2AThreadControllerOptions) {
|
|
171
160
|
this.client = options.client;
|
|
172
161
|
// The option only seeds the context; a re-render with the same value must
|
|
173
162
|
// not clobber a server-assigned contextId learned from the stream.
|
|
174
163
|
if (options.contextId !== this.lastOptionsContextId) {
|
|
175
|
-
this.
|
|
164
|
+
this.dispatch({
|
|
165
|
+
type: "contextLearned",
|
|
166
|
+
contextId: options.contextId,
|
|
167
|
+
});
|
|
176
168
|
this.lastOptionsContextId = options.contextId;
|
|
177
169
|
}
|
|
178
170
|
this.configuration = options.configuration;
|
|
@@ -183,7 +175,7 @@ export class A2AThreadRuntimeCore {
|
|
|
183
175
|
this.history = options.history;
|
|
184
176
|
|
|
185
177
|
if (
|
|
186
|
-
this.
|
|
178
|
+
this.state.loadRequested &&
|
|
187
179
|
!this._loadPromise &&
|
|
188
180
|
!previousHistory &&
|
|
189
181
|
options.history &&
|
|
@@ -197,13 +189,15 @@ export class A2AThreadRuntimeCore {
|
|
|
197
189
|
* switches, deletes, and cancel resyncs, which must keep the live context. */
|
|
198
190
|
resetContext(): void {
|
|
199
191
|
this._historyLoadGeneration++;
|
|
200
|
-
const wasLoading = this.
|
|
201
|
-
this._isLoading = false;
|
|
192
|
+
const wasLoading = this.state.isLoading;
|
|
202
193
|
// Restore the seed before aborting: an onCancel callback that starts a
|
|
203
194
|
// new run must not pick up the old thread's context, and its controller
|
|
204
195
|
// must not be discarded.
|
|
205
196
|
const controller = this.abortController;
|
|
206
|
-
this.
|
|
197
|
+
this.dispatch({
|
|
198
|
+
type: "resetContext",
|
|
199
|
+
contextId: this.lastOptionsContextId,
|
|
200
|
+
});
|
|
207
201
|
if (controller) {
|
|
208
202
|
controller.abort();
|
|
209
203
|
if (this.abortController === controller) {
|
|
@@ -290,23 +284,23 @@ export class A2AThreadRuntimeCore {
|
|
|
290
284
|
}
|
|
291
285
|
|
|
292
286
|
getTask(): A2ATask | undefined {
|
|
293
|
-
return this.currentTask;
|
|
287
|
+
return this.state.currentTask;
|
|
294
288
|
}
|
|
295
289
|
|
|
296
290
|
getArtifacts(): readonly A2AArtifact[] {
|
|
297
|
-
return this.currentArtifacts;
|
|
291
|
+
return this.state.currentArtifacts;
|
|
298
292
|
}
|
|
299
293
|
|
|
300
294
|
getAgentCard(): A2AAgentCard | undefined {
|
|
301
|
-
return this.
|
|
295
|
+
return this.state.agentCard;
|
|
302
296
|
}
|
|
303
297
|
|
|
304
298
|
isRunning(): boolean {
|
|
305
|
-
return this.
|
|
299
|
+
return this.state.isRunning;
|
|
306
300
|
}
|
|
307
301
|
|
|
308
302
|
get isLoading(): boolean {
|
|
309
|
-
return this.
|
|
303
|
+
return this.state.isLoading;
|
|
310
304
|
}
|
|
311
305
|
|
|
312
306
|
private loadAgentCard(): Promise<void> {
|
|
@@ -314,7 +308,10 @@ export class A2AThreadRuntimeCore {
|
|
|
314
308
|
|
|
315
309
|
this._agentCardPromise ??= this.client.getAgentCard().then(
|
|
316
310
|
(agentCard) => {
|
|
317
|
-
this.
|
|
311
|
+
this.dispatch({
|
|
312
|
+
type: "agentCardLoaded",
|
|
313
|
+
agentCard,
|
|
314
|
+
});
|
|
318
315
|
this._agentCardRetryAfter = 0;
|
|
319
316
|
this._agentCardRetryDelay = INITIAL_AGENT_CARD_RETRY_DELAY_MS;
|
|
320
317
|
this._agentCardDiscoveryFailed = false;
|
|
@@ -354,13 +351,13 @@ export class A2AThreadRuntimeCore {
|
|
|
354
351
|
}
|
|
355
352
|
|
|
356
353
|
__internal_load(): Promise<void> {
|
|
357
|
-
this.
|
|
354
|
+
this.dispatch({ type: "loadRequested" });
|
|
358
355
|
const agentCardPromise = this.loadAgentCard();
|
|
359
356
|
|
|
360
357
|
if (this._loadPromise) return this._loadPromise;
|
|
361
358
|
if (!this.history) return agentCardPromise;
|
|
362
359
|
|
|
363
|
-
this.
|
|
360
|
+
this.dispatch({ type: "loadStarted" });
|
|
364
361
|
|
|
365
362
|
const generation = this._historyLoadGeneration;
|
|
366
363
|
const historyPromise = this.history.load();
|
|
@@ -383,7 +380,7 @@ export class A2AThreadRuntimeCore {
|
|
|
383
380
|
})
|
|
384
381
|
.finally(() => {
|
|
385
382
|
if (generation !== this._historyLoadGeneration) return;
|
|
386
|
-
this.
|
|
383
|
+
this.dispatch({ type: "loadFinished" });
|
|
387
384
|
this.publishUpdate();
|
|
388
385
|
});
|
|
389
386
|
|
|
@@ -448,7 +445,7 @@ export class A2AThreadRuntimeCore {
|
|
|
448
445
|
// Read the server target before aborting: the abort listener runs the
|
|
449
446
|
// onCancel callback synchronously, which may clear the thread and with it
|
|
450
447
|
// the task this cancellation is for, or start a new run.
|
|
451
|
-
const task = this.currentTask;
|
|
448
|
+
const task = this.state.currentTask;
|
|
452
449
|
const lease = this.runLeases.current();
|
|
453
450
|
|
|
454
451
|
// Abort locally first so the stream stops immediately
|
|
@@ -462,8 +459,11 @@ export class A2AThreadRuntimeCore {
|
|
|
462
459
|
// or a cleared thread replaces the task object; a follow-up run that
|
|
463
460
|
// has not emitted yet keeps it, so the run lease is what rules
|
|
464
461
|
// that case out.
|
|
465
|
-
if (this.currentTask === task && lease.isCurrent()) {
|
|
466
|
-
this.
|
|
462
|
+
if (this.state.currentTask === task && lease.isCurrent()) {
|
|
463
|
+
this.dispatch({
|
|
464
|
+
type: "taskReplaced",
|
|
465
|
+
task: updated,
|
|
466
|
+
});
|
|
467
467
|
this.publishUpdate();
|
|
468
468
|
}
|
|
469
469
|
} catch {
|
|
@@ -494,10 +494,7 @@ export class A2AThreadRuntimeCore {
|
|
|
494
494
|
for (const { message } of this.getMessageRepository().messages) {
|
|
495
495
|
this.recordedHistoryIds.add(message.id);
|
|
496
496
|
}
|
|
497
|
-
this.
|
|
498
|
-
this.currentArtifacts = [];
|
|
499
|
-
this.a2uiState = new Map();
|
|
500
|
-
this.a2uiMessageIds.clear();
|
|
497
|
+
this.dispatch({ type: "reset" });
|
|
501
498
|
this.publishUpdate();
|
|
502
499
|
}
|
|
503
500
|
|
|
@@ -552,25 +549,15 @@ export class A2AThreadRuntimeCore {
|
|
|
552
549
|
}
|
|
553
550
|
|
|
554
551
|
const a2aMessage = threadMessageToA2AMessage(userThreadMessage, {
|
|
555
|
-
contextId: this.contextId,
|
|
552
|
+
contextId: this.state.contextId,
|
|
556
553
|
taskId:
|
|
557
|
-
this.currentTask?.id &&
|
|
558
|
-
!isTerminalTaskState(this.currentTask.status.state)
|
|
559
|
-
? this.currentTask.id
|
|
554
|
+
this.state.currentTask?.id &&
|
|
555
|
+
!isTerminalTaskState(this.state.currentTask.status.state)
|
|
556
|
+
? this.state.currentTask.id
|
|
560
557
|
: undefined,
|
|
561
558
|
});
|
|
562
559
|
|
|
563
|
-
|
|
564
|
-
if (
|
|
565
|
-
this.currentTask &&
|
|
566
|
-
isTerminalTaskState(this.currentTask.status.state)
|
|
567
|
-
) {
|
|
568
|
-
this.currentTask = undefined;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
this.currentArtifacts = [];
|
|
572
|
-
this.a2uiState = new Map();
|
|
573
|
-
this.a2uiMessageIds.clear();
|
|
560
|
+
this.dispatch({ type: "prepareRun" });
|
|
574
561
|
|
|
575
562
|
const assistantParentId = userThreadMessage.id;
|
|
576
563
|
const assistantId = this.insertAssistantPlaceholder(assistantParentId);
|
|
@@ -598,7 +585,7 @@ export class A2AThreadRuntimeCore {
|
|
|
598
585
|
if (!(await this.waitForAgentCard(abortController.signal))) return;
|
|
599
586
|
|
|
600
587
|
const supportsStreaming =
|
|
601
|
-
this.
|
|
588
|
+
this.state.agentCard?.capabilities?.streaming !== false;
|
|
602
589
|
if (supportsStreaming) {
|
|
603
590
|
await this.runStreaming(a2aMessage, assistantId, abortController);
|
|
604
591
|
} else {
|
|
@@ -718,22 +705,12 @@ export class A2AThreadRuntimeCore {
|
|
|
718
705
|
assistantId: string,
|
|
719
706
|
event: A2ATaskStatusUpdateEvent,
|
|
720
707
|
) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
status: event.status,
|
|
726
|
-
};
|
|
727
|
-
} else {
|
|
728
|
-
this.currentTask = { ...this.currentTask, status: event.status };
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (event.contextId) {
|
|
732
|
-
this.contextId = event.contextId;
|
|
733
|
-
}
|
|
708
|
+
this.dispatch({
|
|
709
|
+
type: "statusUpdate",
|
|
710
|
+
event,
|
|
711
|
+
});
|
|
734
712
|
|
|
735
713
|
if (event.status.message) {
|
|
736
|
-
this.applyA2uiMessage(event.status.message);
|
|
737
714
|
const content = a2aMessageToContent(event.status.message);
|
|
738
715
|
this.updateAssistantContent(assistantId, content);
|
|
739
716
|
}
|
|
@@ -748,41 +725,17 @@ export class A2AThreadRuntimeCore {
|
|
|
748
725
|
assistantId: string,
|
|
749
726
|
event: A2ATaskArtifactUpdateEvent,
|
|
750
727
|
) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (existingIdx >= 0 && append) {
|
|
759
|
-
const existing = this.currentArtifacts[existingIdx]!;
|
|
760
|
-
updated = {
|
|
761
|
-
...existing,
|
|
762
|
-
parts: [...existing.parts, ...artifact.parts],
|
|
763
|
-
};
|
|
764
|
-
this.currentArtifacts = [
|
|
765
|
-
...this.currentArtifacts.slice(0, existingIdx),
|
|
766
|
-
updated,
|
|
767
|
-
...this.currentArtifacts.slice(existingIdx + 1),
|
|
768
|
-
];
|
|
769
|
-
} else if (existingIdx >= 0) {
|
|
770
|
-
updated = artifact;
|
|
771
|
-
this.currentArtifacts = [
|
|
772
|
-
...this.currentArtifacts.slice(0, existingIdx),
|
|
773
|
-
updated,
|
|
774
|
-
...this.currentArtifacts.slice(existingIdx + 1),
|
|
775
|
-
];
|
|
776
|
-
} else {
|
|
777
|
-
updated = artifact;
|
|
778
|
-
this.currentArtifacts = [...this.currentArtifacts, updated];
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
this.applyA2uiParts(artifact.parts);
|
|
728
|
+
this.dispatch({
|
|
729
|
+
type: "artifactUpdate",
|
|
730
|
+
event,
|
|
731
|
+
});
|
|
732
|
+
const updated = this.state.currentArtifacts.find(
|
|
733
|
+
({ artifactId }) => artifactId === event.artifact.artifactId,
|
|
734
|
+
)!;
|
|
782
735
|
this.updateAssistantArtifacts(assistantId);
|
|
783
736
|
this.rebuildAssistantA2uiSurfaces(assistantId);
|
|
784
737
|
|
|
785
|
-
if (lastChunk) {
|
|
738
|
+
if (event.lastChunk) {
|
|
786
739
|
invokeRuntimeCallback(
|
|
787
740
|
"onArtifactComplete",
|
|
788
741
|
this.onArtifactComplete,
|
|
@@ -796,63 +749,26 @@ export class A2AThreadRuntimeCore {
|
|
|
796
749
|
private handleMessage(assistantId: string, message: A2AMessage) {
|
|
797
750
|
if (message.role !== "agent") return;
|
|
798
751
|
|
|
799
|
-
this.
|
|
752
|
+
this.dispatch({
|
|
753
|
+
type: "a2uiMessage",
|
|
754
|
+
message,
|
|
755
|
+
});
|
|
800
756
|
const content = a2aMessageToContent(message);
|
|
801
757
|
this.updateAssistantContent(assistantId, content);
|
|
802
758
|
this.publishUpdate();
|
|
803
759
|
}
|
|
804
760
|
|
|
805
761
|
private handleTaskSnapshot(assistantId: string, task: A2ATask) {
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
: [];
|
|
812
|
-
const history =
|
|
813
|
-
task.history === undefined
|
|
814
|
-
? undefined
|
|
815
|
-
: Array.isArray(task.history)
|
|
816
|
-
? task.history
|
|
817
|
-
: [];
|
|
818
|
-
this.currentTask = {
|
|
819
|
-
...task,
|
|
820
|
-
...(artifacts === undefined ? {} : { artifacts }),
|
|
821
|
-
...(history === undefined ? {} : { history }),
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
if (task.contextId) {
|
|
825
|
-
this.contextId = task.contextId;
|
|
826
|
-
}
|
|
827
|
-
const isCompleteSnapshot = history !== undefined && artifacts !== undefined;
|
|
828
|
-
const artifactsToApply = isCompleteSnapshot
|
|
829
|
-
? artifacts
|
|
830
|
-
: artifacts?.filter(
|
|
831
|
-
(artifact) =>
|
|
832
|
-
!this.currentArtifacts.some(
|
|
833
|
-
({ artifactId }) => artifactId === artifact.artifactId,
|
|
834
|
-
),
|
|
835
|
-
);
|
|
836
|
-
|
|
837
|
-
if (isCompleteSnapshot) {
|
|
838
|
-
this.currentArtifacts = artifacts;
|
|
839
|
-
this.a2uiState = new Map();
|
|
840
|
-
this.a2uiMessageIds.clear();
|
|
841
|
-
} else if (artifactsToApply) {
|
|
842
|
-
this.currentArtifacts = [...this.currentArtifacts, ...artifactsToApply];
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
for (const message of history ?? []) {
|
|
846
|
-
if (message.role === "agent") this.applyA2uiMessage(message, true);
|
|
847
|
-
}
|
|
848
|
-
for (const artifact of artifactsToApply ?? []) {
|
|
849
|
-
this.applyA2uiParts(artifact.parts);
|
|
850
|
-
}
|
|
851
|
-
if (task.status.message) {
|
|
852
|
-
this.applyA2uiMessage(task.status.message, true);
|
|
853
|
-
}
|
|
762
|
+
const previousArtifacts = this.state.currentArtifacts;
|
|
763
|
+
this.dispatch({
|
|
764
|
+
type: "taskSnapshot",
|
|
765
|
+
task,
|
|
766
|
+
});
|
|
854
767
|
|
|
855
|
-
if (
|
|
768
|
+
if (
|
|
769
|
+
(task.history !== undefined && task.artifacts !== undefined) ||
|
|
770
|
+
this.state.currentArtifacts.length > previousArtifacts.length
|
|
771
|
+
) {
|
|
856
772
|
this.updateAssistantArtifacts(assistantId);
|
|
857
773
|
}
|
|
858
774
|
|
|
@@ -906,23 +822,9 @@ export class A2AThreadRuntimeCore {
|
|
|
906
822
|
});
|
|
907
823
|
}
|
|
908
824
|
|
|
909
|
-
private applyA2uiMessage(message: A2AMessage, replay = false) {
|
|
910
|
-
if (message.messageId) {
|
|
911
|
-
if (replay && this.a2uiMessageIds.has(message.messageId)) return;
|
|
912
|
-
this.a2uiMessageIds.add(message.messageId);
|
|
913
|
-
}
|
|
914
|
-
this.applyA2uiParts(message.parts);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
private applyA2uiParts(parts: readonly A2AMessage["parts"][number][]) {
|
|
918
|
-
const operations = a2uiPartsToOperations(parts);
|
|
919
|
-
if (operations.length === 0) return;
|
|
920
|
-
this.a2uiState = applyA2uiOperations(this.a2uiState, operations).state;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
825
|
private a2uiSurfaceParts(): ThreadAssistantMessagePart[] {
|
|
924
826
|
const parts: ThreadAssistantMessagePart[] = [];
|
|
925
|
-
for (const [surfaceId, surface] of this.a2uiState) {
|
|
827
|
+
for (const [surfaceId, surface] of this.state.a2uiState) {
|
|
926
828
|
const { toolCall } = surfaceToPresentToolCall(surfaceId, surface);
|
|
927
829
|
if (!toolCall) continue;
|
|
928
830
|
parts.push({
|
|
@@ -1003,7 +905,7 @@ export class A2AThreadRuntimeCore {
|
|
|
1003
905
|
...custom,
|
|
1004
906
|
a2a: {
|
|
1005
907
|
...a2a,
|
|
1006
|
-
artifacts: this.currentArtifacts.map(storedArtifact),
|
|
908
|
+
artifacts: this.state.currentArtifacts.map(storedArtifact),
|
|
1007
909
|
},
|
|
1008
910
|
},
|
|
1009
911
|
},
|
|
@@ -1042,7 +944,9 @@ export class A2AThreadRuntimeCore {
|
|
|
1042
944
|
// --- Lifecycle helpers ---
|
|
1043
945
|
|
|
1044
946
|
private setRunning(running: boolean) {
|
|
1045
|
-
this.
|
|
947
|
+
this.dispatch({
|
|
948
|
+
type: running ? "runStarted" : "runFinished",
|
|
949
|
+
});
|
|
1046
950
|
this.publishUpdate();
|
|
1047
951
|
}
|
|
1048
952
|
|
|
@@ -1088,7 +992,7 @@ export class A2AThreadRuntimeCore {
|
|
|
1088
992
|
if (this.recordedHistoryIds.has(messageId)) {
|
|
1089
993
|
if (this.history.update) {
|
|
1090
994
|
const update = this.history.update.bind(this.history);
|
|
1091
|
-
const write = this.
|
|
995
|
+
const write = this.historyWrites.chain(messageId, () =>
|
|
1092
996
|
update({ parentId: resolvedParentId, message }),
|
|
1093
997
|
);
|
|
1094
998
|
if (!isPausing) {
|
|
@@ -1099,7 +1003,7 @@ export class A2AThreadRuntimeCore {
|
|
|
1099
1003
|
this.recordedHistoryIds.add(messageId);
|
|
1100
1004
|
},
|
|
1101
1005
|
(error) => {
|
|
1102
|
-
const pending = this.historyWrites.
|
|
1006
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1103
1007
|
if (pending === undefined || pending === write) {
|
|
1104
1008
|
this.assistantHistoryParents.set(messageId, resolvedParentId);
|
|
1105
1009
|
}
|
|
@@ -1119,7 +1023,7 @@ export class A2AThreadRuntimeCore {
|
|
|
1119
1023
|
this.assistantHistoryParents.delete(messageId);
|
|
1120
1024
|
}
|
|
1121
1025
|
void write.catch((error) => {
|
|
1122
|
-
const pending = this.historyWrites.
|
|
1026
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1123
1027
|
if (pending === undefined || pending === write) {
|
|
1124
1028
|
this.assistantHistoryParents.set(messageId, resolvedParentId);
|
|
1125
1029
|
}
|
|
@@ -1134,7 +1038,7 @@ export class A2AThreadRuntimeCore {
|
|
|
1134
1038
|
if (!this.history || this.recordedHistoryIds.has(message.id)) return;
|
|
1135
1039
|
this.recordedHistoryIds.add(message.id);
|
|
1136
1040
|
const append = this.history.append.bind(this.history);
|
|
1137
|
-
const write = this.
|
|
1041
|
+
const write = this.historyWrites.chain(message.id, () =>
|
|
1138
1042
|
append({ parentId, message }),
|
|
1139
1043
|
);
|
|
1140
1044
|
void write.catch(() => {
|
|
@@ -1142,35 +1046,4 @@ export class A2AThreadRuntimeCore {
|
|
|
1142
1046
|
});
|
|
1143
1047
|
return write;
|
|
1144
1048
|
}
|
|
1145
|
-
|
|
1146
|
-
private chainHistoryWrite(
|
|
1147
|
-
id: string,
|
|
1148
|
-
write: () => Promise<void>,
|
|
1149
|
-
): Promise<void> {
|
|
1150
|
-
const pending = this.historyWrites.get(id);
|
|
1151
|
-
let next: Promise<void>;
|
|
1152
|
-
if (pending) {
|
|
1153
|
-
next = pending.then(write, write);
|
|
1154
|
-
} else {
|
|
1155
|
-
try {
|
|
1156
|
-
next = Promise.resolve(write());
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
next = Promise.reject(error);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
this.historyWrites.set(id, next);
|
|
1162
|
-
void next.then(
|
|
1163
|
-
() => {
|
|
1164
|
-
if (this.historyWrites.get(id) === next) {
|
|
1165
|
-
this.historyWrites.delete(id);
|
|
1166
|
-
}
|
|
1167
|
-
},
|
|
1168
|
-
() => {
|
|
1169
|
-
if (this.historyWrites.get(id) === next) {
|
|
1170
|
-
this.historyWrites.delete(id);
|
|
1171
|
-
}
|
|
1172
|
-
},
|
|
1173
|
-
);
|
|
1174
|
-
return next;
|
|
1175
|
-
}
|
|
1176
1049
|
}
|