@assistant-ui/react-ag-ui 0.0.71 → 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/README.md +1 -1
- package/dist/agUiExtras.js +1 -2
- package/dist/hooks.js +1 -2
- package/dist/runtime/{AgUiThreadRuntimeCore.d.ts → AgUiThreadController.d.ts} +6 -12
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -0
- package/dist/runtime/{AgUiThreadRuntimeCore.js → AgUiThreadController.js} +567 -194
- package/dist/runtime/AgUiThreadController.js.map +1 -0
- package/dist/runtime/adapter/conversions.d.ts +4 -4
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +120 -35
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/replay-content.d.ts +9 -0
- package/dist/runtime/adapter/replay-content.d.ts.map +1 -0
- package/dist/runtime/adapter/replay-content.js +185 -0
- package/dist/runtime/adapter/replay-content.js.map +1 -0
- package/dist/runtime/adapter/run-aggregator.d.ts +3 -5
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +48 -63
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +0 -24
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +2 -24
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +2 -0
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.js +23 -6
- package/dist/runtime/adapter/tool-approval.js.map +1 -1
- package/dist/runtime/agUiThreadState.d.ts +60 -0
- package/dist/runtime/agUiThreadState.d.ts.map +1 -0
- package/dist/runtime/agUiThreadState.js +94 -0
- package/dist/runtime/agUiThreadState.js.map +1 -0
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +46 -50
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/interrupt-internals.js +1 -2
- package/dist/runtime/logger.js +1 -2
- package/dist/runtime/mcp-tool-result.d.ts +3 -1
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
- package/dist/runtime/mcp-tool-result.js +7 -5
- package/dist/runtime/mcp-tool-result.js.map +1 -1
- package/dist/runtime/types.d.ts +11 -35
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js +1 -2
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.js +3 -4
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +12 -11
- package/src/runtime/AgUiThreadController.deferred-results.test.ts +548 -0
- package/src/runtime/{AgUiThreadRuntimeCore.interrupt-cancel.test.ts → AgUiThreadController.interrupt-cancel.test.ts} +4 -4
- package/src/runtime/AgUiThreadController.replay.test.ts +3932 -0
- package/src/runtime/{AgUiThreadRuntimeCore.test.ts → AgUiThreadController.test.ts} +114 -6
- package/src/runtime/{AgUiThreadRuntimeCore.ts → AgUiThreadController.ts} +1037 -236
- package/src/runtime/adapter/conversions.content.test.ts +149 -5
- package/src/runtime/adapter/conversions.tools.test.ts +2 -1
- package/src/runtime/adapter/conversions.ts +249 -62
- package/src/runtime/adapter/replay-content.test.ts +235 -0
- package/src/runtime/adapter/replay-content.ts +306 -0
- package/src/runtime/adapter/run-aggregator.ts +79 -114
- package/src/runtime/adapter/subscriber.ts +1 -44
- package/src/runtime/adapter/tool-approval.ts +41 -3
- package/src/runtime/agUiThreadState.test.ts +224 -0
- package/src/runtime/agUiThreadState.ts +139 -0
- package/src/runtime/event-parser.ts +40 -58
- package/src/runtime/mcp-tool-result.ts +16 -2
- package/src/runtime/types.ts +16 -28
- package/src/useAgUiRuntime.fast-refresh.test.tsx +3 -3
- package/src/useAgUiRuntime.thread-switch.test.tsx +4 -4
- package/src/useAgUiRuntime.ts +3 -3
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +0 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createAgUiThreadState,
|
|
4
|
+
reduceAgUiThreadState,
|
|
5
|
+
} from "./agUiThreadState";
|
|
6
|
+
|
|
7
|
+
describe("reduceAgUiThreadState", () => {
|
|
8
|
+
it("tracks run and load phases without changing state for repeated phases", () => {
|
|
9
|
+
const initial = createAgUiThreadState();
|
|
10
|
+
expect(reduceAgUiThreadState(initial, { type: "runFinished" })).toBe(
|
|
11
|
+
initial,
|
|
12
|
+
);
|
|
13
|
+
expect(reduceAgUiThreadState(initial, { type: "loadFinished" })).toBe(
|
|
14
|
+
initial,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const running = reduceAgUiThreadState(initial, { type: "runStarted" });
|
|
18
|
+
const requested = reduceAgUiThreadState(running, { type: "loadRequested" });
|
|
19
|
+
const loading = reduceAgUiThreadState(requested, { type: "loadStarted" });
|
|
20
|
+
expect(loading).toMatchObject({
|
|
21
|
+
isRunning: true,
|
|
22
|
+
isLoading: true,
|
|
23
|
+
loadRequested: true,
|
|
24
|
+
});
|
|
25
|
+
expect(reduceAgUiThreadState(running, { type: "runStarted" })).toBe(
|
|
26
|
+
running,
|
|
27
|
+
);
|
|
28
|
+
expect(reduceAgUiThreadState(requested, { type: "loadRequested" })).toBe(
|
|
29
|
+
requested,
|
|
30
|
+
);
|
|
31
|
+
expect(reduceAgUiThreadState(loading, { type: "loadStarted" })).toBe(
|
|
32
|
+
loading,
|
|
33
|
+
);
|
|
34
|
+
expect(initial).toMatchObject({
|
|
35
|
+
isRunning: false,
|
|
36
|
+
isLoading: false,
|
|
37
|
+
loadRequested: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const stopped = reduceAgUiThreadState(loading, { type: "runFinished" });
|
|
41
|
+
const loaded = reduceAgUiThreadState(stopped, { type: "loadFinished" });
|
|
42
|
+
expect(loaded.isRunning).toBe(false);
|
|
43
|
+
expect(loaded.isLoading).toBe(false);
|
|
44
|
+
expect(loaded.loadRequested).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("advances history mutation generation across thread resets", () => {
|
|
48
|
+
const initial = createAgUiThreadState();
|
|
49
|
+
const first = reduceAgUiThreadState(initial, {
|
|
50
|
+
type: "historyMutationAdvanced",
|
|
51
|
+
});
|
|
52
|
+
const reset = reduceAgUiThreadState(first, { type: "resetThread" });
|
|
53
|
+
const second = reduceAgUiThreadState(reset, {
|
|
54
|
+
type: "historyMutationAdvanced",
|
|
55
|
+
});
|
|
56
|
+
expect(initial.historyMutationGeneration).toBe(0);
|
|
57
|
+
expect(reset.historyMutationGeneration).toBe(1);
|
|
58
|
+
expect(second.historyMutationGeneration).toBe(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("replaces the state snapshot", () => {
|
|
62
|
+
const initial = createAgUiThreadState();
|
|
63
|
+
const snapshot = { count: 1 };
|
|
64
|
+
const first = reduceAgUiThreadState(initial, {
|
|
65
|
+
type: "stateSnapshot",
|
|
66
|
+
snapshot,
|
|
67
|
+
});
|
|
68
|
+
expect(first.stateSnapshot).toBe(snapshot);
|
|
69
|
+
expect(
|
|
70
|
+
reduceAgUiThreadState(first, { type: "stateSnapshot", snapshot }),
|
|
71
|
+
).toBe(first);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("sets and resets state snapshots", () => {
|
|
75
|
+
const initial = createAgUiThreadState();
|
|
76
|
+
const first = reduceAgUiThreadState(initial, {
|
|
77
|
+
type: "stateSnapshot",
|
|
78
|
+
snapshot: { count: 1 },
|
|
79
|
+
});
|
|
80
|
+
const second = reduceAgUiThreadState(first, {
|
|
81
|
+
type: "stateSnapshot",
|
|
82
|
+
snapshot: { count: 2 },
|
|
83
|
+
});
|
|
84
|
+
expect(second.stateSnapshot).toEqual({ count: 2 });
|
|
85
|
+
expect(first.stateSnapshot).toEqual({ count: 1 });
|
|
86
|
+
expect(
|
|
87
|
+
reduceAgUiThreadState(second, {
|
|
88
|
+
type: "stateSnapshot",
|
|
89
|
+
snapshot: second.stateSnapshot!,
|
|
90
|
+
}),
|
|
91
|
+
).toBe(second);
|
|
92
|
+
const reset = reduceAgUiThreadState(second, { type: "resetState" });
|
|
93
|
+
expect(reset.stateSnapshot).toBeUndefined();
|
|
94
|
+
expect(reduceAgUiThreadState(reset, { type: "resetState" })).toBe(reset);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("records run config and manages deferred continuations by owner", () => {
|
|
98
|
+
const initial = createAgUiThreadState();
|
|
99
|
+
const runConfig = {};
|
|
100
|
+
const configured = reduceAgUiThreadState(initial, {
|
|
101
|
+
type: "runConfigRecorded",
|
|
102
|
+
runConfig,
|
|
103
|
+
});
|
|
104
|
+
expect(configured.lastRunConfig).toBe(runConfig);
|
|
105
|
+
expect(
|
|
106
|
+
reduceAgUiThreadState(configured, {
|
|
107
|
+
type: "runConfigRecorded",
|
|
108
|
+
runConfig,
|
|
109
|
+
}),
|
|
110
|
+
).toBe(configured);
|
|
111
|
+
|
|
112
|
+
const owner = new AbortController();
|
|
113
|
+
const withResume = reduceAgUiThreadState(configured, {
|
|
114
|
+
type: "resumeDeferred",
|
|
115
|
+
owner,
|
|
116
|
+
messageId: "assistant",
|
|
117
|
+
});
|
|
118
|
+
expect(withResume.pendingResume).toEqual({
|
|
119
|
+
owner,
|
|
120
|
+
messageIds: ["assistant"],
|
|
121
|
+
});
|
|
122
|
+
expect(
|
|
123
|
+
reduceAgUiThreadState(withResume, {
|
|
124
|
+
type: "resumeDeferred",
|
|
125
|
+
owner,
|
|
126
|
+
messageId: "assistant",
|
|
127
|
+
}),
|
|
128
|
+
).toBe(withResume);
|
|
129
|
+
const withTwoOwners = reduceAgUiThreadState(withResume, {
|
|
130
|
+
type: "resumeDeferred",
|
|
131
|
+
owner,
|
|
132
|
+
messageId: "assistant-next",
|
|
133
|
+
});
|
|
134
|
+
expect(withTwoOwners.pendingResume).toEqual({
|
|
135
|
+
owner,
|
|
136
|
+
messageIds: ["assistant", "assistant-next"],
|
|
137
|
+
});
|
|
138
|
+
expect(
|
|
139
|
+
reduceAgUiThreadState(withTwoOwners, {
|
|
140
|
+
type: "resumeDeferred",
|
|
141
|
+
owner: new AbortController(),
|
|
142
|
+
messageId: "new-run",
|
|
143
|
+
}).pendingResume?.messageIds,
|
|
144
|
+
).toEqual(["new-run"]);
|
|
145
|
+
const withOwner = reduceAgUiThreadState(withResume, {
|
|
146
|
+
type: "a2uiResumeDeferred",
|
|
147
|
+
owner,
|
|
148
|
+
});
|
|
149
|
+
const action = { value: "submit" };
|
|
150
|
+
const withAction = reduceAgUiThreadState(withOwner, {
|
|
151
|
+
type: "a2uiActionDeferred",
|
|
152
|
+
action,
|
|
153
|
+
});
|
|
154
|
+
expect(withAction.pendingA2uiResumeOwner).toBe(owner);
|
|
155
|
+
expect(withAction.pendingA2uiAction).toBe(action);
|
|
156
|
+
expect(
|
|
157
|
+
reduceAgUiThreadState(withOwner, { type: "a2uiResumeDeferred", owner }),
|
|
158
|
+
).toBe(withOwner);
|
|
159
|
+
expect(
|
|
160
|
+
reduceAgUiThreadState(withAction, { type: "a2uiActionDeferred", action }),
|
|
161
|
+
).toBe(withAction);
|
|
162
|
+
|
|
163
|
+
const actionCleared = reduceAgUiThreadState(withAction, {
|
|
164
|
+
type: "a2uiActionCleared",
|
|
165
|
+
});
|
|
166
|
+
expect(actionCleared.pendingA2uiAction).toBeUndefined();
|
|
167
|
+
expect(actionCleared.pendingA2uiResumeOwner).toBe(owner);
|
|
168
|
+
const ownerCleared = reduceAgUiThreadState(actionCleared, {
|
|
169
|
+
type: "a2uiResumeCleared",
|
|
170
|
+
});
|
|
171
|
+
expect(ownerCleared.pendingA2uiResumeOwner).toBeNull();
|
|
172
|
+
expect(
|
|
173
|
+
reduceAgUiThreadState(ownerCleared, { type: "a2uiResumeCleared" }),
|
|
174
|
+
).toBe(ownerCleared);
|
|
175
|
+
expect(
|
|
176
|
+
reduceAgUiThreadState(ownerCleared, { type: "a2uiActionCleared" }),
|
|
177
|
+
).toBe(ownerCleared);
|
|
178
|
+
const resumeCleared = reduceAgUiThreadState(ownerCleared, {
|
|
179
|
+
type: "resumeCleared",
|
|
180
|
+
});
|
|
181
|
+
expect(resumeCleared.pendingResume).toBeNull();
|
|
182
|
+
expect(
|
|
183
|
+
reduceAgUiThreadState(resumeCleared, { type: "resumeCleared" }),
|
|
184
|
+
).toBe(resumeCleared);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("clears A2UI continuations and resets thread state without changing run flags", () => {
|
|
188
|
+
const owner = new AbortController();
|
|
189
|
+
let state = reduceAgUiThreadState(createAgUiThreadState(), {
|
|
190
|
+
type: "runStarted",
|
|
191
|
+
});
|
|
192
|
+
state = reduceAgUiThreadState(state, { type: "loadStarted" });
|
|
193
|
+
state = reduceAgUiThreadState(state, {
|
|
194
|
+
type: "resumeDeferred",
|
|
195
|
+
owner,
|
|
196
|
+
messageId: "assistant",
|
|
197
|
+
});
|
|
198
|
+
state = reduceAgUiThreadState(state, {
|
|
199
|
+
type: "a2uiResumeDeferred",
|
|
200
|
+
owner,
|
|
201
|
+
});
|
|
202
|
+
state = reduceAgUiThreadState(state, {
|
|
203
|
+
type: "a2uiActionDeferred",
|
|
204
|
+
action: { value: "submit" },
|
|
205
|
+
});
|
|
206
|
+
const cleared = reduceAgUiThreadState(state, {
|
|
207
|
+
type: "a2uiContinuationsCleared",
|
|
208
|
+
});
|
|
209
|
+
expect(cleared.pendingA2uiResumeOwner).toBeNull();
|
|
210
|
+
expect(cleared.pendingA2uiAction).toBeUndefined();
|
|
211
|
+
expect(cleared.pendingResume).toEqual({ owner, messageIds: ["assistant"] });
|
|
212
|
+
expect(
|
|
213
|
+
reduceAgUiThreadState(cleared, { type: "a2uiContinuationsCleared" }),
|
|
214
|
+
).toBe(cleared);
|
|
215
|
+
|
|
216
|
+
const reset = reduceAgUiThreadState(state, { type: "resetThread" });
|
|
217
|
+
expect(reset).toMatchObject({ isRunning: true, isLoading: true });
|
|
218
|
+
expect(reset.stateSnapshot).toBeUndefined();
|
|
219
|
+
expect(reset.pendingResume).toBeNull();
|
|
220
|
+
expect(reset.pendingA2uiResumeOwner).toBeNull();
|
|
221
|
+
expect(reset.pendingA2uiAction).toBeUndefined();
|
|
222
|
+
expect(reduceAgUiThreadState(reset, { type: "resetThread" })).toBe(reset);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { RunConfig } from "@assistant-ui/core";
|
|
2
|
+
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
3
|
+
|
|
4
|
+
export type AgUiThreadState = {
|
|
5
|
+
isRunning: boolean;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
loadRequested: boolean;
|
|
8
|
+
historyMutationGeneration: number;
|
|
9
|
+
stateSnapshot: ReadonlyJSONValue | undefined;
|
|
10
|
+
lastRunConfig: RunConfig | undefined;
|
|
11
|
+
pendingResume: {
|
|
12
|
+
owner: AbortController;
|
|
13
|
+
messageIds: readonly string[];
|
|
14
|
+
} | null;
|
|
15
|
+
pendingA2uiResumeOwner: AbortController | null;
|
|
16
|
+
pendingA2uiAction: Record<string, unknown> | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type AgUiThreadAction =
|
|
20
|
+
| { type: "runStarted" }
|
|
21
|
+
| { type: "runFinished" }
|
|
22
|
+
| { type: "loadRequested" }
|
|
23
|
+
| { type: "loadStarted" }
|
|
24
|
+
| { type: "loadFinished" }
|
|
25
|
+
| { type: "historyMutationAdvanced" }
|
|
26
|
+
| { type: "stateSnapshot"; snapshot: ReadonlyJSONValue }
|
|
27
|
+
| { type: "resetState" }
|
|
28
|
+
| { type: "runConfigRecorded"; runConfig: RunConfig }
|
|
29
|
+
| { type: "resumeDeferred"; owner: AbortController; messageId: string }
|
|
30
|
+
| { type: "resumeCleared" }
|
|
31
|
+
| { type: "a2uiResumeDeferred"; owner: AbortController }
|
|
32
|
+
| { type: "a2uiResumeCleared" }
|
|
33
|
+
| { type: "a2uiActionDeferred"; action: Record<string, unknown> }
|
|
34
|
+
| { type: "a2uiActionCleared" }
|
|
35
|
+
| { type: "a2uiContinuationsCleared" }
|
|
36
|
+
| { type: "resetThread" };
|
|
37
|
+
|
|
38
|
+
export const createAgUiThreadState = (): AgUiThreadState => ({
|
|
39
|
+
isRunning: false,
|
|
40
|
+
isLoading: false,
|
|
41
|
+
loadRequested: false,
|
|
42
|
+
historyMutationGeneration: 0,
|
|
43
|
+
stateSnapshot: undefined,
|
|
44
|
+
lastRunConfig: undefined,
|
|
45
|
+
pendingResume: null,
|
|
46
|
+
pendingA2uiResumeOwner: null,
|
|
47
|
+
pendingA2uiAction: undefined,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const reduceAgUiThreadState = (
|
|
51
|
+
state: AgUiThreadState,
|
|
52
|
+
action: AgUiThreadAction,
|
|
53
|
+
): AgUiThreadState => {
|
|
54
|
+
switch (action.type) {
|
|
55
|
+
case "runStarted":
|
|
56
|
+
return state.isRunning ? state : { ...state, isRunning: true };
|
|
57
|
+
case "runFinished":
|
|
58
|
+
return !state.isRunning ? state : { ...state, isRunning: false };
|
|
59
|
+
case "loadRequested":
|
|
60
|
+
return state.loadRequested ? state : { ...state, loadRequested: true };
|
|
61
|
+
case "loadStarted":
|
|
62
|
+
return state.isLoading ? state : { ...state, isLoading: true };
|
|
63
|
+
case "loadFinished":
|
|
64
|
+
return !state.isLoading ? state : { ...state, isLoading: false };
|
|
65
|
+
case "historyMutationAdvanced":
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
historyMutationGeneration: state.historyMutationGeneration + 1,
|
|
69
|
+
};
|
|
70
|
+
case "stateSnapshot":
|
|
71
|
+
return state.stateSnapshot === action.snapshot
|
|
72
|
+
? state
|
|
73
|
+
: { ...state, stateSnapshot: action.snapshot };
|
|
74
|
+
case "resetState":
|
|
75
|
+
return state.stateSnapshot === undefined
|
|
76
|
+
? state
|
|
77
|
+
: { ...state, stateSnapshot: undefined };
|
|
78
|
+
case "runConfigRecorded":
|
|
79
|
+
return state.lastRunConfig === action.runConfig
|
|
80
|
+
? state
|
|
81
|
+
: { ...state, lastRunConfig: action.runConfig };
|
|
82
|
+
case "resumeDeferred":
|
|
83
|
+
return state.pendingResume?.owner === action.owner &&
|
|
84
|
+
state.pendingResume.messageIds.includes(action.messageId)
|
|
85
|
+
? state
|
|
86
|
+
: {
|
|
87
|
+
...state,
|
|
88
|
+
pendingResume: {
|
|
89
|
+
owner: action.owner,
|
|
90
|
+
messageIds:
|
|
91
|
+
state.pendingResume?.owner === action.owner
|
|
92
|
+
? [...state.pendingResume.messageIds, action.messageId]
|
|
93
|
+
: [action.messageId],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
case "resumeCleared":
|
|
97
|
+
return state.pendingResume === null
|
|
98
|
+
? state
|
|
99
|
+
: { ...state, pendingResume: null };
|
|
100
|
+
case "a2uiResumeDeferred":
|
|
101
|
+
return state.pendingA2uiResumeOwner === action.owner
|
|
102
|
+
? state
|
|
103
|
+
: { ...state, pendingA2uiResumeOwner: action.owner };
|
|
104
|
+
case "a2uiResumeCleared":
|
|
105
|
+
return state.pendingA2uiResumeOwner === null
|
|
106
|
+
? state
|
|
107
|
+
: { ...state, pendingA2uiResumeOwner: null };
|
|
108
|
+
case "a2uiActionDeferred":
|
|
109
|
+
return state.pendingA2uiAction === action.action
|
|
110
|
+
? state
|
|
111
|
+
: { ...state, pendingA2uiAction: action.action };
|
|
112
|
+
case "a2uiActionCleared":
|
|
113
|
+
return state.pendingA2uiAction === undefined
|
|
114
|
+
? state
|
|
115
|
+
: { ...state, pendingA2uiAction: undefined };
|
|
116
|
+
case "a2uiContinuationsCleared":
|
|
117
|
+
return state.pendingA2uiResumeOwner === null &&
|
|
118
|
+
state.pendingA2uiAction === undefined
|
|
119
|
+
? state
|
|
120
|
+
: {
|
|
121
|
+
...state,
|
|
122
|
+
pendingA2uiResumeOwner: null,
|
|
123
|
+
pendingA2uiAction: undefined,
|
|
124
|
+
};
|
|
125
|
+
case "resetThread":
|
|
126
|
+
return state.stateSnapshot === undefined &&
|
|
127
|
+
state.pendingResume === null &&
|
|
128
|
+
state.pendingA2uiResumeOwner === null &&
|
|
129
|
+
state.pendingA2uiAction === undefined
|
|
130
|
+
? state
|
|
131
|
+
: {
|
|
132
|
+
...state,
|
|
133
|
+
stateSnapshot: undefined,
|
|
134
|
+
pendingResume: null,
|
|
135
|
+
pendingA2uiResumeOwner: null,
|
|
136
|
+
pendingA2uiAction: undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import type { Logger } from "./logger";
|
|
8
8
|
import { parseMcpToolCallResult } from "./mcp-tool-result";
|
|
9
9
|
import { withRawResponseSchema } from "./interrupt-internals";
|
|
10
|
+
import type { ContentPart } from "@ag-ui/client";
|
|
10
11
|
|
|
11
12
|
export type ParseAgUiEventOptions = {
|
|
12
13
|
logger?: Logger;
|
|
@@ -79,7 +80,19 @@ const parseRunFinishedOutcome = (
|
|
|
79
80
|
);
|
|
80
81
|
return undefined;
|
|
81
82
|
}
|
|
82
|
-
if (raw.type === "success")
|
|
83
|
+
if (raw.type === "success") {
|
|
84
|
+
return withOptional(
|
|
85
|
+
{ type: "success" as const },
|
|
86
|
+
{
|
|
87
|
+
pendingToolCallIds:
|
|
88
|
+
Array.isArray(raw.pendingToolCallIds) &&
|
|
89
|
+
raw.pendingToolCallIds.every(isString)
|
|
90
|
+
? raw.pendingToolCallIds
|
|
91
|
+
: undefined,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (raw.type === "cancelled") return { type: "cancelled" };
|
|
83
96
|
if (raw.type === "interrupt") {
|
|
84
97
|
if (!Array.isArray(raw.interrupts)) {
|
|
85
98
|
logRejectedEvent(
|
|
@@ -210,74 +223,50 @@ export const parseAgUiEvent = (
|
|
|
210
223
|
subagentRunId: getString("subagentRunId"),
|
|
211
224
|
},
|
|
212
225
|
);
|
|
213
|
-
case "
|
|
214
|
-
const
|
|
215
|
-
return
|
|
216
|
-
{ type: "TEXT_MESSAGE_CHUNK" as const, delta },
|
|
217
|
-
{
|
|
218
|
-
messageId: getString("messageId"),
|
|
219
|
-
subagentRunId: getString("subagentRunId"),
|
|
220
|
-
},
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
case "THINKING_START":
|
|
224
|
-
return withOptional(
|
|
225
|
-
{ type: "THINKING_START" as const },
|
|
226
|
-
{ title: getString("title"), timestamp },
|
|
227
|
-
);
|
|
228
|
-
case "THINKING_TEXT_MESSAGE_START":
|
|
229
|
-
return withOptional(
|
|
230
|
-
{ type: "THINKING_TEXT_MESSAGE_START" as const },
|
|
231
|
-
{ timestamp },
|
|
232
|
-
);
|
|
233
|
-
case "THINKING_TEXT_MESSAGE_CONTENT": {
|
|
234
|
-
const delta = getString("delta") ?? "";
|
|
235
|
-
return { type: "THINKING_TEXT_MESSAGE_CONTENT", delta };
|
|
236
|
-
}
|
|
237
|
-
case "THINKING_TEXT_MESSAGE_END":
|
|
238
|
-
return withOptional(
|
|
239
|
-
{ type: "THINKING_TEXT_MESSAGE_END" as const },
|
|
240
|
-
{ timestamp },
|
|
241
|
-
);
|
|
242
|
-
case "THINKING_END":
|
|
243
|
-
return withOptional({ type: "THINKING_END" as const }, { timestamp });
|
|
244
|
-
case "REASONING_START":
|
|
226
|
+
case "REASONING_START": {
|
|
227
|
+
const messageId = getString("messageId");
|
|
228
|
+
if (!messageId) return reject("missing messageId");
|
|
245
229
|
return withOptional(
|
|
246
|
-
{ type: "REASONING_START" as const },
|
|
230
|
+
{ type: "REASONING_START" as const, messageId },
|
|
247
231
|
{
|
|
248
|
-
messageId: getString("messageId"),
|
|
249
232
|
subagentRunId: getString("subagentRunId"),
|
|
250
233
|
timestamp,
|
|
251
234
|
},
|
|
252
235
|
);
|
|
253
|
-
|
|
236
|
+
}
|
|
237
|
+
case "REASONING_MESSAGE_START": {
|
|
238
|
+
const messageId = getString("messageId");
|
|
239
|
+
if (!messageId) return reject("missing messageId");
|
|
254
240
|
return withOptional(
|
|
255
|
-
{ type: "REASONING_MESSAGE_START" as const },
|
|
241
|
+
{ type: "REASONING_MESSAGE_START" as const, messageId },
|
|
256
242
|
{
|
|
257
|
-
messageId: getString("messageId"),
|
|
258
243
|
subagentRunId: getString("subagentRunId"),
|
|
259
244
|
timestamp,
|
|
260
245
|
},
|
|
261
246
|
);
|
|
247
|
+
}
|
|
262
248
|
case "REASONING_MESSAGE_CONTENT": {
|
|
249
|
+
const messageId = getString("messageId");
|
|
250
|
+
if (!messageId) return reject("missing messageId");
|
|
263
251
|
const delta = getString("delta") ?? "";
|
|
264
252
|
return withOptional(
|
|
265
|
-
{ type: "REASONING_MESSAGE_CONTENT" as const, delta },
|
|
253
|
+
{ type: "REASONING_MESSAGE_CONTENT" as const, messageId, delta },
|
|
266
254
|
{
|
|
267
|
-
messageId: getString("messageId"),
|
|
268
255
|
subagentRunId: getString("subagentRunId"),
|
|
269
256
|
},
|
|
270
257
|
);
|
|
271
258
|
}
|
|
272
|
-
case "REASONING_MESSAGE_END":
|
|
259
|
+
case "REASONING_MESSAGE_END": {
|
|
260
|
+
const messageId = getString("messageId");
|
|
261
|
+
if (!messageId) return reject("missing messageId");
|
|
273
262
|
return withOptional(
|
|
274
|
-
{ type: "REASONING_MESSAGE_END" as const },
|
|
263
|
+
{ type: "REASONING_MESSAGE_END" as const, messageId },
|
|
275
264
|
{
|
|
276
|
-
messageId: getString("messageId"),
|
|
277
265
|
subagentRunId: getString("subagentRunId"),
|
|
278
266
|
timestamp,
|
|
279
267
|
},
|
|
280
268
|
);
|
|
269
|
+
}
|
|
281
270
|
case "REASONING_ENCRYPTED_VALUE": {
|
|
282
271
|
const entityId = getString("entityId");
|
|
283
272
|
const encryptedValue = getString("encryptedValue");
|
|
@@ -299,15 +288,17 @@ export const parseAgUiEvent = (
|
|
|
299
288
|
...(subagentRunId !== undefined ? { subagentRunId } : {}),
|
|
300
289
|
};
|
|
301
290
|
}
|
|
302
|
-
case "REASONING_END":
|
|
291
|
+
case "REASONING_END": {
|
|
292
|
+
const messageId = getString("messageId");
|
|
293
|
+
if (!messageId) return reject("missing messageId");
|
|
303
294
|
return withOptional(
|
|
304
|
-
{ type: "REASONING_END" as const },
|
|
295
|
+
{ type: "REASONING_END" as const, messageId },
|
|
305
296
|
{
|
|
306
|
-
messageId: getString("messageId"),
|
|
307
297
|
subagentRunId: getString("subagentRunId"),
|
|
308
298
|
timestamp,
|
|
309
299
|
},
|
|
310
300
|
);
|
|
301
|
+
}
|
|
311
302
|
case "TOOL_CALL_START": {
|
|
312
303
|
const toolCallId = getString("toolCallId");
|
|
313
304
|
if (!toolCallId) return reject("missing toolCallId");
|
|
@@ -337,21 +328,12 @@ export const parseAgUiEvent = (
|
|
|
337
328
|
{ subagentRunId: getString("subagentRunId") },
|
|
338
329
|
);
|
|
339
330
|
}
|
|
340
|
-
case "TOOL_CALL_CHUNK":
|
|
341
|
-
return withOptional(
|
|
342
|
-
{ type: "TOOL_CALL_CHUNK" as const },
|
|
343
|
-
{
|
|
344
|
-
toolCallId: getString("toolCallId"),
|
|
345
|
-
toolCallName: getString("toolCallName"),
|
|
346
|
-
parentMessageId: getString("parentMessageId"),
|
|
347
|
-
delta: getString("delta"),
|
|
348
|
-
subagentRunId: getString("subagentRunId"),
|
|
349
|
-
},
|
|
350
|
-
);
|
|
351
331
|
case "TOOL_CALL_RESULT": {
|
|
352
332
|
const toolCallId = getString("toolCallId");
|
|
353
333
|
if (!toolCallId) return reject("missing toolCallId");
|
|
354
|
-
const content =
|
|
334
|
+
const content = Array.isArray(payload.content)
|
|
335
|
+
? (payload.content as ContentPart[])
|
|
336
|
+
: (getString("content") ?? "");
|
|
355
337
|
return withOptional(
|
|
356
338
|
{
|
|
357
339
|
type: "TOOL_CALL_RESULT" as const,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isRecord } from "@assistant-ui/core/internal";
|
|
2
2
|
import { isMcpAppUri } from "@assistant-ui/core";
|
|
3
|
+
import { contentToText, type ContentPart } from "@ag-ui/client";
|
|
3
4
|
|
|
4
5
|
export type McpToolCallResult = Record<string, unknown> & {
|
|
5
6
|
content: unknown[];
|
|
@@ -8,9 +9,16 @@ export type McpToolCallResult = Record<string, unknown> & {
|
|
|
8
9
|
isError?: boolean;
|
|
9
10
|
};
|
|
10
11
|
|
|
12
|
+
export const toolContentText = (content: string | ContentPart[]): string =>
|
|
13
|
+
contentToText(
|
|
14
|
+
Array.isArray(content)
|
|
15
|
+
? (content.filter(isRecord) as ContentPart[])
|
|
16
|
+
: content,
|
|
17
|
+
);
|
|
18
|
+
|
|
11
19
|
export const parseMcpToolCallResult = (
|
|
12
20
|
source: Record<string, unknown>,
|
|
13
|
-
|
|
21
|
+
content: string | ContentPart[],
|
|
14
22
|
): McpToolCallResult | undefined => {
|
|
15
23
|
const structuredContent = isRecord(source.structuredContent)
|
|
16
24
|
? source.structuredContent
|
|
@@ -18,8 +26,14 @@ export const parseMcpToolCallResult = (
|
|
|
18
26
|
const meta = isRecord(source._meta) ? source._meta : undefined;
|
|
19
27
|
if (!structuredContent && !meta) return undefined;
|
|
20
28
|
|
|
29
|
+
const modelContent = toolContentText(content);
|
|
30
|
+
|
|
21
31
|
return {
|
|
22
|
-
content:
|
|
32
|
+
content: Array.isArray(content)
|
|
33
|
+
? content
|
|
34
|
+
: modelContent
|
|
35
|
+
? [{ type: "text", text: modelContent }]
|
|
36
|
+
: [],
|
|
23
37
|
...(structuredContent ? { structuredContent } : {}),
|
|
24
38
|
...(meta ? { _meta: meta } : {}),
|
|
25
39
|
...(typeof source.isError === "boolean" ? { isError: source.isError } : {}),
|
package/src/runtime/types.ts
CHANGED
|
@@ -12,7 +12,12 @@ import type {
|
|
|
12
12
|
ThreadMessage,
|
|
13
13
|
} from "@assistant-ui/core";
|
|
14
14
|
import type { JoinStrategy } from "@assistant-ui/core/react";
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
AbstractAgent,
|
|
17
|
+
ContentPart,
|
|
18
|
+
Context,
|
|
19
|
+
Message,
|
|
20
|
+
} from "@ag-ui/client";
|
|
16
21
|
import type { Logger } from "./logger";
|
|
17
22
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
18
23
|
import type { McpToolCallResult } from "./mcp-tool-result";
|
|
@@ -33,6 +38,7 @@ export type AgUiCustomMetadata = {
|
|
|
33
38
|
* represent (a developer record rides as a system message). */
|
|
34
39
|
role?: "developer";
|
|
35
40
|
interrupts?: AgUiInterrupt[];
|
|
41
|
+
pendingToolCallIds?: string[];
|
|
36
42
|
opaqueReasoning?: AgUiOpaqueReasoning[];
|
|
37
43
|
};
|
|
38
44
|
|
|
@@ -189,8 +195,9 @@ export type AgUiRuntimeExtras = {
|
|
|
189
195
|
};
|
|
190
196
|
|
|
191
197
|
export type AgUiRunFinishedOutcome =
|
|
192
|
-
| { type: "success" }
|
|
193
|
-
| { type: "interrupt"; interrupts: AgUiInterrupt[] }
|
|
198
|
+
| { type: "success"; pendingToolCallIds?: string[] }
|
|
199
|
+
| { type: "interrupt"; interrupts: AgUiInterrupt[] }
|
|
200
|
+
| { type: "cancelled" };
|
|
194
201
|
|
|
195
202
|
export type AgUiSubagentFinishedOutcome =
|
|
196
203
|
| { type: "success" }
|
|
@@ -213,38 +220,27 @@ export type AgUiEvent =
|
|
|
213
220
|
subagentRunId?: string;
|
|
214
221
|
}
|
|
215
222
|
| { type: "TEXT_MESSAGE_END"; messageId?: string; subagentRunId?: string }
|
|
216
|
-
| {
|
|
217
|
-
type: "TEXT_MESSAGE_CHUNK";
|
|
218
|
-
messageId?: string;
|
|
219
|
-
delta: string;
|
|
220
|
-
subagentRunId?: string;
|
|
221
|
-
}
|
|
222
|
-
| { type: "THINKING_START"; title?: string; timestamp?: number }
|
|
223
|
-
| { type: "THINKING_TEXT_MESSAGE_START"; timestamp?: number }
|
|
224
|
-
| { type: "THINKING_TEXT_MESSAGE_CONTENT"; delta: string }
|
|
225
|
-
| { type: "THINKING_TEXT_MESSAGE_END"; timestamp?: number }
|
|
226
|
-
| { type: "THINKING_END"; timestamp?: number }
|
|
227
223
|
| {
|
|
228
224
|
type: "REASONING_START";
|
|
229
|
-
messageId
|
|
225
|
+
messageId: string;
|
|
230
226
|
subagentRunId?: string;
|
|
231
227
|
timestamp?: number;
|
|
232
228
|
}
|
|
233
229
|
| {
|
|
234
230
|
type: "REASONING_MESSAGE_START";
|
|
235
|
-
messageId
|
|
231
|
+
messageId: string;
|
|
236
232
|
subagentRunId?: string;
|
|
237
233
|
timestamp?: number;
|
|
238
234
|
}
|
|
239
235
|
| {
|
|
240
236
|
type: "REASONING_MESSAGE_CONTENT";
|
|
241
|
-
messageId
|
|
237
|
+
messageId: string;
|
|
242
238
|
delta: string;
|
|
243
239
|
subagentRunId?: string;
|
|
244
240
|
}
|
|
245
241
|
| {
|
|
246
242
|
type: "REASONING_MESSAGE_END";
|
|
247
|
-
messageId
|
|
243
|
+
messageId: string;
|
|
248
244
|
subagentRunId?: string;
|
|
249
245
|
timestamp?: number;
|
|
250
246
|
}
|
|
@@ -257,7 +253,7 @@ export type AgUiEvent =
|
|
|
257
253
|
}
|
|
258
254
|
| {
|
|
259
255
|
type: "REASONING_END";
|
|
260
|
-
messageId
|
|
256
|
+
messageId: string;
|
|
261
257
|
subagentRunId?: string;
|
|
262
258
|
timestamp?: number;
|
|
263
259
|
}
|
|
@@ -275,19 +271,11 @@ export type AgUiEvent =
|
|
|
275
271
|
subagentRunId?: string;
|
|
276
272
|
}
|
|
277
273
|
| { type: "TOOL_CALL_END"; toolCallId: string; subagentRunId?: string }
|
|
278
|
-
| {
|
|
279
|
-
type: "TOOL_CALL_CHUNK";
|
|
280
|
-
toolCallId?: string;
|
|
281
|
-
toolCallName?: string;
|
|
282
|
-
parentMessageId?: string;
|
|
283
|
-
delta?: string;
|
|
284
|
-
subagentRunId?: string;
|
|
285
|
-
}
|
|
286
274
|
| {
|
|
287
275
|
type: "TOOL_CALL_RESULT";
|
|
288
276
|
messageId?: string;
|
|
289
277
|
toolCallId: string;
|
|
290
|
-
content: string;
|
|
278
|
+
content: string | ContentPart[];
|
|
291
279
|
role?: "tool";
|
|
292
280
|
mcpResult?: McpToolCallResult;
|
|
293
281
|
subagentRunId?: string;
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
AssistantRuntime,
|
|
8
8
|
MessageQueueController,
|
|
9
9
|
} from "@assistant-ui/core";
|
|
10
|
-
import {
|
|
10
|
+
import { AgUiThreadController } from "./runtime/AgUiThreadController";
|
|
11
11
|
import type { UseAgUiRuntimeOptions } from "./runtime/types";
|
|
12
12
|
import { useAgUiRuntime, type AgUiAssistantRuntime } from "./useAgUiRuntime";
|
|
13
13
|
|
|
@@ -230,8 +230,8 @@ const expectInFlightRunAcrossRefresh = async (
|
|
|
230
230
|
runAgent: vi.fn(() => run),
|
|
231
231
|
abortRun: vi.fn(),
|
|
232
232
|
} as unknown as HttpAgent;
|
|
233
|
-
const attach = vi.spyOn(
|
|
234
|
-
const detach = vi.spyOn(
|
|
233
|
+
const attach = vi.spyOn(AgUiThreadController.prototype, "attachRuntime");
|
|
234
|
+
const detach = vi.spyOn(AgUiThreadController.prototype, "detachRuntime");
|
|
235
235
|
options = { agent };
|
|
236
236
|
const { Before, After } = createProbe();
|
|
237
237
|
const Shell: ComponentType<{ mode: "visible" | "hidden" }> = inActivity
|