@economic/agents-react 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +156 -68
  2. package/package.json +1 -1
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,16 @@ 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" && state.connectionId === connectionId && state.status === "error") return state;
19
+ return {
20
+ connectionId,
21
+ status
22
+ };
23
+ }
14
24
  function getCloseReason(code) {
15
25
  return {
16
26
  1e3: "normal-closure",
@@ -29,93 +39,168 @@ function getCloseReason(code) {
29
39
  1015: "tls-handshake-failure"
30
40
  }[code] ?? "unknown";
31
41
  }
42
+ function getDefaultHost() {
43
+ return globalThis.window?.location?.host || "dummy-domain.com";
44
+ }
45
+ function normalizeAgentName(name) {
46
+ if (name === name.toUpperCase() && name !== name.toLowerCase()) return name.toLowerCase().replace(/_/g, "-");
47
+ const kebabified = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
48
+ return (kebabified.startsWith("-") ? kebabified.slice(1) : kebabified).replace(/_/g, "-").replace(/-$/, "");
49
+ }
50
+ function getSubPath(sub) {
51
+ if (!sub?.length) return void 0;
52
+ return sub.flatMap(({ agent, name }) => [
53
+ "sub",
54
+ normalizeAgentName(agent),
55
+ encodeURIComponent(name)
56
+ ]).join("/");
57
+ }
58
+ function getAgentConnectionTarget(options) {
59
+ const effectiveName = options.actorId ? `${options.actorId}:${options.name}` : options.name;
60
+ return {
61
+ enabled: options.enabled ?? true,
62
+ host: options.host || getDefaultHost(),
63
+ agentName: normalizeAgentName(options.agentName),
64
+ name: effectiveName || "default",
65
+ subPath: getSubPath(options.sub),
66
+ authToken: options.authToken || void 0
67
+ };
68
+ }
69
+ function getAgentSocketTarget(agent) {
70
+ const options = agent.partySocketOptions;
71
+ if (typeof options?.host !== "string" || typeof options.party !== "string" || typeof options.room !== "string") return;
72
+ const protocols = options.protocols;
73
+ const authToken = Array.isArray(protocols) && protocols[0] === "bearer" && typeof protocols[1] === "string" ? protocols[1] : void 0;
74
+ return {
75
+ host: options.host,
76
+ agentName: options.party,
77
+ name: options.room,
78
+ subPath: typeof options.path === "string" && options.path ? options.path : void 0,
79
+ authToken
80
+ };
81
+ }
82
+ function isSameSocketTarget(a, b) {
83
+ return a.host === b.host && a.agentName === b.agentName && a.name === b.name && a.subPath === b.subPath && a.authToken === b.authToken;
84
+ }
32
85
  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;
86
+ return a.enabled === b.enabled && isSameSocketTarget(a, b);
87
+ }
88
+ function getNextAgentConnection(connection, target) {
89
+ return isSameConnectionTarget(connection.target, target) ? connection : {
90
+ id: connection.id + 1,
91
+ target
92
+ };
93
+ }
94
+ function isCurrentAgentSocketTarget(eventConnection, connection) {
95
+ if (!connection.target.enabled || !eventConnection) return false;
96
+ const socketTarget = getAgentSocketTarget(eventConnection);
97
+ return socketTarget !== void 0 && isSameSocketTarget(socketTarget, connection.target);
35
98
  }
