cortena-ui 1.4.2 → 1.5.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/LICENSE +7 -0
- package/README.md +149 -1
- package/dist/a2ui/views.js +2 -2
- package/dist/agent-chat/a2ui-block.d.ts +60 -0
- package/dist/agent-chat/a2ui-block.js +69 -0
- package/dist/agent-chat/a2ui-block.js.map +1 -0
- package/dist/agent-chat/agui-client.d.ts +40 -0
- package/dist/agent-chat/agui-client.js +251 -0
- package/dist/agent-chat/agui-client.js.map +1 -0
- package/dist/agent-chat/bridge.d.ts +109 -0
- package/dist/agent-chat/bridge.js +349 -0
- package/dist/agent-chat/bridge.js.map +1 -0
- package/dist/agent-chat/session.d.ts +58 -0
- package/dist/agent-chat/session.js +249 -0
- package/dist/agent-chat/session.js.map +1 -0
- package/dist/agent-chat/store.d.ts +67 -0
- package/dist/agent-chat/store.js +548 -0
- package/dist/agent-chat/store.js.map +1 -0
- package/dist/agent-chat/types.d.ts +187 -0
- package/dist/agent-chat/types.js +17 -0
- package/dist/agent-chat/types.js.map +1 -0
- package/dist/agent-chat.d.ts +10 -0
- package/dist/agent-chat.js +10 -0
- package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
- package/dist/components/admin-permissions/admin-permissions.js +101 -0
- package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
- package/dist/components/admin-permissions/context.d.ts +70 -0
- package/dist/components/admin-permissions/context.js +258 -0
- package/dist/components/admin-permissions/context.js.map +1 -0
- package/dist/components/admin-permissions/index.d.ts +10 -0
- package/dist/components/admin-permissions/licence.d.ts +15 -0
- package/dist/components/admin-permissions/licence.js +78 -0
- package/dist/components/admin-permissions/licence.js.map +1 -0
- package/dist/components/admin-permissions/matrix.d.ts +20 -0
- package/dist/components/admin-permissions/matrix.js +191 -0
- package/dist/components/admin-permissions/matrix.js.map +1 -0
- package/dist/components/admin-permissions/members.d.ts +18 -0
- package/dist/components/admin-permissions/members.js +185 -0
- package/dist/components/admin-permissions/members.js.map +1 -0
- package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
- package/dist/components/admin-permissions/role-assignment.js +174 -0
- package/dist/components/admin-permissions/role-assignment.js.map +1 -0
- package/dist/components/admin-permissions/roles.d.ts +25 -0
- package/dist/components/admin-permissions/roles.js +168 -0
- package/dist/components/admin-permissions/roles.js.map +1 -0
- package/dist/components/admin-permissions/types.d.ts +152 -0
- package/dist/components/admin-permissions/types.js +63 -0
- package/dist/components/admin-permissions/types.js.map +1 -0
- package/dist/components/agent-chat-popup.d.ts +29 -0
- package/dist/components/agent-chat-popup.js +188 -0
- package/dist/components/agent-chat-popup.js.map +1 -0
- package/dist/components/agent-chat.d.ts +143 -0
- package/dist/components/agent-chat.js +578 -0
- package/dist/components/agent-chat.js.map +1 -0
- package/dist/components/app-shell.d.ts +126 -0
- package/dist/components/app-shell.js +297 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button-link.js +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/consent-screen.d.ts +65 -0
- package/dist/components/consent-screen.js +123 -0
- package/dist/components/consent-screen.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +15 -1
- package/dist/components/data-table/data-table.js +18 -4
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/data-table/index.d.ts +4 -4
- package/dist/components/data-table/parts.d.ts +27 -3
- package/dist/components/data-table/parts.js +175 -55
- package/dist/components/data-table/parts.js.map +1 -1
- package/dist/components/data-table/types.d.ts +61 -0
- package/dist/components/data-table/use-data-table.js +91 -6
- package/dist/components/data-table/use-data-table.js.map +1 -1
- package/dist/components/data-table/use-server-source.js +119 -28
- package/dist/components/data-table/use-server-source.js.map +1 -1
- package/dist/components/help-panel.d.ts +131 -0
- package/dist/components/help-panel.js +545 -0
- package/dist/components/help-panel.js.map +1 -0
- package/dist/components/login-screen.d.ts +127 -0
- package/dist/components/login-screen.js +339 -0
- package/dist/components/login-screen.js.map +1 -0
- package/dist/components/session-guard.d.ts +268 -0
- package/dist/components/session-guard.js +632 -0
- package/dist/components/session-guard.js.map +1 -0
- package/dist/components/toast.d.ts +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +11 -7
- package/dist/data-table.d.ts +13 -4
- package/dist/data-table.js +10 -2
- package/dist/hooks/use-cortena-theme.js +49 -3
- package/dist/hooks/use-cortena-theme.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +21 -8
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -1
- package/package.json +16 -4
- package/src/agent-chat/a2ui-block.ts +118 -0
- package/src/agent-chat/agui-client.ts +405 -0
- package/src/agent-chat/bridge.ts +433 -0
- package/src/agent-chat/session.ts +392 -0
- package/src/agent-chat/store.ts +738 -0
- package/src/agent-chat/types.ts +213 -0
- package/src/components/admin-permissions/admin-permissions.tsx +130 -0
- package/src/components/admin-permissions/context.tsx +376 -0
- package/src/components/admin-permissions/index.tsx +32 -0
- package/src/components/admin-permissions/licence.tsx +84 -0
- package/src/components/admin-permissions/matrix.tsx +257 -0
- package/src/components/admin-permissions/members.tsx +204 -0
- package/src/components/admin-permissions/role-assignment.tsx +239 -0
- package/src/components/admin-permissions/roles.tsx +169 -0
- package/src/components/admin-permissions/types.ts +231 -0
- package/src/components/agent-chat-popup.tsx +289 -0
- package/src/components/agent-chat.tsx +843 -0
- package/src/components/app-shell.tsx +502 -0
- package/src/components/consent-screen.tsx +239 -0
- package/src/components/data-table/data-table.tsx +36 -0
- package/src/components/data-table/index.tsx +6 -1
- package/src/components/data-table/parts.tsx +223 -47
- package/src/components/data-table/types.ts +68 -0
- package/src/components/data-table/use-data-table.ts +152 -4
- package/src/components/data-table/use-server-source.ts +150 -12
- package/src/components/help-panel.tsx +765 -0
- package/src/components/login-screen.tsx +479 -0
- package/src/components/session-guard.tsx +1071 -0
- package/src/entries/agent-chat.ts +113 -0
- package/src/entries/core.ts +8 -0
- package/src/entries/data-table.ts +41 -0
- package/src/entries/markdown.ts +25 -0
- package/src/hooks/use-cortena-theme.ts +63 -4
- package/src/index.ts +6 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { extractA2UIBlocks, foldA2UIBlocks } from "./a2ui-block.js";
|
|
4
|
+
import { isProvisionalSessionKey, isSessionOfAgent, mintSessionKey, parseHistoryMessages, readStoredSessionKey, separateThinking, wouldShrinkHistory, writeStoredSessionKey } from "./session.js";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
//#region src/agent-chat/store.ts
|
|
7
|
+
/**
|
|
8
|
+
* The chat state, and the hook that drives an `AgentChatClient` from React.
|
|
9
|
+
*
|
|
10
|
+
* The accumulation rules are ported from `cortena-shared/src/stores/use-chat.ts`
|
|
11
|
+
* (Cortena monorepo) so a run leaves this state in the same place it leaves
|
|
12
|
+
* cortenaweb's. The ones that are not obvious, and that a rewrite gets wrong:
|
|
13
|
+
*
|
|
14
|
+
* - A `delta` carries a snapshot, so text and A2UI blocks are **replaced**, not
|
|
15
|
+
* appended. Appending doubles every character.
|
|
16
|
+
* - Thinking is never replaced by something shorter. The upstream emitter can
|
|
17
|
+
* fall back to increments when a snapshot is unavailable, and treating one of
|
|
18
|
+
* those as a snapshot truncates the accumulated chain of thought.
|
|
19
|
+
* - On `final`, a second assistant message in the same turn folds the previous
|
|
20
|
+
* visible text into the chain of thought rather than adding a second bubble,
|
|
21
|
+
* and keeps the A2UI blocks of the message it folded away — the surface is
|
|
22
|
+
* still on screen and still interactive.
|
|
23
|
+
* - A reply that is only a drawn surface has no text, so `text || a2ui.length`
|
|
24
|
+
* is the test for "there is something to show". Testing text alone silently
|
|
25
|
+
* dropped everything the agent drew.
|
|
26
|
+
*/
|
|
27
|
+
const EMPTY = {
|
|
28
|
+
sessionKey: null,
|
|
29
|
+
messages: [],
|
|
30
|
+
streaming: false,
|
|
31
|
+
streamText: "",
|
|
32
|
+
streamThinkingText: "",
|
|
33
|
+
streamA2ui: [],
|
|
34
|
+
runId: null,
|
|
35
|
+
hasMoreHistory: false,
|
|
36
|
+
loadingHistory: false,
|
|
37
|
+
toolOrder: [],
|
|
38
|
+
tools: {},
|
|
39
|
+
approvals: [],
|
|
40
|
+
historyCount: 0,
|
|
41
|
+
sessions: [],
|
|
42
|
+
loadingSessions: false,
|
|
43
|
+
error: null
|
|
44
|
+
};
|
|
45
|
+
function generateId() {
|
|
46
|
+
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
47
|
+
}
|
|
48
|
+
function extractText(message) {
|
|
49
|
+
return (message?.content ?? []).filter((c) => c.type === "text" && c.text).map((c) => c.text).join("");
|
|
50
|
+
}
|
|
51
|
+
function extractThinking(message) {
|
|
52
|
+
return (message?.content ?? []).filter((c) => c.type === "thinking").map((c) => c.thinking ?? c.text ?? "").filter(Boolean).join("\n\n");
|
|
53
|
+
}
|
|
54
|
+
function reduce(state, action) {
|
|
55
|
+
switch (action.kind) {
|
|
56
|
+
case "chat": return reduceChat(state, action.event);
|
|
57
|
+
case "tool": return reduceTool(state, action.event);
|
|
58
|
+
case "approval/request": return state.approvals.some((r) => r.id === action.request.id) ? state : {
|
|
59
|
+
...state,
|
|
60
|
+
approvals: [...state.approvals, action.request]
|
|
61
|
+
};
|
|
62
|
+
case "approval/resolved": return {
|
|
63
|
+
...state,
|
|
64
|
+
approvals: state.approvals.filter((r) => r.id !== action.id)
|
|
65
|
+
};
|
|
66
|
+
case "approval/failed": return state.approvals.some((r) => r.id === action.request.id) ? state : {
|
|
67
|
+
...state,
|
|
68
|
+
approvals: [...state.approvals, {
|
|
69
|
+
...action.request,
|
|
70
|
+
error: action.message
|
|
71
|
+
}]
|
|
72
|
+
};
|
|
73
|
+
case "session": return action.key === state.sessionKey ? state : {
|
|
74
|
+
...EMPTY,
|
|
75
|
+
sessions: state.sessions,
|
|
76
|
+
sessionKey: action.key
|
|
77
|
+
};
|
|
78
|
+
case "send": return {
|
|
79
|
+
...state,
|
|
80
|
+
sessionKey: action.sessionKey,
|
|
81
|
+
messages: action.message ? [...state.messages, action.message] : state.messages,
|
|
82
|
+
streaming: true,
|
|
83
|
+
streamText: "",
|
|
84
|
+
streamThinkingText: "",
|
|
85
|
+
streamA2ui: [],
|
|
86
|
+
runId: action.runId,
|
|
87
|
+
toolOrder: [],
|
|
88
|
+
tools: {},
|
|
89
|
+
approvals: [],
|
|
90
|
+
error: null
|
|
91
|
+
};
|
|
92
|
+
case "send/failed": return {
|
|
93
|
+
...state,
|
|
94
|
+
streaming: false,
|
|
95
|
+
runId: null,
|
|
96
|
+
messages: [...state.messages, {
|
|
97
|
+
id: generateId(),
|
|
98
|
+
role: "assistant",
|
|
99
|
+
text: action.message,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
isError: true
|
|
102
|
+
}]
|
|
103
|
+
};
|
|
104
|
+
case "abort": return {
|
|
105
|
+
...state,
|
|
106
|
+
streaming: false,
|
|
107
|
+
streamText: "",
|
|
108
|
+
streamThinkingText: "",
|
|
109
|
+
streamA2ui: [],
|
|
110
|
+
runId: null,
|
|
111
|
+
approvals: []
|
|
112
|
+
};
|
|
113
|
+
case "history/loading": return {
|
|
114
|
+
...state,
|
|
115
|
+
loadingHistory: true
|
|
116
|
+
};
|
|
117
|
+
case "history/loaded":
|
|
118
|
+
if (wouldShrinkHistory({
|
|
119
|
+
currentSessionKey: state.sessionKey,
|
|
120
|
+
loadedSessionKey: action.sessionKey,
|
|
121
|
+
currentCount: state.messages.length,
|
|
122
|
+
loadedCount: action.messages.length
|
|
123
|
+
})) return {
|
|
124
|
+
...state,
|
|
125
|
+
sessionKey: action.sessionKey,
|
|
126
|
+
hasMoreHistory: action.hasMore,
|
|
127
|
+
loadingHistory: false,
|
|
128
|
+
historyCount: action.records
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
sessionKey: action.sessionKey,
|
|
133
|
+
messages: action.messages,
|
|
134
|
+
hasMoreHistory: action.hasMore,
|
|
135
|
+
loadingHistory: false,
|
|
136
|
+
historyCount: action.records,
|
|
137
|
+
streaming: false,
|
|
138
|
+
streamText: "",
|
|
139
|
+
streamThinkingText: "",
|
|
140
|
+
streamA2ui: []
|
|
141
|
+
};
|
|
142
|
+
case "history/older": return {
|
|
143
|
+
...state,
|
|
144
|
+
messages: [...action.messages, ...state.messages],
|
|
145
|
+
hasMoreHistory: action.hasMore,
|
|
146
|
+
loadingHistory: false,
|
|
147
|
+
historyCount: state.historyCount + action.records
|
|
148
|
+
};
|
|
149
|
+
case "history/failed": return {
|
|
150
|
+
...state,
|
|
151
|
+
loadingHistory: false
|
|
152
|
+
};
|
|
153
|
+
case "sessions/loading": return {
|
|
154
|
+
...state,
|
|
155
|
+
loadingSessions: true
|
|
156
|
+
};
|
|
157
|
+
case "sessions/loaded": return {
|
|
158
|
+
...state,
|
|
159
|
+
sessions: action.sessions,
|
|
160
|
+
loadingSessions: false
|
|
161
|
+
};
|
|
162
|
+
case "error": return {
|
|
163
|
+
...state,
|
|
164
|
+
error: action.message
|
|
165
|
+
};
|
|
166
|
+
default: return state;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function reduceChat(state, event) {
|
|
170
|
+
if (event.sessionKey && state.sessionKey && event.sessionKey !== state.sessionKey) {
|
|
171
|
+
if (!isProvisionalSessionKey(state.sessionKey)) return state;
|
|
172
|
+
state = {
|
|
173
|
+
...state,
|
|
174
|
+
sessionKey: event.sessionKey
|
|
175
|
+
};
|
|
176
|
+
} else if (event.sessionKey && !state.sessionKey) state = {
|
|
177
|
+
...state,
|
|
178
|
+
sessionKey: event.sessionKey
|
|
179
|
+
};
|
|
180
|
+
switch (event.state) {
|
|
181
|
+
case "delta": {
|
|
182
|
+
const text = extractText(event.message);
|
|
183
|
+
const thinking = extractThinking(event.message);
|
|
184
|
+
const a2ui = extractA2UIBlocks(event.message);
|
|
185
|
+
const next = {
|
|
186
|
+
...state,
|
|
187
|
+
streaming: true
|
|
188
|
+
};
|
|
189
|
+
if (text) next.streamText = text;
|
|
190
|
+
if (a2ui.length > 0) next.streamA2ui = a2ui;
|
|
191
|
+
const acceptThinking = !!thinking && thinking.length >= state.streamThinkingText.length;
|
|
192
|
+
if (acceptThinking) next.streamThinkingText = thinking;
|
|
193
|
+
return text || acceptThinking || a2ui.length > 0 ? next : state;
|
|
194
|
+
}
|
|
195
|
+
case "final": {
|
|
196
|
+
const finalText = extractText(event.message);
|
|
197
|
+
const finalThinking = extractThinking(event.message);
|
|
198
|
+
const finalA2ui = extractA2UIBlocks(event.message);
|
|
199
|
+
const text = finalText || state.streamText;
|
|
200
|
+
const thinkingFromBlocks = finalThinking || state.streamThinkingText;
|
|
201
|
+
const a2ui = foldA2UIBlocks(finalA2ui.length > 0 ? finalA2ui : state.streamA2ui);
|
|
202
|
+
const cleared = {
|
|
203
|
+
streaming: false,
|
|
204
|
+
streamText: "",
|
|
205
|
+
streamThinkingText: "",
|
|
206
|
+
streamA2ui: []
|
|
207
|
+
};
|
|
208
|
+
if (!text && a2ui.length === 0) {
|
|
209
|
+
const accumulated = state.streamThinkingText;
|
|
210
|
+
const last = state.messages[state.messages.length - 1];
|
|
211
|
+
if (!accumulated || !foldsInto(last, state.runId)) return {
|
|
212
|
+
...state,
|
|
213
|
+
...cleared
|
|
214
|
+
};
|
|
215
|
+
const messages = [...state.messages];
|
|
216
|
+
messages[messages.length - 1] = {
|
|
217
|
+
...last,
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
thinkingText: [last.thinkingText ?? "", accumulated].filter(Boolean).join("\n\n")
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
...state,
|
|
223
|
+
...cleared,
|
|
224
|
+
messages
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const { thinkingText: tagThinking, mainText } = separateThinking(text);
|
|
228
|
+
const thinkingText = thinkingFromBlocks || tagThinking;
|
|
229
|
+
const displayText = thinkingFromBlocks ? text : mainText;
|
|
230
|
+
const messages = [...state.messages];
|
|
231
|
+
const last = messages[messages.length - 1];
|
|
232
|
+
if (foldsInto(last, state.runId)) {
|
|
233
|
+
const updated = [[last.thinkingText ?? "", last.text.trim()].filter(Boolean).join("\n\n"), thinkingText].filter(Boolean).join("\n\n");
|
|
234
|
+
messages[messages.length - 1] = {
|
|
235
|
+
...last,
|
|
236
|
+
text: displayText,
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
...updated ? { thinkingText: updated } : { thinkingText: void 0 },
|
|
239
|
+
...a2ui.length > 0 || last.a2ui?.length ? { a2ui: foldA2UIBlocks([...last.a2ui ?? [], ...a2ui]) } : {}
|
|
240
|
+
};
|
|
241
|
+
} else messages.push({
|
|
242
|
+
id: generateId(),
|
|
243
|
+
role: "assistant",
|
|
244
|
+
text: displayText,
|
|
245
|
+
timestamp: Date.now(),
|
|
246
|
+
...state.runId ? { runId: state.runId } : {},
|
|
247
|
+
...thinkingText ? { thinkingText } : {},
|
|
248
|
+
...a2ui.length > 0 ? { a2ui } : {}
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
...state,
|
|
252
|
+
...cleared,
|
|
253
|
+
messages
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
case "error": return {
|
|
257
|
+
...state,
|
|
258
|
+
streaming: false,
|
|
259
|
+
streamText: "",
|
|
260
|
+
streamThinkingText: "",
|
|
261
|
+
streamA2ui: [],
|
|
262
|
+
runId: null,
|
|
263
|
+
messages: [...state.messages, {
|
|
264
|
+
id: generateId(),
|
|
265
|
+
role: "assistant",
|
|
266
|
+
text: event.errorMessage ?? "An error occurred",
|
|
267
|
+
timestamp: Date.now(),
|
|
268
|
+
isError: true
|
|
269
|
+
}]
|
|
270
|
+
};
|
|
271
|
+
default: return state;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* May a `final` fold into this message?
|
|
276
|
+
*
|
|
277
|
+
* Only when it is an assistant bubble from the SAME run. Two assistant
|
|
278
|
+
* messages in one turn are one answer with an interruption in the middle, and
|
|
279
|
+
* folding them is right. Two assistant messages from different runs are two
|
|
280
|
+
* answers to two questions, and folding them rewrote the earlier answer with
|
|
281
|
+
* the later one — visibly, in a transcript the user had already read.
|
|
282
|
+
*
|
|
283
|
+
* A message with no `runId` is history, which never folds either.
|
|
284
|
+
*/
|
|
285
|
+
function foldsInto(last, runId) {
|
|
286
|
+
return last !== void 0 && last.role === "assistant" && !last.isError && runId !== null && last.runId === runId;
|
|
287
|
+
}
|
|
288
|
+
function reduceTool(state, event) {
|
|
289
|
+
const id = event.data.id;
|
|
290
|
+
if (!id) return state;
|
|
291
|
+
const base = state.tools[id] ?? {
|
|
292
|
+
id,
|
|
293
|
+
name: event.data.name ?? "tool",
|
|
294
|
+
status: "running",
|
|
295
|
+
startedAt: event.data.startedAt ?? Date.now()
|
|
296
|
+
};
|
|
297
|
+
let entry;
|
|
298
|
+
switch (event.event) {
|
|
299
|
+
case "tool.start":
|
|
300
|
+
entry = {
|
|
301
|
+
...base,
|
|
302
|
+
name: event.data.name ?? base.name,
|
|
303
|
+
status: "running"
|
|
304
|
+
};
|
|
305
|
+
break;
|
|
306
|
+
case "tool.args":
|
|
307
|
+
entry = {
|
|
308
|
+
...base,
|
|
309
|
+
input: event.data.input
|
|
310
|
+
};
|
|
311
|
+
break;
|
|
312
|
+
case "tool.progress":
|
|
313
|
+
entry = {
|
|
314
|
+
...base,
|
|
315
|
+
output: event.data.output
|
|
316
|
+
};
|
|
317
|
+
break;
|
|
318
|
+
case "tool.complete":
|
|
319
|
+
entry = {
|
|
320
|
+
...base,
|
|
321
|
+
status: "complete",
|
|
322
|
+
output: event.data.output,
|
|
323
|
+
completedAt: event.data.completedAt ?? Date.now()
|
|
324
|
+
};
|
|
325
|
+
break;
|
|
326
|
+
case "tool.error":
|
|
327
|
+
entry = {
|
|
328
|
+
...base,
|
|
329
|
+
status: "error",
|
|
330
|
+
output: event.data.output,
|
|
331
|
+
completedAt: event.data.completedAt ?? Date.now()
|
|
332
|
+
};
|
|
333
|
+
break;
|
|
334
|
+
default: return state;
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
...state,
|
|
338
|
+
tools: {
|
|
339
|
+
...state.tools,
|
|
340
|
+
[id]: entry
|
|
341
|
+
},
|
|
342
|
+
toolOrder: state.toolOrder.includes(id) ? state.toolOrder : [...state.toolOrder, id]
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function useAgentChat({ client, storageScope, ephemeral, thinking }) {
|
|
346
|
+
const [state, dispatch] = React.useReducer(reduce, EMPTY);
|
|
347
|
+
const stateRef = React.useRef(state);
|
|
348
|
+
stateRef.current = state;
|
|
349
|
+
React.useEffect(() => {
|
|
350
|
+
const offChat = client.onChat((event) => dispatch({
|
|
351
|
+
kind: "chat",
|
|
352
|
+
event
|
|
353
|
+
}));
|
|
354
|
+
const offTool = client.onTool((event) => dispatch({
|
|
355
|
+
kind: "tool",
|
|
356
|
+
event
|
|
357
|
+
}));
|
|
358
|
+
const offApproval = client.onApproval((event) => {
|
|
359
|
+
if (event.type === "request") dispatch({
|
|
360
|
+
kind: "approval/request",
|
|
361
|
+
request: event.request
|
|
362
|
+
});
|
|
363
|
+
else dispatch({
|
|
364
|
+
kind: "approval/resolved",
|
|
365
|
+
id: event.id
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
return () => {
|
|
369
|
+
offChat();
|
|
370
|
+
offTool();
|
|
371
|
+
offApproval();
|
|
372
|
+
};
|
|
373
|
+
}, [client]);
|
|
374
|
+
const refreshSessions = React.useCallback(() => {
|
|
375
|
+
dispatch({ kind: "sessions/loading" });
|
|
376
|
+
client.request("sessions.list", { includeDerivedTitles: true }).then((response) => {
|
|
377
|
+
const sessions = (response?.sessions ?? []).filter((s) => isSessionOfAgent(s.key, client.agentId));
|
|
378
|
+
dispatch({
|
|
379
|
+
kind: "sessions/loaded",
|
|
380
|
+
sessions
|
|
381
|
+
});
|
|
382
|
+
}).catch((err) => {
|
|
383
|
+
dispatch({
|
|
384
|
+
kind: "sessions/loaded",
|
|
385
|
+
sessions: []
|
|
386
|
+
});
|
|
387
|
+
dispatch({
|
|
388
|
+
kind: "error",
|
|
389
|
+
message: errorText(err)
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}, [client]);
|
|
393
|
+
const loadHistory = React.useCallback((sessionKey) => {
|
|
394
|
+
dispatch({ kind: "history/loading" });
|
|
395
|
+
client.request("chat.history", {
|
|
396
|
+
sessionKey,
|
|
397
|
+
limit: 100
|
|
398
|
+
}).then((response) => {
|
|
399
|
+
const records = response?.messages ?? [];
|
|
400
|
+
dispatch({
|
|
401
|
+
kind: "history/loaded",
|
|
402
|
+
sessionKey,
|
|
403
|
+
messages: parseHistoryMessages(records),
|
|
404
|
+
hasMore: response?.hasMore ?? false,
|
|
405
|
+
records: records.length
|
|
406
|
+
});
|
|
407
|
+
}).catch(() => dispatch({ kind: "history/failed" }));
|
|
408
|
+
}, [client]);
|
|
409
|
+
const restored = React.useRef(false);
|
|
410
|
+
React.useEffect(() => {
|
|
411
|
+
if (restored.current) return;
|
|
412
|
+
restored.current = true;
|
|
413
|
+
const stored = ephemeral ? null : readStoredSessionKey(storageScope);
|
|
414
|
+
const key = stored && isSessionOfAgent(stored, client.agentId) ? stored : null;
|
|
415
|
+
if (key) {
|
|
416
|
+
dispatch({
|
|
417
|
+
kind: "session",
|
|
418
|
+
key
|
|
419
|
+
});
|
|
420
|
+
loadHistory(key);
|
|
421
|
+
}
|
|
422
|
+
refreshSessions();
|
|
423
|
+
}, [
|
|
424
|
+
client,
|
|
425
|
+
ephemeral,
|
|
426
|
+
loadHistory,
|
|
427
|
+
refreshSessions,
|
|
428
|
+
storageScope
|
|
429
|
+
]);
|
|
430
|
+
const setSession = React.useCallback((key) => {
|
|
431
|
+
dispatch({
|
|
432
|
+
kind: "session",
|
|
433
|
+
key
|
|
434
|
+
});
|
|
435
|
+
if (!ephemeral) writeStoredSessionKey(storageScope, key);
|
|
436
|
+
}, [ephemeral, storageScope]);
|
|
437
|
+
const run = React.useCallback((text, extra = {}) => {
|
|
438
|
+
const current = stateRef.current;
|
|
439
|
+
if (current.streaming) return;
|
|
440
|
+
const sessionKey = current.sessionKey ?? mintSessionKey(client.agentId);
|
|
441
|
+
if (!current.sessionKey && !ephemeral) writeStoredSessionKey(storageScope, sessionKey);
|
|
442
|
+
const runId = generateId();
|
|
443
|
+
const message = text || !extra.a2uiAction && !extra.mcpAppAction ? {
|
|
444
|
+
id: runId,
|
|
445
|
+
role: "user",
|
|
446
|
+
text,
|
|
447
|
+
timestamp: Date.now(),
|
|
448
|
+
runId
|
|
449
|
+
} : null;
|
|
450
|
+
dispatch({
|
|
451
|
+
kind: "send",
|
|
452
|
+
message,
|
|
453
|
+
runId,
|
|
454
|
+
sessionKey
|
|
455
|
+
});
|
|
456
|
+
client.send({
|
|
457
|
+
sessionKey,
|
|
458
|
+
message: text,
|
|
459
|
+
runId,
|
|
460
|
+
...thinking ? { thinking } : {},
|
|
461
|
+
...extra
|
|
462
|
+
}).then(() => refreshSessions()).catch((err) => dispatch({
|
|
463
|
+
kind: "send/failed",
|
|
464
|
+
message: errorText(err)
|
|
465
|
+
}));
|
|
466
|
+
}, [
|
|
467
|
+
client,
|
|
468
|
+
ephemeral,
|
|
469
|
+
refreshSessions,
|
|
470
|
+
storageScope,
|
|
471
|
+
thinking
|
|
472
|
+
]);
|
|
473
|
+
const send = React.useCallback((text, attachments) => {
|
|
474
|
+
run(text, attachments?.length ? { attachments } : {});
|
|
475
|
+
}, [run]);
|
|
476
|
+
const sendAction = React.useCallback((a2uiAction) => {
|
|
477
|
+
run("", { a2uiAction });
|
|
478
|
+
}, [run]);
|
|
479
|
+
const abort = React.useCallback(() => {
|
|
480
|
+
const { runId, sessionKey } = stateRef.current;
|
|
481
|
+
dispatch({ kind: "abort" });
|
|
482
|
+
client.abort({
|
|
483
|
+
...runId ? { runId } : {},
|
|
484
|
+
...sessionKey ? { sessionKey } : {}
|
|
485
|
+
}).catch(() => {});
|
|
486
|
+
}, [client]);
|
|
487
|
+
const resolveApproval = React.useCallback((id, decision) => {
|
|
488
|
+
const request = stateRef.current.approvals.find((r) => r.id === id);
|
|
489
|
+
if (!request) return;
|
|
490
|
+
dispatch({
|
|
491
|
+
kind: "approval/resolved",
|
|
492
|
+
id
|
|
493
|
+
});
|
|
494
|
+
client.resolveApproval(id, decision, {
|
|
495
|
+
runId: request.runId,
|
|
496
|
+
sessionKey: request.sessionKey
|
|
497
|
+
}).catch((err) => {
|
|
498
|
+
dispatch({
|
|
499
|
+
kind: "approval/failed",
|
|
500
|
+
request,
|
|
501
|
+
message: errorText(err)
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
}, [client]);
|
|
505
|
+
const newSession = React.useCallback(() => {
|
|
506
|
+
setSession(mintSessionKey(client.agentId));
|
|
507
|
+
}, [client, setSession]);
|
|
508
|
+
const resumeSession = React.useCallback((key) => {
|
|
509
|
+
setSession(key);
|
|
510
|
+
loadHistory(key);
|
|
511
|
+
}, [loadHistory, setSession]);
|
|
512
|
+
const loadOlder = React.useCallback(() => {
|
|
513
|
+
const current = stateRef.current;
|
|
514
|
+
if (!current.sessionKey || current.loadingHistory || !current.hasMoreHistory) return;
|
|
515
|
+
dispatch({ kind: "history/loading" });
|
|
516
|
+
client.request("chat.history", {
|
|
517
|
+
sessionKey: current.sessionKey,
|
|
518
|
+
limit: 100,
|
|
519
|
+
offset: current.historyCount
|
|
520
|
+
}).then((response) => {
|
|
521
|
+
const records = response?.messages ?? [];
|
|
522
|
+
dispatch({
|
|
523
|
+
kind: "history/older",
|
|
524
|
+
messages: parseHistoryMessages(records),
|
|
525
|
+
hasMore: response?.hasMore ?? false,
|
|
526
|
+
records: records.length
|
|
527
|
+
});
|
|
528
|
+
}).catch(() => dispatch({ kind: "history/failed" }));
|
|
529
|
+
}, [client]);
|
|
530
|
+
return {
|
|
531
|
+
...state,
|
|
532
|
+
send,
|
|
533
|
+
sendAction,
|
|
534
|
+
abort,
|
|
535
|
+
resolveApproval,
|
|
536
|
+
newSession,
|
|
537
|
+
resumeSession,
|
|
538
|
+
loadOlder,
|
|
539
|
+
refreshSessions
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function errorText(err) {
|
|
543
|
+
return err instanceof Error ? err.message : "The agent could not be reached";
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
export { useAgentChat };
|
|
547
|
+
|
|
548
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../src/agent-chat/store.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The chat state, and the hook that drives an `AgentChatClient` from React.\n *\n * The accumulation rules are ported from `cortena-shared/src/stores/use-chat.ts`\n * (Cortena monorepo) so a run leaves this state in the same place it leaves\n * cortenaweb's. The ones that are not obvious, and that a rewrite gets wrong:\n *\n * - A `delta` carries a snapshot, so text and A2UI blocks are **replaced**, not\n * appended. Appending doubles every character.\n * - Thinking is never replaced by something shorter. The upstream emitter can\n * fall back to increments when a snapshot is unavailable, and treating one of\n * those as a snapshot truncates the accumulated chain of thought.\n * - On `final`, a second assistant message in the same turn folds the previous\n * visible text into the chain of thought rather than adding a second bubble,\n * and keeps the A2UI blocks of the message it folded away — the surface is\n * still on screen and still interactive.\n * - A reply that is only a drawn surface has no text, so `text || a2ui.length`\n * is the test for \"there is something to show\". Testing text alone silently\n * dropped everything the agent drew.\n */\n\nimport * as React from \"react\";\nimport { type A2UIChatBlock, extractA2UIBlocks, foldA2UIBlocks } from \"./a2ui-block\";\nimport {\n isProvisionalSessionKey,\n isSessionOfAgent,\n mintSessionKey,\n parseHistoryMessages,\n readStoredSessionKey,\n separateThinking,\n wouldShrinkHistory,\n writeStoredSessionKey,\n} from \"./session\";\nimport type {\n AgentApprovalDecision,\n AgentApprovalRequest,\n AgentChatClient,\n AgentChatEvent,\n AgentChatHistoryResponse,\n AgentChatMessage,\n AgentSession,\n AgentSessionsListResponse,\n AgentToolEvent,\n} from \"./types\";\n\nexport interface AgentToolEntry {\n id: string;\n name: string;\n status: \"running\" | \"complete\" | \"error\";\n input?: unknown;\n output?: unknown;\n startedAt: number;\n completedAt?: number;\n}\n\nexport interface AgentChatState {\n sessionKey: string | null;\n messages: AgentChatMessage[];\n streaming: boolean;\n streamText: string;\n streamThinkingText: string;\n streamA2ui: A2UIChatBlock[];\n runId: string | null;\n hasMoreHistory: boolean;\n loadingHistory: boolean;\n toolOrder: string[];\n tools: Record<string, AgentToolEntry>;\n approvals: AgentApprovalRequest[];\n /**\n * Transcript RECORDS loaded, which is not `messages.length`.\n *\n * `parseHistoryMessages` folds a turn — an assistant preamble, the tool call,\n * the tool result, the answer — into one visible message, so the two numbers\n * diverge on the very first agent turn. `offset` is an offset into the\n * server's records, and paging with the folded count asked for records the\n * screen already had and skipped the ones it did not: \"load older\" silently\n * lost messages the further back a user read.\n */\n historyCount: number;\n sessions: AgentSession[];\n loadingSessions: boolean;\n /** A failure the user has to be told about, e.g. no template for the slug. */\n error: string | null;\n}\n\nconst EMPTY: AgentChatState = {\n sessionKey: null,\n messages: [],\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n hasMoreHistory: false,\n loadingHistory: false,\n toolOrder: [],\n tools: {},\n approvals: [],\n historyCount: 0,\n sessions: [],\n loadingSessions: false,\n error: null,\n};\n\ntype Action =\n | { kind: \"chat\"; event: AgentChatEvent }\n | { kind: \"tool\"; event: AgentToolEvent }\n | { kind: \"approval/request\"; request: AgentApprovalRequest }\n | { kind: \"approval/resolved\"; id: string }\n | { kind: \"approval/failed\"; request: AgentApprovalRequest; message: string }\n | { kind: \"session\"; key: string | null }\n | { kind: \"send\"; message: AgentChatMessage | null; runId: string; sessionKey: string }\n | { kind: \"send/failed\"; message: string }\n | { kind: \"abort\" }\n | { kind: \"history/loading\" }\n | {\n kind: \"history/loaded\";\n sessionKey: string;\n messages: AgentChatMessage[];\n hasMore: boolean;\n records: number;\n }\n | { kind: \"history/older\"; messages: AgentChatMessage[]; hasMore: boolean; records: number }\n | { kind: \"history/failed\" }\n | { kind: \"sessions/loading\" }\n | { kind: \"sessions/loaded\"; sessions: AgentSession[] }\n | { kind: \"error\"; message: string | null };\n\nfunction generateId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction extractText(message?: AgentChatEvent[\"message\"]): string {\n return (message?.content ?? [])\n .filter((c) => c.type === \"text\" && c.text)\n .map((c) => c.text!)\n .join(\"\");\n}\n\nfunction extractThinking(message?: AgentChatEvent[\"message\"]): string {\n const blocks = (message?.content ?? [])\n .filter((c) => c.type === \"thinking\")\n .map((c) => c.thinking ?? c.text ?? \"\")\n .filter(Boolean);\n return blocks.join(\"\\n\\n\");\n}\n\nfunction reduce(state: AgentChatState, action: Action): AgentChatState {\n switch (action.kind) {\n case \"chat\":\n return reduceChat(state, action.event);\n case \"tool\":\n return reduceTool(state, action.event);\n case \"approval/request\":\n return state.approvals.some((r) => r.id === action.request.id)\n ? state\n : { ...state, approvals: [...state.approvals, action.request] };\n case \"approval/resolved\":\n return { ...state, approvals: state.approvals.filter((r) => r.id !== action.id) };\n case \"approval/failed\":\n // The decision did not land, so the card comes back — with the reason on\n // it. Removing it optimistically and then swallowing the failure left\n // the user certain they had answered and the run paused for ever.\n return state.approvals.some((r) => r.id === action.request.id)\n ? state\n : {\n ...state,\n approvals: [...state.approvals, { ...action.request, error: action.message }],\n };\n case \"session\":\n return action.key === state.sessionKey\n ? state\n : { ...EMPTY, sessions: state.sessions, sessionKey: action.key };\n case \"send\":\n return {\n ...state,\n sessionKey: action.sessionKey,\n messages: action.message ? [...state.messages, action.message] : state.messages,\n streaming: true,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: action.runId,\n toolOrder: [],\n tools: {},\n // A new run is a new set of pending decisions. An approval card left\n // over from the previous run points at a run that is over, and\n // answering it sends a decision nothing is waiting for.\n approvals: [],\n error: null,\n };\n case \"send/failed\":\n return {\n ...state,\n streaming: false,\n runId: null,\n messages: [\n ...state.messages,\n {\n id: generateId(),\n role: \"assistant\",\n text: action.message,\n timestamp: Date.now(),\n isError: true,\n },\n ],\n };\n case \"abort\":\n // Reset immediately so the composer flips Stop -> Send without waiting on\n // a server event an aborted run does not reliably send.\n return {\n ...state,\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n // The run they belonged to is gone; the decisions go with it.\n approvals: [],\n };\n case \"history/loading\":\n return { ...state, loadingHistory: true };\n case \"history/loaded\":\n if (\n wouldShrinkHistory({\n currentSessionKey: state.sessionKey,\n loadedSessionKey: action.sessionKey,\n currentCount: state.messages.length,\n loadedCount: action.messages.length,\n })\n ) {\n return {\n ...state,\n sessionKey: action.sessionKey,\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: action.records,\n };\n }\n return {\n ...state,\n sessionKey: action.sessionKey,\n messages: action.messages,\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: action.records,\n // Avoids an orphan streaming bubble beside reloaded history.\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n };\n case \"history/older\":\n return {\n ...state,\n messages: [...action.messages, ...state.messages],\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: state.historyCount + action.records,\n };\n case \"history/failed\":\n return { ...state, loadingHistory: false };\n case \"sessions/loading\":\n return { ...state, loadingSessions: true };\n case \"sessions/loaded\":\n return { ...state, sessions: action.sessions, loadingSessions: false };\n case \"error\":\n return { ...state, error: action.message };\n default:\n return state;\n }\n}\n\nfunction reduceChat(state: AgentChatState, event: AgentChatEvent): AgentChatState {\n // Events from another session are dropped, or one window's run leaks into\n // another window's transcript. A provisional key is the exception: the server\n // echoes it back today, but if it ever canonicalised the key instead, dropping\n // the event would silently kill the whole first turn.\n if (event.sessionKey && state.sessionKey && event.sessionKey !== state.sessionKey) {\n if (!isProvisionalSessionKey(state.sessionKey)) {\n return state;\n }\n state = { ...state, sessionKey: event.sessionKey };\n } else if (event.sessionKey && !state.sessionKey) {\n state = { ...state, sessionKey: event.sessionKey };\n }\n\n switch (event.state) {\n case \"delta\": {\n const text = extractText(event.message);\n const thinking = extractThinking(event.message);\n const a2ui = extractA2UIBlocks(event.message);\n const next: AgentChatState = { ...state, streaming: true };\n if (text) {\n next.streamText = text;\n }\n if (a2ui.length > 0) {\n next.streamA2ui = a2ui;\n }\n // Never replace accumulated thinking with something shorter.\n const acceptThinking = !!thinking && thinking.length >= state.streamThinkingText.length;\n if (acceptThinking) {\n next.streamThinkingText = thinking;\n }\n return text || acceptThinking || a2ui.length > 0 ? next : state;\n }\n case \"final\": {\n const finalText = extractText(event.message);\n const finalThinking = extractThinking(event.message);\n const finalA2ui = extractA2UIBlocks(event.message);\n const text = finalText || state.streamText;\n const thinkingFromBlocks = finalThinking || state.streamThinkingText;\n const a2ui = foldA2UIBlocks(finalA2ui.length > 0 ? finalA2ui : state.streamA2ui);\n // The run id deliberately SURVIVES a final. A run can end more than\n // once — an interrupt for an approval, then the continuation — and\n // clearing it here meant the second final saw `runId: null`, could not\n // fold, and pushed a second bubble: one answer to one question, drawn\n // as two. `send` sets the next run's id, and `abort`, `error` and\n // `send/failed` each clear it, so nothing outlives the turn it belongs\n // to.\n const cleared = {\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [] as A2UIChatBlock[],\n };\n\n if (!text && a2ui.length === 0) {\n // No text and nothing drawn: fold whatever thinking accumulated into\n // the previous turn rather than dropping it.\n const accumulated = state.streamThinkingText;\n const last = state.messages[state.messages.length - 1];\n if (!accumulated || !foldsInto(last, state.runId)) {\n return { ...state, ...cleared };\n }\n const messages = [...state.messages];\n messages[messages.length - 1] = {\n ...last,\n timestamp: Date.now(),\n thinkingText: [last.thinkingText ?? \"\", accumulated].filter(Boolean).join(\"\\n\\n\"),\n };\n return { ...state, ...cleared, messages };\n }\n\n const { thinkingText: tagThinking, mainText } = separateThinking(text);\n const thinkingText = thinkingFromBlocks || tagThinking;\n const displayText = thinkingFromBlocks ? text : mainText;\n const messages = [...state.messages];\n const last = messages[messages.length - 1];\n\n if (foldsInto(last, state.runId)) {\n const combined = [last.thinkingText ?? \"\", last.text.trim()].filter(Boolean).join(\"\\n\\n\");\n const updated = [combined, thinkingText].filter(Boolean).join(\"\\n\\n\");\n messages[messages.length - 1] = {\n ...last,\n text: displayText,\n timestamp: Date.now(),\n ...(updated ? { thinkingText: updated } : { thinkingText: undefined }),\n ...(a2ui.length > 0 || last.a2ui?.length\n ? { a2ui: foldA2UIBlocks([...(last.a2ui ?? []), ...a2ui]) }\n : {}),\n };\n } else {\n messages.push({\n id: generateId(),\n role: \"assistant\",\n text: displayText,\n timestamp: Date.now(),\n // The run this bubble belongs to, so the NEXT run's `final` cannot\n // fold its answer into it.\n ...(state.runId ? { runId: state.runId } : {}),\n ...(thinkingText ? { thinkingText } : {}),\n ...(a2ui.length > 0 ? { a2ui } : {}),\n });\n }\n return { ...state, ...cleared, messages };\n }\n case \"error\":\n return {\n ...state,\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n messages: [\n ...state.messages,\n {\n id: generateId(),\n role: \"assistant\",\n text: event.errorMessage ?? \"An error occurred\",\n timestamp: Date.now(),\n isError: true,\n },\n ],\n };\n default:\n return state;\n }\n}\n\n/**\n * May a `final` fold into this message?\n *\n * Only when it is an assistant bubble from the SAME run. Two assistant\n * messages in one turn are one answer with an interruption in the middle, and\n * folding them is right. Two assistant messages from different runs are two\n * answers to two questions, and folding them rewrote the earlier answer with\n * the later one — visibly, in a transcript the user had already read.\n *\n * A message with no `runId` is history, which never folds either.\n */\nfunction foldsInto(\n last: AgentChatMessage | undefined,\n runId: string | null,\n): last is AgentChatMessage {\n return (\n last !== undefined &&\n last.role === \"assistant\" &&\n !last.isError &&\n runId !== null &&\n last.runId === runId\n );\n}\n\nfunction reduceTool(state: AgentChatState, event: AgentToolEvent): AgentChatState {\n const id = event.data.id;\n if (!id) {\n return state;\n }\n const existing = state.tools[id];\n const base: AgentToolEntry = existing ?? {\n id,\n name: event.data.name ?? \"tool\",\n status: \"running\",\n startedAt: event.data.startedAt ?? Date.now(),\n };\n let entry: AgentToolEntry;\n switch (event.event) {\n case \"tool.start\":\n entry = { ...base, name: event.data.name ?? base.name, status: \"running\" };\n break;\n case \"tool.args\":\n entry = { ...base, input: event.data.input };\n break;\n case \"tool.progress\":\n entry = { ...base, output: event.data.output };\n break;\n case \"tool.complete\":\n entry = {\n ...base,\n status: \"complete\",\n output: event.data.output,\n completedAt: event.data.completedAt ?? Date.now(),\n };\n break;\n case \"tool.error\":\n entry = {\n ...base,\n status: \"error\",\n output: event.data.output,\n completedAt: event.data.completedAt ?? Date.now(),\n };\n break;\n default:\n return state;\n }\n return {\n ...state,\n tools: { ...state.tools, [id]: entry },\n toolOrder: state.toolOrder.includes(id) ? state.toolOrder : [...state.toolOrder, id],\n };\n}\n\n/* ── the hook ────────────────────────────────────────────────────────────── */\n\nexport interface UseAgentChatOptions {\n client: AgentChatClient;\n /**\n * Names the `sessionStorage` slot the current session key lives in. Two\n * pop-ups with different scopes never share a key, which is what keeps one\n * extension's chat out of another's.\n */\n storageScope: string;\n /** Skip reading and writing `sessionStorage`; for a transient mount. */\n ephemeral?: boolean;\n thinking?: \"low\" | \"medium\" | \"high\";\n}\n\nexport interface UseAgentChatResult extends AgentChatState {\n send: (text: string, attachments?: unknown[]) => void;\n sendAction: (action: NonNullable<Parameters<AgentChatClient[\"send\"]>[0][\"a2uiAction\"]>) => void;\n abort: () => void;\n resolveApproval: (id: string, decision: AgentApprovalDecision) => void;\n newSession: () => void;\n resumeSession: (key: string) => void;\n loadOlder: () => void;\n refreshSessions: () => void;\n}\n\nexport function useAgentChat({\n client,\n storageScope,\n ephemeral,\n thinking,\n}: UseAgentChatOptions): UseAgentChatResult {\n const [state, dispatch] = React.useReducer(reduce, EMPTY);\n const stateRef = React.useRef(state);\n stateRef.current = state;\n\n // Subscribe once per client. A pop-up that collapses unmounts the surface but\n // keeps the client, so a run started before the collapse still lands.\n React.useEffect(() => {\n const offChat = client.onChat((event) => dispatch({ kind: \"chat\", event }));\n const offTool = client.onTool((event) => dispatch({ kind: \"tool\", event }));\n const offApproval = client.onApproval((event) => {\n if (event.type === \"request\") {\n dispatch({ kind: \"approval/request\", request: event.request });\n } else {\n dispatch({ kind: \"approval/resolved\", id: event.id });\n }\n });\n return () => {\n offChat();\n offTool();\n offApproval();\n };\n }, [client]);\n\n const refreshSessions = React.useCallback(() => {\n dispatch({ kind: \"sessions/loading\" });\n void client\n .request<AgentSessionsListResponse>(\"sessions.list\", { includeDerivedTitles: true })\n .then((response) => {\n // Filtered to this agent: a shared control plane lists every session on\n // the pod, and an extension must never show another agent's chats.\n const sessions = (response?.sessions ?? []).filter((s) =>\n isSessionOfAgent(s.key, client.agentId),\n );\n dispatch({ kind: \"sessions/loaded\", sessions });\n })\n .catch((err: unknown) => {\n dispatch({ kind: \"sessions/loaded\", sessions: [] });\n dispatch({ kind: \"error\", message: errorText(err) });\n });\n }, [client]);\n\n const loadHistory = React.useCallback(\n (sessionKey: string) => {\n dispatch({ kind: \"history/loading\" });\n void client\n .request<AgentChatHistoryResponse>(\"chat.history\", { sessionKey, limit: 100 })\n .then((response) => {\n const records = response?.messages ?? [];\n dispatch({\n kind: \"history/loaded\",\n sessionKey,\n messages: parseHistoryMessages(records),\n hasMore: response?.hasMore ?? false,\n records: records.length,\n });\n })\n .catch(() => dispatch({ kind: \"history/failed\" }));\n },\n [client],\n );\n\n // Restore this window's session, or mint one. `sessionStorage`, not\n // `localStorage`: a second window must not adopt the first window's session.\n const restored = React.useRef(false);\n React.useEffect(() => {\n if (restored.current) {\n return;\n }\n restored.current = true;\n const stored = ephemeral ? null : readStoredSessionKey(storageScope);\n const key = stored && isSessionOfAgent(stored, client.agentId) ? stored : null;\n if (key) {\n dispatch({ kind: \"session\", key });\n loadHistory(key);\n }\n refreshSessions();\n }, [client, ephemeral, loadHistory, refreshSessions, storageScope]);\n\n const setSession = React.useCallback(\n (key: string) => {\n dispatch({ kind: \"session\", key });\n if (!ephemeral) {\n writeStoredSessionKey(storageScope, key);\n }\n },\n [ephemeral, storageScope],\n );\n\n const run = React.useCallback(\n (text: string, extra: Partial<Parameters<AgentChatClient[\"send\"]>[0]> = {}) => {\n const current = stateRef.current;\n // One run at a time. A second `send` on a streaming session opens a\n // second stream into one store: two sets of deltas overwrite each\n // other's snapshot, `final` folds one run's answer into the other's\n // bubble, and `abort` can only reach whichever the client thinks is\n // active. The composer disables Send while a run is live; this is the\n // same rule for Enter, for a2ui actions and for a caller of the hook.\n if (current.streaming) {\n return;\n }\n const sessionKey = current.sessionKey ?? mintSessionKey(client.agentId);\n if (!current.sessionKey && !ephemeral) {\n writeStoredSessionKey(storageScope, sessionKey);\n }\n const runId = generateId();\n // An action with no typed text is a click, not a sentence: a user bubble\n // for it would put `{\"name\":\"createTask\"}` in the transcript as though\n // the user had said it.\n const message: AgentChatMessage | null =\n text || (!extra.a2uiAction && !extra.mcpAppAction)\n ? { id: runId, role: \"user\", text, timestamp: Date.now(), runId }\n : null;\n dispatch({ kind: \"send\", message, runId, sessionKey });\n void client\n .send({\n sessionKey,\n message: text,\n runId,\n ...(thinking ? { thinking } : {}),\n ...extra,\n })\n .then(() => refreshSessions())\n .catch((err: unknown) => dispatch({ kind: \"send/failed\", message: errorText(err) }));\n },\n [client, ephemeral, refreshSessions, storageScope, thinking],\n );\n\n const send = React.useCallback(\n (text: string, attachments?: unknown[]) => {\n run(text, attachments?.length ? { attachments } : {});\n },\n [run],\n );\n\n const sendAction = React.useCallback(\n (a2uiAction: NonNullable<Parameters<AgentChatClient[\"send\"]>[0][\"a2uiAction\"]>) => {\n run(\"\", { a2uiAction });\n },\n [run],\n );\n\n const abort = React.useCallback(() => {\n const { runId, sessionKey } = stateRef.current;\n dispatch({ kind: \"abort\" });\n void client\n .abort({\n ...(runId ? { runId } : {}),\n ...(sessionKey ? { sessionKey } : {}),\n })\n .catch(() => {\n // Best-effort: local state is already reset, and dropping the stream\n // aborts the run server-side anyway.\n });\n }, [client]);\n\n const resolveApproval = React.useCallback(\n (id: string, decision: AgentApprovalDecision) => {\n const request = stateRef.current.approvals.find((r) => r.id === id);\n if (!request) {\n return;\n }\n dispatch({ kind: \"approval/resolved\", id });\n // Bound to the run and the session the REQUEST arrived on, never to\n // whatever is in flight now. The two come apart the moment a user opens\n // another session while a command waits for an answer.\n void client\n .resolveApproval(id, decision, {\n runId: request.runId,\n sessionKey: request.sessionKey,\n })\n .catch((err: unknown) => {\n dispatch({ kind: \"approval/failed\", request, message: errorText(err) });\n });\n },\n [client],\n );\n\n const newSession = React.useCallback(() => {\n setSession(mintSessionKey(client.agentId));\n }, [client, setSession]);\n\n const resumeSession = React.useCallback(\n (key: string) => {\n setSession(key);\n loadHistory(key);\n },\n [loadHistory, setSession],\n );\n\n const loadOlder = React.useCallback(() => {\n const current = stateRef.current;\n if (!current.sessionKey || current.loadingHistory || !current.hasMoreHistory) {\n return;\n }\n dispatch({ kind: \"history/loading\" });\n void client\n .request<AgentChatHistoryResponse>(\"chat.history\", {\n sessionKey: current.sessionKey,\n limit: 100,\n // Records, not bubbles. See `historyCount`.\n offset: current.historyCount,\n })\n .then((response) => {\n const records = response?.messages ?? [];\n dispatch({\n kind: \"history/older\",\n messages: parseHistoryMessages(records),\n hasMore: response?.hasMore ?? false,\n records: records.length,\n });\n })\n .catch(() => dispatch({ kind: \"history/failed\" }));\n }, [client]);\n\n return {\n ...state,\n send,\n sendAction,\n abort,\n resolveApproval,\n newSession,\n resumeSession,\n loadOlder,\n refreshSessions,\n };\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : \"The agent could not be reached\";\n}\n"],"mappings":"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"}
|