@economic/agents-react 1.6.0 → 1.6.3
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 +13 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +158 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,7 +111,12 @@ function Chat({ userId, authToken }: { userId: string; authToken: string }) {
|
|
|
111
111
|
))}
|
|
112
112
|
|
|
113
113
|
<Composer
|
|
114
|
-
onSend={(text) =>
|
|
114
|
+
onSend={(text) =>
|
|
115
|
+
chat.chat.sendMessage({
|
|
116
|
+
role: "user",
|
|
117
|
+
parts: [{ type: "text", text }],
|
|
118
|
+
})
|
|
119
|
+
}
|
|
115
120
|
/>
|
|
116
121
|
</div>
|
|
117
122
|
);
|
|
@@ -168,7 +173,13 @@ All hooks share these connection options (`useAssistant` omits `actorId`):
|
|
|
168
173
|
## Types
|
|
169
174
|
|
|
170
175
|
```ts
|
|
171
|
-
type AgentConnectionStatus =
|
|
176
|
+
type AgentConnectionStatus =
|
|
177
|
+
| "connecting"
|
|
178
|
+
| "reconnecting"
|
|
179
|
+
| "connected"
|
|
180
|
+
| "disconnected"
|
|
181
|
+
| "unauthorized"
|
|
182
|
+
| "error";
|
|
172
183
|
type AgentConnectionType = "agent" | "chat" | "assistant";
|
|
173
184
|
|
|
174
185
|
type AgentConnectionState = {
|
|
@@ -202,5 +213,3 @@ The package also exports the handler types `AssistantActions`, `GetChatsHandler`
|
|
|
202
213
|
## Authentication
|
|
203
214
|
|
|
204
215
|
Pass a JWT as `authToken`. It is sent both as an `Authorization: Bearer …` header and as a WebSocket subprotocol (`["bearer", token]`), matching the server-side [`getJwtAuthConfig`](../agents/README.md#authentication). When verification fails, the connection status becomes `"unauthorized"`.
|
|
205
|
-
|
|
206
|
-
.
|
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@ type Agent = Omit<_CfAgent, "call"> & {
|
|
|
7
7
|
call<T = unknown>(method: string, args?: unknown[], options?: unknown): Promise<T>;
|
|
8
8
|
};
|
|
9
9
|
type Chat = ReturnType<typeof useAgentChat>;
|
|
10
|
-
type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "error";
|
|
10
|
+
type AgentConnectionStatus = "connecting" | "reconnecting" | "connected" | "disconnected" | "unauthorized" | "error";
|
|
11
11
|
type AgentConnectionType = "agent" | "chat" | "assistant";
|
|
12
12
|
type ChatSummary = {
|
|
13
13
|
name: string; /** True when this entry only represents the active, unregistered chat facet. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { useAgent as useAgent$1 } from "agents/react";
|
|
3
3
|
import { getToolApproval, getToolCallId, getToolPartState, useAgentChat } from "@cloudflare/ai-chat/react";
|
|
4
4
|
//#region src/util.ts
|
|
@@ -11,6 +11,20 @@ function getTokenArgs(authToken) {
|
|
|
11
11
|
}
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/useAgent.ts
|
|
14
|
+
function getAgentConnectionStatus(state, connectionId) {
|
|
15
|
+
return state.connectionId === connectionId ? state.status : "connecting";
|
|
16
|
+
}
|
|
17
|
+
function getNextAgentConnectionState(state, connectionId, status) {
|
|
18
|
+
if ((status === "disconnected" || status === "reconnecting") && state.connectionId === connectionId && state.status === "error") return state;
|
|
19
|
+
return {
|
|
20
|
+
connectionId,
|
|
21
|
+
status
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getAgentCloseStatus(code, shouldReconnect) {
|
|
25
|
+
if (code >= 3e3) return "unauthorized";
|
|
26
|
+
return shouldReconnect ? "reconnecting" : "disconnected";
|
|
27
|
+
}
|
|
14
28
|
function getCloseReason(code) {
|
|
15
29
|
return {
|
|
16
30
|
1e3: "normal-closure",
|
|
@@ -29,93 +43,166 @@ function getCloseReason(code) {
|
|
|
29
43
|
1015: "tls-handshake-failure"
|
|
30
44
|
}[code] ?? "unknown";
|
|
31
45
|
}
|
|
46
|
+
function getDefaultHost() {
|
|
47
|
+
return globalThis.window?.location?.host || "dummy-domain.com";
|
|
48
|
+
}
|
|
49
|
+
function normalizeAgentName(name) {
|
|
50
|
+
if (name === name.toUpperCase() && name !== name.toLowerCase()) return name.toLowerCase().replace(/_/g, "-");
|
|
51
|
+
const kebabified = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
52
|
+
return (kebabified.startsWith("-") ? kebabified.slice(1) : kebabified).replace(/_/g, "-").replace(/-$/, "");
|
|
53
|
+
}
|
|
54
|
+
function getSubPath(sub) {
|
|
55
|
+
if (!sub?.length) return void 0;
|
|
56
|
+
return sub.flatMap(({ agent, name }) => [
|
|
57
|
+
"sub",
|
|
58
|
+
normalizeAgentName(agent),
|
|
59
|
+
encodeURIComponent(name)
|
|
60
|
+
]).join("/");
|
|
61
|
+
}
|
|
62
|
+
function getAgentConnectionTarget(options) {
|
|
63
|
+
const effectiveName = options.actorId ? `${options.actorId}:${options.name}` : options.name;
|
|
64
|
+
return {
|
|
65
|
+
enabled: options.enabled ?? true,
|
|
66
|
+
host: options.host || getDefaultHost(),
|
|
67
|
+
agentName: normalizeAgentName(options.agentName),
|
|
68
|
+
name: effectiveName || "default",
|
|
69
|
+
subPath: getSubPath(options.sub),
|
|
70
|
+
authToken: options.authToken || void 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function getAgentSocketTarget(agent) {
|
|
74
|
+
const options = agent.partySocketOptions;
|
|
75
|
+
if (typeof options?.host !== "string" || typeof options.party !== "string" || typeof options.room !== "string") return;
|
|
76
|
+
const protocols = options.protocols;
|
|
77
|
+
const authToken = Array.isArray(protocols) && protocols[0] === "bearer" && typeof protocols[1] === "string" ? protocols[1] : void 0;
|
|
78
|
+
return {
|
|
79
|
+
host: options.host,
|
|
80
|
+
agentName: options.party,
|
|
81
|
+
name: options.room,
|
|
82
|
+
subPath: typeof options.path === "string" && options.path ? options.path : void 0,
|
|
83
|
+
authToken
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function isSameSocketTarget(a, b) {
|
|
87
|
+
return a.host === b.host && a.agentName === b.agentName && a.name === b.name && a.subPath === b.subPath && a.authToken === b.authToken;
|
|
88
|
+
}
|
|
32
89
|
function isSameConnectionTarget(a, b) {
|
|
33
|
-
|
|
34
|
-
|
|
90
|
+
return a.enabled === b.enabled && isSameSocketTarget(a, b);
|
|
91
|
+
}
|
|
92
|
+
function getNextAgentConnection(connection, target) {
|
|
93
|
+
return isSameConnectionTarget(connection.target, target) ? connection : {
|
|
94
|
+
id: connection.id + 1,
|
|
95
|
+
target
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function isCurrentAgentSocketTarget(eventConnection, connection) {
|
|
99
|
+
if (!connection.target.enabled || !eventConnection) return false;
|
|
100
|
+
const socketTarget = getAgentSocketTarget(eventConnection);
|
|
101
|
+
return socketTarget !== void 0 && isSameSocketTarget(socketTarget, connection.target);
|
|
35
102
|
}
|
|
36
103
|
function useAgent(options) {
|
|
37
104
|
const { enabled, host, agentName, actorId, name, sub, authToken, onError, onOpen, onClose, onMessage } = options;
|
|
38
|
-
const target =
|
|
39
|
-
|
|
40
|
-
host,
|
|
41
|
-
agentName,
|
|
42
|
-
actorId,
|
|
43
|
-
name,
|
|
44
|
-
sub,
|
|
45
|
-
authToken
|
|
46
|
-
};
|
|
47
|
-
const connection = useRef({
|
|
105
|
+
const target = getAgentConnectionTarget(options);
|
|
106
|
+
const [connection, setConnection] = useState({
|
|
48
107
|
id: 0,
|
|
49
108
|
target
|
|
50
109
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
const connectionId = connection.current.id;
|
|
56
|
-
const currentConnectionId = useRef(connectionId);
|
|
57
|
-
currentConnectionId.current = connectionId;
|
|
110
|
+
const nextConnection = getNextAgentConnection(connection, target);
|
|
111
|
+
if (nextConnection !== connection) setConnection(nextConnection);
|
|
112
|
+
const connectionId = nextConnection.id;
|
|
58
113
|
const [connectionType, setConnectionType] = useState();
|
|
59
114
|
const type = connectionType?.connectionId === connectionId ? connectionType.type : void 0;
|
|
60
115
|
const [connectionStatus, setConnectionStatus] = useState({
|
|
61
116
|
connectionId,
|
|
62
117
|
status: "connecting"
|
|
63
118
|
});
|
|
64
|
-
const status = connectionStatus
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
const status = getAgentConnectionStatus(connectionStatus, connectionId);
|
|
120
|
+
const committedConnection = useRef({
|
|
121
|
+
connection: nextConnection,
|
|
122
|
+
callbacks: {
|
|
123
|
+
onOpen,
|
|
124
|
+
onClose,
|
|
125
|
+
onError,
|
|
126
|
+
onMessage
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const setStatus = useCallback((nextStatus) => {
|
|
130
|
+
const committedConnectionId = committedConnection.current.connection.id;
|
|
131
|
+
setConnectionStatus((current) => getNextAgentConnectionState(current, committedConnectionId, nextStatus));
|
|
132
|
+
}, []);
|
|
133
|
+
const setType = useCallback((nextType) => {
|
|
134
|
+
setConnectionType({
|
|
135
|
+
connectionId: committedConnection.current.connection.id,
|
|
78
136
|
type: nextType
|
|
79
137
|
});
|
|
80
|
-
};
|
|
81
|
-
const
|
|
138
|
+
}, []);
|
|
139
|
+
const isCurrentConnectionEvent = useCallback((event) => {
|
|
140
|
+
const committed = committedConnection.current;
|
|
141
|
+
return isCurrentAgentSocketTarget(event.target, committed.connection);
|
|
142
|
+
}, []);
|
|
143
|
+
const handleOpen = useCallback((event) => {
|
|
144
|
+
if (!isCurrentConnectionEvent(event)) return;
|
|
145
|
+
committedConnection.current.callbacks.onOpen?.(event);
|
|
146
|
+
}, [isCurrentConnectionEvent]);
|
|
147
|
+
const handleClose = useCallback((event) => {
|
|
148
|
+
if (!isCurrentConnectionEvent(event)) return;
|
|
149
|
+
const shouldReconnect = event.target.shouldReconnect;
|
|
150
|
+
setStatus(getAgentCloseStatus(event.code, shouldReconnect));
|
|
151
|
+
if (event.code < 3e3 && !event.wasClean) console.warn("[useAgent] WebSocket closed unexpectedly", {
|
|
152
|
+
code: event.code,
|
|
153
|
+
reason: event.reason,
|
|
154
|
+
closeReason: getCloseReason(event.code),
|
|
155
|
+
wasClean: event.wasClean
|
|
156
|
+
});
|
|
157
|
+
committedConnection.current.callbacks.onClose?.(event);
|
|
158
|
+
}, [isCurrentConnectionEvent, setStatus]);
|
|
159
|
+
const handleMessage = useCallback((event) => {
|
|
160
|
+
if (!isCurrentConnectionEvent(event)) return;
|
|
161
|
+
const msg = JSON.parse(event.data);
|
|
162
|
+
if (msg.type === "connection_status") {
|
|
163
|
+
if (msg.status === "unauthorized") event.target.close(3e3);
|
|
164
|
+
setStatus(msg.status);
|
|
165
|
+
} else if (msg.type === "agent_type") setType(msg.agentType);
|
|
166
|
+
committedConnection.current.callbacks.onMessage?.(event);
|
|
167
|
+
}, [
|
|
168
|
+
isCurrentConnectionEvent,
|
|
169
|
+
setStatus,
|
|
170
|
+
setType
|
|
171
|
+
]);
|
|
172
|
+
const handleError = useCallback((event) => {
|
|
173
|
+
if (!isCurrentConnectionEvent(event)) return;
|
|
174
|
+
setStatus(event.target.shouldReconnect ? "reconnecting" : "error");
|
|
175
|
+
committedConnection.current.callbacks.onError?.(event);
|
|
176
|
+
}, [isCurrentConnectionEvent, setStatus]);
|
|
82
177
|
const agent = useAgent$1({
|
|
83
178
|
enabled,
|
|
84
179
|
host,
|
|
85
180
|
agent: agentName,
|
|
86
181
|
name: actorId ? `${actorId}:${name}` : name,
|
|
87
182
|
sub,
|
|
88
|
-
onOpen:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (!event.target || event.target === currentAgent.current) if (event.code >= 3e3) setStatus("unauthorized");
|
|
93
|
-
else {
|
|
94
|
-
setStatus("disconnected");
|
|
95
|
-
if (!event.wasClean) console.warn("[useAgent] WebSocket closed unexpectedly", {
|
|
96
|
-
code: event.code,
|
|
97
|
-
reason: event.reason,
|
|
98
|
-
closeReason: getCloseReason(event.code),
|
|
99
|
-
wasClean: event.wasClean
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
onClose?.(event);
|
|
103
|
-
},
|
|
104
|
-
onMessage: (event) => {
|
|
105
|
-
const msg = JSON.parse(event.data);
|
|
106
|
-
if (msg.type === "connection_status" && (!event.target || event.target === currentAgent.current)) {
|
|
107
|
-
if (msg.status === "unauthorized") agent.close(3e3);
|
|
108
|
-
setStatus(msg.status);
|
|
109
|
-
} else if (msg.type === "agent_type" && (!event.target || event.target === currentAgent.current)) setType(msg.agentType);
|
|
110
|
-
onMessage?.(event);
|
|
111
|
-
},
|
|
112
|
-
onError: (event) => {
|
|
113
|
-
if (!event.target || event.target === currentAgent.current) setStatus("error");
|
|
114
|
-
onError?.(event);
|
|
115
|
-
},
|
|
183
|
+
onOpen: handleOpen,
|
|
184
|
+
onClose: handleClose,
|
|
185
|
+
onMessage: handleMessage,
|
|
186
|
+
onError: handleError,
|
|
116
187
|
...getTokenArgs(authToken)
|
|
117
188
|
});
|
|
118
|
-
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
committedConnection.current = {
|
|
191
|
+
connection: nextConnection,
|
|
192
|
+
callbacks: {
|
|
193
|
+
onOpen,
|
|
194
|
+
onClose,
|
|
195
|
+
onError,
|
|
196
|
+
onMessage
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}, [
|
|
200
|
+
nextConnection,
|
|
201
|
+
onOpen,
|
|
202
|
+
onClose,
|
|
203
|
+
onError,
|
|
204
|
+
onMessage
|
|
205
|
+
]);
|
|
119
206
|
return {
|
|
120
207
|
agent,
|
|
121
208
|
status,
|
|
@@ -173,6 +260,11 @@ function useChat(options) {
|
|
|
173
260
|
}
|
|
174
261
|
//#endregion
|
|
175
262
|
//#region src/useAssistant.ts
|
|
263
|
+
function getAssistantConnectionStatus(currentChatName, isChatEnabled, assistantStatus, chatStatus) {
|
|
264
|
+
if (currentChatName === void 0) return assistantStatus;
|
|
265
|
+
if (!isChatEnabled) return assistantStatus === "connected" ? "connecting" : assistantStatus;
|
|
266
|
+
return chatStatus;
|
|
267
|
+
}
|
|
176
268
|
function useAssistant(options) {
|
|
177
269
|
const { toolContext, welcomeMessage, initialChatId, ...agentOptions } = options;
|
|
178
270
|
const [chatId, setChatId] = useState(initialChatId);
|
|
@@ -262,7 +354,6 @@ function useAssistant(options) {
|
|
|
262
354
|
await refreshChats();
|
|
263
355
|
};
|
|
264
356
|
const isChatEnabled = !!chatId && !!subAgentName;
|
|
265
|
-
const isChatReady = isChatEnabled && assistantStatus === "connected";
|
|
266
357
|
const chat = useChat({
|
|
267
358
|
...agentOptions,
|
|
268
359
|
enabled: isChatEnabled,
|
|
@@ -270,7 +361,6 @@ function useAssistant(options) {
|
|
|
270
361
|
agent: subAgentName,
|
|
271
362
|
name: chatId
|
|
272
363
|
}] : void 0,
|
|
273
|
-
getInitialMessages: isChatReady ? void 0 : null,
|
|
274
364
|
toolContext,
|
|
275
365
|
welcomeMessage
|
|
276
366
|
});
|
|
@@ -287,7 +377,7 @@ function useAssistant(options) {
|
|
|
287
377
|
draftChat,
|
|
288
378
|
scheduleDraftRefresh
|
|
289
379
|
]);
|
|
290
|
-
const status = isChatEnabled
|
|
380
|
+
const status = getAssistantConnectionStatus(chatId, isChatEnabled, assistantStatus, chat.status);
|
|
291
381
|
return {
|
|
292
382
|
currentChatName: chatId,
|
|
293
383
|
chats: draftChat && draftChat.name === chatId && !chats.some((persistedChat) => persistedChat.name === draftChat.name) ? [draftChat, ...chats] : chats,
|