connectonion 0.0.18 → 0.0.21

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.
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isEventType = void 0;
4
+ exports.useAgentForHuman = useAgentForHuman;
5
+ exports.isChatItemType = isChatItemType;
6
+ const react_1 = require("react");
7
+ const connect_1 = require("../connect");
8
+ const store_1 = require("./store");
9
+ /**
10
+ * React hook for a human user to interact with a remote AI agent.
11
+ *
12
+ * This is the primary hook for building chat UIs where a human drives the
13
+ * conversation. It handles approval gates, ULW pauses, onboarding flows,
14
+ * and session persistence — all concerns specific to human interaction.
15
+ * For agent-to-agent communication, use `connect()` directly instead.
16
+ *
17
+ * Wraps a `RemoteAgent` instance with Zustand-backed localStorage persistence
18
+ * so chat history and session state survive page refreshes. One store is created
19
+ * per `(address, sessionId)` pair and cached for the lifetime of the module.
20
+ *
21
+ * **Lifecycle**
22
+ * 1. On mount (or when `sessionId` changes), any persisted session is restored
23
+ * into the `RemoteAgent` so the server can resume from the correct context.
24
+ * 2. `agent.onMessage` is registered in an effect to receive every streaming
25
+ * event from the agent — UI items, status, connection state, and session
26
+ * snapshots are all synced here without a polling interval.
27
+ * 3. `input()` is fire-and-forget: it merges the session and dispatches the
28
+ * prompt; all reactive updates come back through `onMessage`.
29
+ *
30
+ * **Session ID ownership**
31
+ * The caller is responsible for generating and managing the session UUID.
32
+ * A stable ID (e.g. persisted in a URL parameter or parent component state)
33
+ * lets users resume interrupted sessions across browser refreshes.
34
+ *
35
+ * @param address - Agent's 0x-prefixed public address on the relay network
36
+ * @param sessionId - UUID identifying this conversation session
37
+ * @returns Reactive state and methods for driving a chat UI
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * const { status, ui, input, isProcessing } = useAgentForHuman(agentAddress, sessionId);
42
+ *
43
+ * return (
44
+ * <button disabled={isProcessing} onClick={() => input('Hello')}>
45
+ * Send
46
+ * </button>
47
+ * );
48
+ * ```
49
+ */
50
+ function useAgentForHuman(address, sessionId) {
51
+ const useStore = (0, store_1.getStore)(address, sessionId);
52
+ // State from store
53
+ const messages = useStore((s) => s.messages);
54
+ const ui = useStore((s) => s.ui);
55
+ const session = useStore((s) => s.session);
56
+ const status = useStore((s) => s.status);
57
+ const error = useStore((s) => s.error);
58
+ // Actions from store
59
+ const setStatus = useStore((s) => s.setStatus);
60
+ const setUI = useStore((s) => s.setUI);
61
+ const setSession = useStore((s) => s.setSession);
62
+ const setError = useStore((s) => s.setError);
63
+ const updateMessages = useStore((s) => s.updateMessages);
64
+ const resetStore = useStore((s) => s.reset);
65
+ // RemoteAgent instance (keyed by address + sessionId)
66
+ const agentRef = (0, react_1.useRef)(null);
67
+ const keyRef = (0, react_1.useRef)(`${address}:${sessionId}`);
68
+ // Tear down the cached agent when the caller switches to a different address or session
69
+ // so the next render creates a fresh RemoteAgent pointing at the correct endpoint.
70
+ if (keyRef.current !== `${address}:${sessionId}`) {
71
+ agentRef.current = null;
72
+ keyRef.current = `${address}:${sessionId}`;
73
+ }
74
+ if (!agentRef.current) {
75
+ agentRef.current = (0, connect_1.connect)(address);
76
+ }
77
+ const agent = agentRef.current;
78
+ // connectionState is initialized from the agent and then kept in sync via onMessage.
79
+ const [connectionState, setConnectionState] = (0, react_1.useState)(agent.connectionState);
80
+ // Register a single onMessage callback for the lifetime of this agent instance.
81
+ // This replaces a polling interval: every streaming event from the server triggers
82
+ // one synchronous flush of all derived state into React/Zustand.
83
+ (0, react_1.useEffect)(() => {
84
+ agent.onMessage = () => {
85
+ setUI([...agent.ui]);
86
+ setStatus(agent.status);
87
+ setConnectionState(agent.connectionState);
88
+ if (agent.error)
89
+ setError(agent.error);
90
+ if (agent.currentSession) {
91
+ setSession(agent.currentSession);
92
+ if (agent.currentSession.messages) {
93
+ updateMessages(agent.currentSession.messages);
94
+ }
95
+ }
96
+ };
97
+ return () => { agent.onMessage = null; };
98
+ // eslint-disable-next-line react-hooks/exhaustive-deps
99
+ }, [agent]);
100
+ // Restore persisted session into the RemoteAgent on mount or when sessionId changes.
101
+ // Then auto-reconnect to sync with server (get newer data, resume executing agent, etc.)
102
+ (0, react_1.useEffect)(() => {
103
+ if (session) {
104
+ agent._currentSession = { ...session, session_id: sessionId };
105
+ agent._chatItems = [...ui];
106
+ }
107
+ else if (messages.length > 0) {
108
+ agent._currentSession = { session_id: sessionId, messages };
109
+ agent._chatItems = [...ui];
110
+ }
111
+ // No auto-reconnect on mount. Show cached conversation from localStorage.
112
+ // When user sends next message, input() → _ensureConnected() → CONNECT
113
+ // will sync with server (session merge, server_newer, etc.).
114
+ }, [sessionId]);
115
+ const input = (prompt, options) => {
116
+ setError(null);
117
+ // Merge session before dispatching: the agent may have received a mode change via
118
+ // setMode() (stored only on _currentSession) that isn't reflected in the Zustand
119
+ // store yet. We preserve those in-flight agent properties while ensuring the server
120
+ // receives the canonical message history and the correct session ID.
121
+ const agentSession = agent._currentSession || {};
122
+ agent._currentSession = {
123
+ ...agentSession, // Preserve mode set by setMode()
124
+ ...(session || {}), // Overlay with store session
125
+ session_id: sessionId, // Ensure correct session ID
126
+ messages: session?.messages || messages,
127
+ };
128
+ // Restore chat items if agent is empty but store has data
129
+ // (Zustand hydration is async — the mount-time restore effect may have
130
+ // run before localStorage was hydrated, leaving _chatItems empty)
131
+ if (agent._chatItems.length === 0 && ui.length > 0) {
132
+ agent._chatItems = [...ui];
133
+ }
134
+ agent.input(prompt, options); // non-blocking — updates come via onMessage
135
+ };
136
+ const reconnect = () => {
137
+ // Ensure session is set on agent before reconnecting
138
+ if (!agent._currentSession?.session_id) {
139
+ agent._currentSession = { ...(session || {}), session_id: sessionId };
140
+ }
141
+ if (agent._chatItems.length === 0 && ui.length > 0) {
142
+ agent._chatItems = [...ui];
143
+ }
144
+ agent.reconnect(sessionId); // non-blocking — updates come via onMessage
145
+ };
146
+ const reset = () => {
147
+ agent.reset();
148
+ resetStore();
149
+ };
150
+ const sendMessage = (message) => {
151
+ agent.send(message);
152
+ };
153
+ const setMode = (newMode, options) => {
154
+ agent.setMode(newMode, options);
155
+ // Mirror the mode change into the Zustand store immediately so the UI reflects it
156
+ // before the next server-synced session arrives. ULW counters are also seeded here
157
+ // so consumers can render a turn budget without waiting for the first response.
158
+ const updates = { mode: newMode };
159
+ if (newMode === 'ulw') {
160
+ updates.ulw_turns = options?.turns || 100;
161
+ updates.ulw_turns_used = 0;
162
+ }
163
+ setSession(session
164
+ ? { ...session, ...updates }
165
+ : { session_id: sessionId, ...updates });
166
+ };
167
+ return {
168
+ status,
169
+ connectionState,
170
+ ui,
171
+ sessionId,
172
+ isProcessing: status !== 'idle',
173
+ error,
174
+ checkSessionStatus: (sid) => agent.checkSessionStatus(sid),
175
+ mode: session?.mode || 'safe',
176
+ ulwTurns: session?.ulw_turns ?? null,
177
+ ulwTurnsUsed: session?.ulw_turns_used ?? null,
178
+ input,
179
+ sendMessage,
180
+ signOnboard: (options) => agent.signOnboard(options),
181
+ setMode,
182
+ reconnect,
183
+ reset,
184
+ };
185
+ }
186
+ /**
187
+ * Type guard that narrows a `ChatItem` to the specific variant identified by `type`.
188
+ *
189
+ * Prefer this over a raw `item.type === 'tool_call'` comparison in render code
190
+ * because TypeScript will fully narrow the variant's unique fields inside the branch.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * if (isChatItemType(item, 'tool_call')) {
195
+ * console.log(item.name, item.timing_ms); // fully typed
196
+ * }
197
+ * ```
198
+ */
199
+ function isChatItemType(item, type) {
200
+ return item.type === type;
201
+ }
202
+ /** @deprecated Use isChatItemType instead */
203
+ exports.isEventType = isChatItemType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectonion",
3
- "version": "0.0.18",
3
+ "version": "0.0.21",
4
4
  "description": "Connect to Python AI agents from TypeScript apps - Use powerful Python agents in your React, Next.js, Node.js, and Electron applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",