@economic/agents-react 1.4.5 → 1.6.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 +14 -8
- package/dist/index.d.mts +6 -2
- package/dist/index.mjs +118 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,22 +123,27 @@ function Chat({ userId, authToken }: { userId: string; authToken: string }) {
|
|
|
123
123
|
| Field | Type | Description |
|
|
124
124
|
| ----------------- | ----------------------- | --------------------------------------------------------------------------- |
|
|
125
125
|
| `status` | `AgentConnectionStatus` | Connection status of the assistant, or of the active chat once one is open. |
|
|
126
|
-
| `chats` | `ChatSummary[]` |
|
|
126
|
+
| `chats` | `ChatSummary[]` | Started chats plus the active empty draft, most recently updated first. |
|
|
127
127
|
| `currentChatName` | `string \| undefined` | Id of the active chat, if any. |
|
|
128
|
-
| `assistant` | `AssistantActions` | `getChats`, `createChat`, `openChat`, `deleteChat`.
|
|
128
|
+
| `assistant` | `AssistantActions` | `getChats`, `createChat`, `openChat`, `deleteChat`, `renameChat`. |
|
|
129
129
|
| `chat` | `useChat` result | The active chat — `chat.chat` is the message interface (see below). |
|
|
130
130
|
|
|
131
131
|
`assistant` actions:
|
|
132
132
|
|
|
133
|
-
| Action | Signature
|
|
134
|
-
| -------------------- |
|
|
135
|
-
| `createChat()` | `() => Promise<string>`
|
|
136
|
-
| `openChat(chatId)` | `(chatId: string) => void`
|
|
137
|
-
| `getChats()` | `() => Promise<ChatSummary[]>`
|
|
138
|
-
| `deleteChat(chatId)` | `(chatId: string) => Promise<void>`
|
|
133
|
+
| Action | Signature | Description |
|
|
134
|
+
| -------------------- | -------------------------------------------------- | ------------------------------------------------ |
|
|
135
|
+
| `createChat()` | `() => Promise<string>` | Creates a chat, makes it active, returns its id. |
|
|
136
|
+
| `openChat(chatId)` | `(chatId: string) => void` | Switches the active chat. |
|
|
137
|
+
| `getChats()` | `() => Promise<ChatSummary[]>` | Refreshes and returns the chat list. |
|
|
138
|
+
| `deleteChat(chatId)` | `(chatId: string) => Promise<void>` | Deletes a chat and clears the active selection. |
|
|
139
|
+
| `renameChat(chatId)` | `(chatId: string, title: string) => Promise<void>` | Renames a registered chat. |
|
|
139
140
|
|
|
140
141
|
> Titles and summaries are generated server-side a few seconds after the first message, so refetch with `getChats()` shortly after sending the opening message if you want the new title to appear.
|
|
141
142
|
|
|
143
|
+
The active empty chat is represented locally until its first turn registers it on the server.
|
|
144
|
+
|
|
145
|
+
After the active draft's first turn finishes, `useAssistant` refreshes the chat list automatically so the temporary entry is replaced by its persisted title and summary.
|
|
146
|
+
|
|
142
147
|
---
|
|
143
148
|
|
|
144
149
|
## Options
|
|
@@ -174,6 +179,7 @@ type AgentConnectionState = {
|
|
|
174
179
|
|
|
175
180
|
type ChatSummary = {
|
|
176
181
|
name: string;
|
|
182
|
+
isTemporary?: boolean; // true for the active chat before its first turn is registered
|
|
177
183
|
title?: string;
|
|
178
184
|
summary?: string;
|
|
179
185
|
created_at: number;
|
package/dist/index.d.mts
CHANGED
|
@@ -10,9 +10,11 @@ type Chat = ReturnType<typeof useAgentChat>;
|
|
|
10
10
|
type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "error";
|
|
11
11
|
type AgentConnectionType = "agent" | "chat" | "assistant";
|
|
12
12
|
type ChatSummary = {
|
|
13
|
-
name: string;
|
|
13
|
+
name: string; /** True when this entry only represents the active, unregistered chat facet. */
|
|
14
|
+
isTemporary?: boolean;
|
|
14
15
|
title?: string;
|
|
15
16
|
summary?: string;
|
|
17
|
+
title_set_by_user?: number;
|
|
16
18
|
created_at: number;
|
|
17
19
|
updated_at: number;
|
|
18
20
|
};
|
|
@@ -29,6 +31,7 @@ type GetChatsHandler = () => Promise<ChatSummary[]>;
|
|
|
29
31
|
type CreateChatHandler = () => Promise<string>;
|
|
30
32
|
type OpenChatHandler = (chatId: string) => void;
|
|
31
33
|
type DeleteChatHandler = (chatId: string) => Promise<void>;
|
|
34
|
+
type RenameChatHandler = (chatId: string, title: string) => Promise<void>;
|
|
32
35
|
//#endregion
|
|
33
36
|
//#region src/useAgent.d.ts
|
|
34
37
|
interface UseAgentReturn {
|
|
@@ -76,6 +79,7 @@ interface Assistant {
|
|
|
76
79
|
createChat: CreateChatHandler;
|
|
77
80
|
openChat: OpenChatHandler;
|
|
78
81
|
deleteChat: DeleteChatHandler;
|
|
82
|
+
renameChat: RenameChatHandler;
|
|
79
83
|
}
|
|
80
84
|
interface UseAssistantReturn {
|
|
81
85
|
currentChatName: string | undefined;
|
|
@@ -89,4 +93,4 @@ interface UseAssistantOptions<ToolContext extends Record<string, unknown> = Reco
|
|
|
89
93
|
}
|
|
90
94
|
declare function useAssistant<ToolContext extends Record<string, unknown>>(options: UseAssistantOptions<ToolContext>): UseAssistantReturn;
|
|
91
95
|
//#endregion
|
|
92
|
-
export { type Agent, type AgentConnectionStatus, type AgentConnectionType, type Assistant, type Chat, type ChatSummary, type CreateChatHandler, type DeleteChatHandler, type GetChatsHandler, type GetMessageFeedbackHandler, type MessageFeedback, type MessageFeedbackHandler, type OpenChatHandler, type UseAgentReturn, type UseAssistantReturn, type UseChatReturn, getToolApproval, getToolCallId, getToolPartState, useAgent, useAssistant, useChat };
|
|
96
|
+
export { type Agent, type AgentConnectionStatus, type AgentConnectionType, type Assistant, type Chat, type ChatSummary, type CreateChatHandler, type DeleteChatHandler, type GetChatsHandler, type GetMessageFeedbackHandler, type MessageFeedback, type MessageFeedbackHandler, type OpenChatHandler, type RenameChatHandler, type UseAgentReturn, type UseAssistantReturn, type UseChatReturn, getToolApproval, getToolCallId, getToolPartState, useAgent, useAssistant, useChat };
|
package/dist/index.mjs
CHANGED
|
@@ -29,10 +29,56 @@ function getCloseReason(code) {
|
|
|
29
29
|
1015: "tls-handshake-failure"
|
|
30
30
|
}[code] ?? "unknown";
|
|
31
31
|
}
|
|
32
|
+
function isSameConnectionTarget(a, b) {
|
|
33
|
+
if (a.enabled !== b.enabled || a.host !== b.host || a.agentName !== b.agentName || a.actorId !== b.actorId || a.name !== b.name || a.authToken !== b.authToken || a.sub?.length !== b.sub?.length) return false;
|
|
34
|
+
return a.sub?.every((subAgent, index) => subAgent.agent === b.sub?.[index]?.agent && subAgent.name === b.sub?.[index]?.name) ?? true;
|
|
35
|
+
}
|
|
32
36
|
function useAgent(options) {
|
|
33
37
|
const { enabled, host, agentName, actorId, name, sub, authToken, onError, onOpen, onClose, onMessage } = options;
|
|
34
|
-
const
|
|
35
|
-
|
|
38
|
+
const target = {
|
|
39
|
+
enabled,
|
|
40
|
+
host,
|
|
41
|
+
agentName,
|
|
42
|
+
actorId,
|
|
43
|
+
name,
|
|
44
|
+
sub,
|
|
45
|
+
authToken
|
|
46
|
+
};
|
|
47
|
+
const connection = useRef({
|
|
48
|
+
id: 0,
|
|
49
|
+
target
|
|
50
|
+
});
|
|
51
|
+
if (!isSameConnectionTarget(connection.current.target, target)) connection.current = {
|
|
52
|
+
id: connection.current.id + 1,
|
|
53
|
+
target
|
|
54
|
+
};
|
|
55
|
+
const connectionId = connection.current.id;
|
|
56
|
+
const currentConnectionId = useRef(connectionId);
|
|
57
|
+
currentConnectionId.current = connectionId;
|
|
58
|
+
const [connectionType, setConnectionType] = useState();
|
|
59
|
+
const type = connectionType?.connectionId === connectionId ? connectionType.type : void 0;
|
|
60
|
+
const [connectionStatus, setConnectionStatus] = useState({
|
|
61
|
+
connectionId,
|
|
62
|
+
status: "connecting"
|
|
63
|
+
});
|
|
64
|
+
const status = connectionStatus.connectionId === connectionId ? connectionStatus.status : "connecting";
|
|
65
|
+
const connectedConnectionId = useRef(void 0);
|
|
66
|
+
const setStatus = (nextStatus) => {
|
|
67
|
+
if (currentConnectionId.current !== connectionId) return;
|
|
68
|
+
if (nextStatus === "connected") connectedConnectionId.current = connectionId;
|
|
69
|
+
else if (nextStatus === "disconnected" && connectedConnectionId.current !== connectionId) return;
|
|
70
|
+
setConnectionStatus({
|
|
71
|
+
connectionId,
|
|
72
|
+
status: nextStatus
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const setType = (nextType) => {
|
|
76
|
+
if (currentConnectionId.current === connectionId) setConnectionType({
|
|
77
|
+
connectionId,
|
|
78
|
+
type: nextType
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const currentAgent = useRef(null);
|
|
36
82
|
const agent = useAgent$1({
|
|
37
83
|
enabled,
|
|
38
84
|
host,
|
|
@@ -43,7 +89,7 @@ function useAgent(options) {
|
|
|
43
89
|
onOpen?.(event);
|
|
44
90
|
},
|
|
45
91
|
onClose: (event) => {
|
|
46
|
-
if (event.code >= 3e3) setStatus("unauthorized");
|
|
92
|
+
if (!event.target || event.target === currentAgent.current) if (event.code >= 3e3) setStatus("unauthorized");
|
|
47
93
|
else {
|
|
48
94
|
setStatus("disconnected");
|
|
49
95
|
if (!event.wasClean) console.warn("[useAgent] WebSocket closed unexpectedly", {
|
|
@@ -57,18 +103,19 @@ function useAgent(options) {
|
|
|
57
103
|
},
|
|
58
104
|
onMessage: (event) => {
|
|
59
105
|
const msg = JSON.parse(event.data);
|
|
60
|
-
if (msg.type === "connection_status") {
|
|
106
|
+
if (msg.type === "connection_status" && (!event.target || event.target === currentAgent.current)) {
|
|
61
107
|
if (msg.status === "unauthorized") agent.close(3e3);
|
|
62
108
|
setStatus(msg.status);
|
|
63
|
-
} else if (msg.type === "agent_type") setType(msg.agentType);
|
|
109
|
+
} else if (msg.type === "agent_type" && (!event.target || event.target === currentAgent.current)) setType(msg.agentType);
|
|
64
110
|
onMessage?.(event);
|
|
65
111
|
},
|
|
66
112
|
onError: (event) => {
|
|
67
|
-
setStatus("error");
|
|
113
|
+
if (!event.target || event.target === currentAgent.current) setStatus("error");
|
|
68
114
|
onError?.(event);
|
|
69
115
|
},
|
|
70
116
|
...getTokenArgs(authToken)
|
|
71
117
|
});
|
|
118
|
+
currentAgent.current = agent;
|
|
72
119
|
return {
|
|
73
120
|
agent,
|
|
74
121
|
status,
|
|
@@ -130,6 +177,16 @@ function useAssistant(options) {
|
|
|
130
177
|
const { toolContext, welcomeMessage, initialChatId, ...agentOptions } = options;
|
|
131
178
|
const [chatId, setChatId] = useState(initialChatId);
|
|
132
179
|
const [chats, setChats] = useState([]);
|
|
180
|
+
const [draftChat, setDraftChat] = useState(() => {
|
|
181
|
+
if (!initialChatId) return void 0;
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
return {
|
|
184
|
+
name: initialChatId,
|
|
185
|
+
isTemporary: true,
|
|
186
|
+
created_at: now,
|
|
187
|
+
updated_at: now
|
|
188
|
+
};
|
|
189
|
+
});
|
|
133
190
|
const [subAgentName, setSubAgentName] = useState(void 0);
|
|
134
191
|
const { agent: assistant, status: assistantStatus } = useAgent({
|
|
135
192
|
...agentOptions,
|
|
@@ -148,8 +205,29 @@ function useAssistant(options) {
|
|
|
148
205
|
return c;
|
|
149
206
|
});
|
|
150
207
|
setChats(fetched);
|
|
208
|
+
setDraftChat((current) => {
|
|
209
|
+
if (current && fetched.some((chat) => chat.name === current.name)) return;
|
|
210
|
+
return current;
|
|
211
|
+
});
|
|
151
212
|
return fetched;
|
|
152
213
|
}, [assistant]);
|
|
214
|
+
const refreshChatsRef = useRef(refreshChats);
|
|
215
|
+
refreshChatsRef.current = refreshChats;
|
|
216
|
+
const draftRefreshTimeouts = useRef(/* @__PURE__ */ new Map());
|
|
217
|
+
const scheduleDraftRefresh = useCallback((draftId) => {
|
|
218
|
+
const existingTimeout = draftRefreshTimeouts.current.get(draftId);
|
|
219
|
+
if (existingTimeout) clearTimeout(existingTimeout);
|
|
220
|
+
const timeout = setTimeout(() => {
|
|
221
|
+
draftRefreshTimeouts.current.delete(draftId);
|
|
222
|
+
refreshChatsRef.current().catch(console.error);
|
|
223
|
+
}, 3e3);
|
|
224
|
+
draftRefreshTimeouts.current.set(draftId, timeout);
|
|
225
|
+
}, []);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
return () => {
|
|
228
|
+
for (const timeout of draftRefreshTimeouts.current.values()) clearTimeout(timeout);
|
|
229
|
+
};
|
|
230
|
+
}, []);
|
|
153
231
|
useEffect(() => {
|
|
154
232
|
if (assistantStatus === "connected") refreshChats().catch(console.error);
|
|
155
233
|
}, [assistantStatus, refreshChats]);
|
|
@@ -157,17 +235,30 @@ function useAssistant(options) {
|
|
|
157
235
|
return refreshChats();
|
|
158
236
|
};
|
|
159
237
|
const createChat = async () => {
|
|
238
|
+
if (draftChat && draftChat.name === chatId) scheduleDraftRefresh(draftChat.name);
|
|
160
239
|
const newChatId = await assistant.call("createChat");
|
|
240
|
+
const now = Date.now();
|
|
161
241
|
setChatId(newChatId);
|
|
162
|
-
|
|
242
|
+
setDraftChat({
|
|
243
|
+
name: newChatId,
|
|
244
|
+
isTemporary: true,
|
|
245
|
+
created_at: now,
|
|
246
|
+
updated_at: now
|
|
247
|
+
});
|
|
163
248
|
return newChatId;
|
|
164
249
|
};
|
|
165
|
-
const openChat = (
|
|
166
|
-
|
|
250
|
+
const openChat = (nextChatId) => {
|
|
251
|
+
if (draftChat && draftChat.name === chatId) scheduleDraftRefresh(draftChat.name);
|
|
252
|
+
setChatId(nextChatId);
|
|
167
253
|
};
|
|
168
254
|
const deleteChat = async (chatId) => {
|
|
169
255
|
await assistant.call("deleteChat", [chatId]);
|
|
170
256
|
setChatId(void 0);
|
|
257
|
+
setDraftChat((current) => current?.name === chatId ? void 0 : current);
|
|
258
|
+
await refreshChats();
|
|
259
|
+
};
|
|
260
|
+
const renameChat = async (chatId, title) => {
|
|
261
|
+
await assistant.call("renameChat", [chatId, title]);
|
|
171
262
|
await refreshChats();
|
|
172
263
|
};
|
|
173
264
|
const isChatEnabled = !!chatId && !!subAgentName;
|
|
@@ -183,16 +274,31 @@ function useAssistant(options) {
|
|
|
183
274
|
toolContext,
|
|
184
275
|
welcomeMessage
|
|
185
276
|
});
|
|
277
|
+
const previousChatStatus = useRef(chat.chat.status);
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
const wasRunning = previousChatStatus.current === "submitted" || previousChatStatus.current === "streaming";
|
|
280
|
+
const hasFinished = chat.chat.status === "ready" || chat.chat.status === "error";
|
|
281
|
+
previousChatStatus.current = chat.chat.status;
|
|
282
|
+
if (!draftChat || draftChat.name !== chatId || !wasRunning || !hasFinished) return;
|
|
283
|
+
scheduleDraftRefresh(draftChat.name);
|
|
284
|
+
}, [
|
|
285
|
+
chat.chat.status,
|
|
286
|
+
chatId,
|
|
287
|
+
draftChat,
|
|
288
|
+
scheduleDraftRefresh
|
|
289
|
+
]);
|
|
290
|
+
const status = isChatEnabled && chat.status !== "connecting" ? chat.status : assistantStatus;
|
|
186
291
|
return {
|
|
187
292
|
currentChatName: chatId,
|
|
188
|
-
chats,
|
|
189
|
-
status
|
|
293
|
+
chats: draftChat && draftChat.name === chatId && !chats.some((persistedChat) => persistedChat.name === draftChat.name) ? [draftChat, ...chats] : chats,
|
|
294
|
+
status,
|
|
190
295
|
assistant: {
|
|
191
296
|
agent: assistant,
|
|
192
297
|
getChats,
|
|
193
298
|
createChat,
|
|
194
299
|
openChat,
|
|
195
|
-
deleteChat
|
|
300
|
+
deleteChat,
|
|
301
|
+
renameChat
|
|
196
302
|
},
|
|
197
303
|
chat
|
|
198
304
|
};
|