36
99
  function useAgent(options) {
37
100
  const { enabled, host, agentName, actorId, name, sub, authToken, onError, onOpen, onClose, onMessage } = options;
38
- const target = {
39
- enabled,
40
- host,
41
- agentName,
42
- actorId,
43
- name,
44
- sub,
45
- authToken
46
- };
47
- const connection = useRef({
101
+ const target = getAgentConnectionTarget(options);
102
+ const [connection, setConnection] = useState({
48
103
  id: 0,
49
104
  target
50
105
  });
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;
106
+ const nextConnection = getNextAgentConnection(connection, target);
107
+ if (nextConnection !== connection) setConnection(nextConnection);
108
+ const connectionId = nextConnection.id;
58
109
  const [connectionType, setConnectionType] = useState();
59
110
  const type = connectionType?.connectionId === connectionId ? connectionType.type : void 0;
60
111
  const [connectionStatus, setConnectionStatus] = useState({
61
112
  connectionId,
62
113
  status: "connecting"
63
114
  });
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,
115
+ const status = getAgentConnectionStatus(connectionStatus, connectionId);
116
+ const committedConnection = useRef({
117
+ connection: nextConnection,
118
+ callbacks: {
119
+ onOpen,
120
+ onClose,
121
+ onError,
122
+ onMessage
123
+ }
124
+ });
125
+ const setStatus = useCallback((nextStatus) => {
126
+ const committedConnectionId = committedConnection.current.connection.id;
127
+ setConnectionStatus((current) => getNextAgentConnectionState(current, committedConnectionId, nextStatus));
128
+ }, []);
129
+ const setType = useCallback((nextType) => {
130
+ setConnectionType({
131
+ connectionId: committedConnection.current.connection.id,
78
132
  type: nextType
79
133
  });
80
- };
81
- const currentAgent = useRef(null);
134
+ }, []);
135
+ const isCurrentConnectionEvent = useCallback((event) => {
136
+ const committed = committedConnection.current;
137
+ return isCurrentAgentSocketTarget(event.target, committed.connection);
138
+ }, []);
139
+ const handleOpen = useCallback((event) => {
140
+ if (!isCurrentConnectionEvent(event)) return;
141
+ committedConnection.current.callbacks.onOpen?.(event);
142
+ }, [isCurrentConnectionEvent]);
143
+ const handleClose = useCallback((event) => {
144
+ if (!isCurrentConnectionEvent(event)) return;
145
+ if (event.code >= 3e3) setStatus("unauthorized");
146
+ else {
147
+ setStatus("disconnected");
148
+ if (!event.wasClean) console.warn("[useAgent] WebSocket closed unexpectedly", {
149
+ code: event.code,
150
+ reason: event.reason,
151
+ closeReason: getCloseReason(event.code),
152
+ wasClean: event.wasClean
153
+ });
154
+ }
155
+ committedConnection.current.callbacks.onClose?.(event);
156
+ }, [isCurrentConnectionEvent, setStatus]);
157
+ const handleMessage = useCallback((event) => {
158
+ if (!isCurrentConnectionEvent(event)) return;
159
+ const msg = JSON.parse(event.data);
160
+ if (msg.type === "connection_status") {
161
+ if (msg.status === "unauthorized") event.target.close(3e3);
162
+ setStatus(msg.status);
163
+ } else if (msg.type === "agent_type") setType(msg.agentType);
164
+ committedConnection.current.callbacks.onMessage?.(event);
165
+ }, [
166
+ isCurrentConnectionEvent,
167
+ setStatus,
168
+ setType
169
+ ]);
170
+ const handleError = useCallback((event) => {
171
+ if (!isCurrentConnectionEvent(event)) return;
172
+ setStatus("error");
173
+ committedConnection.current.callbacks.onError?.(event);
174
+ }, [isCurrentConnectionEvent, setStatus]);
82
175
  const agent = useAgent$1({
83
176
  enabled,
84
177
  host,
85
178
  agent: agentName,
86
179
  name: actorId ? `${actorId}:${name}` : name,
87
180
  sub,
88
- onOpen: (event) => {
89
- onOpen?.(event);
90
- },
91
- onClose: (event) => {
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
- },
181
+ onOpen: handleOpen,
182
+ onClose: handleClose,
183
+ onMessage: handleMessage,
184
+ onError: handleError,
116
185
  ...getTokenArgs(authToken)
117
186
  });
118
- currentAgent.current = agent;
187
+ useLayoutEffect(() => {
188
+ committedConnection.current = {
189
+ connection: nextConnection,
190
+ callbacks: {
191
+ onOpen,
192
+ onClose,
193
+ onError,
194
+ onMessage
195
+ }
196
+ };
197
+ }, [
198
+ nextConnection,
199
+ onOpen,
200
+ onClose,
201
+ onError,
202
+ onMessage
203
+ ]);
119
204
  return {
120
205
  agent,
121
206
  status,
@@ -173,6 +258,11 @@ function useChat(options) {
173
258
  }
174
259
  //#endregion
175
260
  //#region src/useAssistant.ts
261
+ function getAssistantConnectionStatus(currentChatName, isChatEnabled, assistantStatus, chatStatus) {
262
+ if (currentChatName === void 0) return assistantStatus;
263
+ if (!isChatEnabled) return assistantStatus === "connected" ? "connecting" : assistantStatus;
264
+ return chatStatus;
265
+ }
176
266
  function useAssistant(options) {
177
267
  const { toolContext, welcomeMessage, initialChatId, ...agentOptions } = options;
178
268
  const [chatId, setChatId] = useState(initialChatId);
@@ -262,7 +352,6 @@ function useAssistant(options) {
262
352
  await refreshChats();
263
353
  };
264
354
  const isChatEnabled = !!chatId && !!subAgentName;
265
- const isChatReady = isChatEnabled && assistantStatus === "connected";
266
355
  const chat = useChat({
267
356
  ...agentOptions,
268
357
  enabled: isChatEnabled,
@@ -270,7 +359,6 @@ function useAssistant(options) {
270
359
  agent: subAgentName,
271
360
  name: chatId
272
361
  }] : void 0,
273
- getInitialMessages: isChatReady ? void 0 : null,
274
362
  toolContext,
275
363
  welcomeMessage
276
364
  });
@@ -287,7 +375,7 @@ function useAssistant(options) {
287
375
  draftChat,
288
376
  scheduleDraftRefresh
289
377
  ]);
290
- const status = isChatEnabled && chat.status !== "connecting" ? chat.status : assistantStatus;
378
+ const status = getAssistantConnectionStatus(chatId, isChatEnabled, assistantStatus, chat.status);
291
379
  return {
292
380
  currentChatName: chatId,
293
381
  chats: draftChat && draftChat.name === chatId && !chats.some((persistedChat) => persistedChat.name === draftChat.name) ? [draftChat, ...chats] : chats,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents-react",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"