@assistant-ui/react-ag-ui 0.0.70 → 0.1.0
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/runtime/{AgUiThreadRuntimeCore.d.ts → AgUiThreadController.d.ts} +8 -12
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -0
- package/dist/runtime/{AgUiThreadRuntimeCore.js → AgUiThreadController.js} +302 -162
- 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 +111 -33
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +6 -5
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +50 -62
- 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 +1 -22
- package/dist/runtime/adapter/subscriber.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 +95 -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 +45 -48
- package/dist/runtime/event-parser.js.map +1 -1
- 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 +6 -3
- package/dist/runtime/mcp-tool-result.js.map +1 -1
- package/dist/runtime/types.d.ts +13 -35
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +4 -2
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +13 -12
- package/src/runtime/{AgUiThreadRuntimeCore.interrupt-cancel.test.ts → AgUiThreadController.interrupt-cancel.test.ts} +4 -4
- package/src/runtime/{AgUiThreadRuntimeCore.test.ts → AgUiThreadController.test.ts} +35 -6
- package/src/runtime/{AgUiThreadRuntimeCore.ts → AgUiThreadController.ts} +471 -169
- package/src/runtime/adapter/conversions.content.test.ts +37 -5
- package/src/runtime/adapter/conversions.tools.test.ts +2 -1
- package/src/runtime/adapter/conversions.ts +235 -58
- package/src/runtime/adapter/run-aggregator.ts +84 -114
- package/src/runtime/adapter/subscriber.ts +1 -44
- package/src/runtime/agUiThreadState.test.ts +205 -0
- package/src/runtime/agUiThreadState.ts +130 -0
- package/src/runtime/event-parser.ts +40 -58
- package/src/runtime/mcp-tool-result.ts +16 -2
- package/src/runtime/types.ts +18 -28
- package/src/useAgUiRuntime.external-store.test.tsx +39 -0
- package/src/useAgUiRuntime.fast-refresh.test.tsx +3 -3
- package/src/useAgUiRuntime.thread-switch.test.tsx +4 -4
- package/src/useAgUiRuntime.ts +5 -3
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +0 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
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({ owner, messageId: "assistant" });
|
|
119
|
+
expect(
|
|
120
|
+
reduceAgUiThreadState(withResume, {
|
|
121
|
+
type: "resumeDeferred",
|
|
122
|
+
owner,
|
|
123
|
+
messageId: "assistant",
|
|
124
|
+
}),
|
|
125
|
+
).toBe(withResume);
|
|
126
|
+
const withOwner = reduceAgUiThreadState(withResume, {
|
|
127
|
+
type: "a2uiResumeDeferred",
|
|
128
|
+
owner,
|
|
129
|
+
});
|
|
130
|
+
const action = { value: "submit" };
|
|
131
|
+
const withAction = reduceAgUiThreadState(withOwner, {
|
|
132
|
+
type: "a2uiActionDeferred",
|
|
133
|
+
action,
|
|
134
|
+
});
|
|
135
|
+
expect(withAction.pendingA2uiResumeOwner).toBe(owner);
|
|
136
|
+
expect(withAction.pendingA2uiAction).toBe(action);
|
|
137
|
+
expect(
|
|
138
|
+
reduceAgUiThreadState(withOwner, { type: "a2uiResumeDeferred", owner }),
|
|
139
|
+
).toBe(withOwner);
|
|
140
|
+
expect(
|
|
141
|
+
reduceAgUiThreadState(withAction, { type: "a2uiActionDeferred", action }),
|
|
142
|
+
).toBe(withAction);
|
|
143
|
+
|
|
144
|
+
const actionCleared = reduceAgUiThreadState(withAction, {
|
|
145
|
+
type: "a2uiActionCleared",
|
|
146
|
+
});
|
|
147
|
+
expect(actionCleared.pendingA2uiAction).toBeUndefined();
|
|
148
|
+
expect(actionCleared.pendingA2uiResumeOwner).toBe(owner);
|
|
149
|
+
const ownerCleared = reduceAgUiThreadState(actionCleared, {
|
|
150
|
+
type: "a2uiResumeCleared",
|
|
151
|
+
});
|
|
152
|
+
expect(ownerCleared.pendingA2uiResumeOwner).toBeNull();
|
|
153
|
+
expect(
|
|
154
|
+
reduceAgUiThreadState(ownerCleared, { type: "a2uiResumeCleared" }),
|
|
155
|
+
).toBe(ownerCleared);
|
|
156
|
+
expect(
|
|
157
|
+
reduceAgUiThreadState(ownerCleared, { type: "a2uiActionCleared" }),
|
|
158
|
+
).toBe(ownerCleared);
|
|
159
|
+
const resumeCleared = reduceAgUiThreadState(ownerCleared, {
|
|
160
|
+
type: "resumeCleared",
|
|
161
|
+
});
|
|
162
|
+
expect(resumeCleared.pendingResume).toBeNull();
|
|
163
|
+
expect(
|
|
164
|
+
reduceAgUiThreadState(resumeCleared, { type: "resumeCleared" }),
|
|
165
|
+
).toBe(resumeCleared);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("clears A2UI continuations and resets thread state without changing run flags", () => {
|
|
169
|
+
const owner = new AbortController();
|
|
170
|
+
let state = reduceAgUiThreadState(createAgUiThreadState(), {
|
|
171
|
+
type: "runStarted",
|
|
172
|
+
});
|
|
173
|
+
state = reduceAgUiThreadState(state, { type: "loadStarted" });
|
|
174
|
+
state = reduceAgUiThreadState(state, {
|
|
175
|
+
type: "resumeDeferred",
|
|
176
|
+
owner,
|
|
177
|
+
messageId: "assistant",
|
|
178
|
+
});
|
|
179
|
+
state = reduceAgUiThreadState(state, {
|
|
180
|
+
type: "a2uiResumeDeferred",
|
|
181
|
+
owner,
|
|
182
|
+
});
|
|
183
|
+
state = reduceAgUiThreadState(state, {
|
|
184
|
+
type: "a2uiActionDeferred",
|
|
185
|
+
action: { value: "submit" },
|
|
186
|
+
});
|
|
187
|
+
const cleared = reduceAgUiThreadState(state, {
|
|
188
|
+
type: "a2uiContinuationsCleared",
|
|
189
|
+
});
|
|
190
|
+
expect(cleared.pendingA2uiResumeOwner).toBeNull();
|
|
191
|
+
expect(cleared.pendingA2uiAction).toBeUndefined();
|
|
192
|
+
expect(cleared.pendingResume).toEqual({ owner, messageId: "assistant" });
|
|
193
|
+
expect(
|
|
194
|
+
reduceAgUiThreadState(cleared, { type: "a2uiContinuationsCleared" }),
|
|
195
|
+
).toBe(cleared);
|
|
196
|
+
|
|
197
|
+
const reset = reduceAgUiThreadState(state, { type: "resetThread" });
|
|
198
|
+
expect(reset).toMatchObject({ isRunning: true, isLoading: true });
|
|
199
|
+
expect(reset.stateSnapshot).toBeUndefined();
|
|
200
|
+
expect(reset.pendingResume).toBeNull();
|
|
201
|
+
expect(reset.pendingA2uiResumeOwner).toBeNull();
|
|
202
|
+
expect(reset.pendingA2uiAction).toBeUndefined();
|
|
203
|
+
expect(reduceAgUiThreadState(reset, { type: "resetThread" })).toBe(reset);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
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: { owner: AbortController; messageId: string } | null;
|
|
12
|
+
pendingA2uiResumeOwner: AbortController | null;
|
|
13
|
+
pendingA2uiAction: Record<string, unknown> | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type AgUiThreadAction =
|
|
17
|
+
| { type: "runStarted" }
|
|
18
|
+
| { type: "runFinished" }
|
|
19
|
+
| { type: "loadRequested" }
|
|
20
|
+
| { type: "loadStarted" }
|
|
21
|
+
| { type: "loadFinished" }
|
|
22
|
+
| { type: "historyMutationAdvanced" }
|
|
23
|
+
| { type: "stateSnapshot"; snapshot: ReadonlyJSONValue }
|
|
24
|
+
| { type: "resetState" }
|
|
25
|
+
| { type: "runConfigRecorded"; runConfig: RunConfig }
|
|
26
|
+
| { type: "resumeDeferred"; owner: AbortController; messageId: string }
|
|
27
|
+
| { type: "resumeCleared" }
|
|
28
|
+
| { type: "a2uiResumeDeferred"; owner: AbortController }
|
|
29
|
+
| { type: "a2uiResumeCleared" }
|
|
30
|
+
| { type: "a2uiActionDeferred"; action: Record<string, unknown> }
|
|
31
|
+
| { type: "a2uiActionCleared" }
|
|
32
|
+
| { type: "a2uiContinuationsCleared" }
|
|
33
|
+
| { type: "resetThread" };
|
|
34
|
+
|
|
35
|
+
export const createAgUiThreadState = (): AgUiThreadState => ({
|
|
36
|
+
isRunning: false,
|
|
37
|
+
isLoading: false,
|
|
38
|
+
loadRequested: false,
|
|
39
|
+
historyMutationGeneration: 0,
|
|
40
|
+
stateSnapshot: undefined,
|
|
41
|
+
lastRunConfig: undefined,
|
|
42
|
+
pendingResume: null,
|
|
43
|
+
pendingA2uiResumeOwner: null,
|
|
44
|
+
pendingA2uiAction: undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const reduceAgUiThreadState = (
|
|
48
|
+
state: AgUiThreadState,
|
|
49
|
+
action: AgUiThreadAction,
|
|
50
|
+
): AgUiThreadState => {
|
|
51
|
+
switch (action.type) {
|
|
52
|
+
case "runStarted":
|
|
53
|
+
return state.isRunning ? state : { ...state, isRunning: true };
|
|
54
|
+
case "runFinished":
|
|
55
|
+
return !state.isRunning ? state : { ...state, isRunning: false };
|
|
56
|
+
case "loadRequested":
|
|
57
|
+
return state.loadRequested ? state : { ...state, loadRequested: true };
|
|
58
|
+
case "loadStarted":
|
|
59
|
+
return state.isLoading ? state : { ...state, isLoading: true };
|
|
60
|
+
case "loadFinished":
|
|
61
|
+
return !state.isLoading ? state : { ...state, isLoading: false };
|
|
62
|
+
case "historyMutationAdvanced":
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
historyMutationGeneration: state.historyMutationGeneration + 1,
|
|
66
|
+
};
|
|
67
|
+
case "stateSnapshot":
|
|
68
|
+
return state.stateSnapshot === action.snapshot
|
|
69
|
+
? state
|
|
70
|
+
: { ...state, stateSnapshot: action.snapshot };
|
|
71
|
+
case "resetState":
|
|
72
|
+
return state.stateSnapshot === undefined
|
|
73
|
+
? state
|
|
74
|
+
: { ...state, stateSnapshot: undefined };
|
|
75
|
+
case "runConfigRecorded":
|
|
76
|
+
return state.lastRunConfig === action.runConfig
|
|
77
|
+
? state
|
|
78
|
+
: { ...state, lastRunConfig: action.runConfig };
|
|
79
|
+
case "resumeDeferred":
|
|
80
|
+
return state.pendingResume?.owner === action.owner &&
|
|
81
|
+
state.pendingResume.messageId === action.messageId
|
|
82
|
+
? state
|
|
83
|
+
: {
|
|
84
|
+
...state,
|
|
85
|
+
pendingResume: { owner: action.owner, messageId: action.messageId },
|
|
86
|
+
};
|
|
87
|
+
case "resumeCleared":
|
|
88
|
+
return state.pendingResume === null
|
|
89
|
+
? state
|
|
90
|
+
: { ...state, pendingResume: null };
|
|
91
|
+
case "a2uiResumeDeferred":
|
|
92
|
+
return state.pendingA2uiResumeOwner === action.owner
|
|
93
|
+
? state
|
|
94
|
+
: { ...state, pendingA2uiResumeOwner: action.owner };
|
|
95
|
+
case "a2uiResumeCleared":
|
|
96
|
+
return state.pendingA2uiResumeOwner === null
|
|
97
|
+
? state
|
|
98
|
+
: { ...state, pendingA2uiResumeOwner: null };
|
|
99
|
+
case "a2uiActionDeferred":
|
|
100
|
+
return state.pendingA2uiAction === action.action
|
|
101
|
+
? state
|
|
102
|
+
: { ...state, pendingA2uiAction: action.action };
|
|
103
|
+
case "a2uiActionCleared":
|
|
104
|
+
return state.pendingA2uiAction === undefined
|
|
105
|
+
? state
|
|
106
|
+
: { ...state, pendingA2uiAction: undefined };
|
|
107
|
+
case "a2uiContinuationsCleared":
|
|
108
|
+
return state.pendingA2uiResumeOwner === null &&
|
|
109
|
+
state.pendingA2uiAction === undefined
|
|
110
|
+
? state
|
|
111
|
+
: {
|
|
112
|
+
...state,
|
|
113
|
+
pendingA2uiResumeOwner: null,
|
|
114
|
+
pendingA2uiAction: undefined,
|
|
115
|
+
};
|
|
116
|
+
case "resetThread":
|
|
117
|
+
return state.stateSnapshot === undefined &&
|
|
118
|
+
state.pendingResume === null &&
|
|
119
|
+
state.pendingA2uiResumeOwner === null &&
|
|
120
|
+
state.pendingA2uiAction === undefined
|
|
121
|
+
? state
|
|
122
|
+
: {
|
|
123
|
+
...state,
|
|
124
|
+
stateSnapshot: undefined,
|
|
125
|
+
pendingResume: null,
|
|
126
|
+
pendingA2uiResumeOwner: null,
|
|
127
|
+
pendingA2uiAction: undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
@@ -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
|
@@ -11,7 +11,13 @@ import type {
|
|
|
11
11
|
ThreadHistoryAdapter,
|
|
12
12
|
ThreadMessage,
|
|
13
13
|
} from "@assistant-ui/core";
|
|
14
|
-
import type {
|
|
14
|
+
import type { JoinStrategy } from "@assistant-ui/core/react";
|
|
15
|
+
import type {
|
|
16
|
+
AbstractAgent,
|
|
17
|
+
ContentPart,
|
|
18
|
+
Context,
|
|
19
|
+
Message,
|
|
20
|
+
} from "@ag-ui/client";
|
|
15
21
|
import type { Logger } from "./logger";
|
|
16
22
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
17
23
|
import type { McpToolCallResult } from "./mcp-tool-result";
|
|
@@ -32,6 +38,7 @@ export type AgUiCustomMetadata = {
|
|
|
32
38
|
* represent (a developer record rides as a system message). */
|
|
33
39
|
role?: "developer";
|
|
34
40
|
interrupts?: AgUiInterrupt[];
|
|
41
|
+
pendingToolCallIds?: string[];
|
|
35
42
|
opaqueReasoning?: AgUiOpaqueReasoning[];
|
|
36
43
|
};
|
|
37
44
|
|
|
@@ -82,6 +89,7 @@ export type UseAgUiRuntimeOptions = ExternalStoreSharedOptions & {
|
|
|
82
89
|
agent: AbstractAgent;
|
|
83
90
|
logger?: Partial<Logger>;
|
|
84
91
|
showThinking?: boolean;
|
|
92
|
+
joinStrategy?: JoinStrategy;
|
|
85
93
|
/**
|
|
86
94
|
* Return context entries for each run. `messages` is the exact AG-UI
|
|
87
95
|
* transcript sent in the run input, after transcript selection and message
|
|
@@ -187,8 +195,9 @@ export type AgUiRuntimeExtras = {
|
|
|
187
195
|
};
|
|
188
196
|
|
|
189
197
|
export type AgUiRunFinishedOutcome =
|
|
190
|
-
| { type: "success" }
|
|
191
|
-
| { type: "interrupt"; interrupts: AgUiInterrupt[] }
|
|
198
|
+
| { type: "success"; pendingToolCallIds?: string[] }
|
|
199
|
+
| { type: "interrupt"; interrupts: AgUiInterrupt[] }
|
|
200
|
+
| { type: "cancelled" };
|
|
192
201
|
|
|
193
202
|
export type AgUiSubagentFinishedOutcome =
|
|
194
203
|
| { type: "success" }
|
|
@@ -211,38 +220,27 @@ export type AgUiEvent =
|
|
|
211
220
|
subagentRunId?: string;
|
|
212
221
|
}
|
|
213
222
|
| { type: "TEXT_MESSAGE_END"; messageId?: string; subagentRunId?: string }
|
|
214
|
-
| {
|
|
215
|
-
type: "TEXT_MESSAGE_CHUNK";
|
|
216
|
-
messageId?: string;
|
|
217
|
-
delta: string;
|
|
218
|
-
subagentRunId?: string;
|
|
219
|
-
}
|
|
220
|
-
| { type: "THINKING_START"; title?: string; timestamp?: number }
|
|
221
|
-
| { type: "THINKING_TEXT_MESSAGE_START"; timestamp?: number }
|
|
222
|
-
| { type: "THINKING_TEXT_MESSAGE_CONTENT"; delta: string }
|
|
223
|
-
| { type: "THINKING_TEXT_MESSAGE_END"; timestamp?: number }
|
|
224
|
-
| { type: "THINKING_END"; timestamp?: number }
|
|
225
223
|
| {
|
|
226
224
|
type: "REASONING_START";
|
|
227
|
-
messageId
|
|
225
|
+
messageId: string;
|
|
228
226
|
subagentRunId?: string;
|
|
229
227
|
timestamp?: number;
|
|
230
228
|
}
|
|
231
229
|
| {
|
|
232
230
|
type: "REASONING_MESSAGE_START";
|
|
233
|
-
messageId
|
|
231
|
+
messageId: string;
|
|
234
232
|
subagentRunId?: string;
|
|
235
233
|
timestamp?: number;
|
|
236
234
|
}
|
|
237
235
|
| {
|
|
238
236
|
type: "REASONING_MESSAGE_CONTENT";
|
|
239
|
-
messageId
|
|
237
|
+
messageId: string;
|
|
240
238
|
delta: string;
|
|
241
239
|
subagentRunId?: string;
|
|
242
240
|
}
|
|
243
241
|
| {
|
|
244
242
|
type: "REASONING_MESSAGE_END";
|
|
245
|
-
messageId
|
|
243
|
+
messageId: string;
|
|
246
244
|
subagentRunId?: string;
|
|
247
245
|
timestamp?: number;
|
|
248
246
|
}
|
|
@@ -255,7 +253,7 @@ export type AgUiEvent =
|
|
|
255
253
|
}
|
|
256
254
|
| {
|
|
257
255
|
type: "REASONING_END";
|
|
258
|
-
messageId
|
|
256
|
+
messageId: string;
|
|
259
257
|
subagentRunId?: string;
|
|
260
258
|
timestamp?: number;
|
|
261
259
|
}
|
|
@@ -273,19 +271,11 @@ export type AgUiEvent =
|
|
|
273
271
|
subagentRunId?: string;
|
|
274
272
|
}
|
|
275
273
|
| { type: "TOOL_CALL_END"; toolCallId: string; subagentRunId?: string }
|
|
276
|
-
| {
|
|
277
|
-
type: "TOOL_CALL_CHUNK";
|
|
278
|
-
toolCallId?: string;
|
|
279
|
-
toolCallName?: string;
|
|
280
|
-
parentMessageId?: string;
|
|
281
|
-
delta?: string;
|
|
282
|
-
subagentRunId?: string;
|
|
283
|
-
}
|
|
284
274
|
| {
|
|
285
275
|
type: "TOOL_CALL_RESULT";
|
|
286
276
|
messageId?: string;
|
|
287
277
|
toolCallId: string;
|
|
288
|
-
content: string;
|
|
278
|
+
content: string | ContentPart[];
|
|
289
279
|
role?: "tool";
|
|
290
280
|
mcpResult?: McpToolCallResult;
|
|
291
281
|
subagentRunId?: string;
|