@applica-software-guru/persona-sdk 0.1.84 → 0.1.86

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 (67) hide show
  1. package/README.md +92 -0
  2. package/dist/bundle.cjs.js +18 -18
  3. package/dist/bundle.cjs.js.map +1 -1
  4. package/dist/bundle.es.js +3345 -2891
  5. package/dist/bundle.es.js.map +1 -1
  6. package/dist/bundle.iife.js +17 -17
  7. package/dist/bundle.iife.js.map +1 -1
  8. package/dist/bundle.umd.js +17 -17
  9. package/dist/bundle.umd.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/messages.d.ts +2 -0
  13. package/dist/messages.d.ts.map +1 -1
  14. package/dist/protocol/webrtc.d.ts.map +1 -1
  15. package/dist/protocol/websocket.d.ts.map +1 -1
  16. package/dist/runtime/context.d.ts +34 -0
  17. package/dist/runtime/context.d.ts.map +1 -0
  18. package/dist/runtime/handlers.d.ts +21 -0
  19. package/dist/runtime/handlers.d.ts.map +1 -0
  20. package/dist/runtime/listeners.d.ts +6 -0
  21. package/dist/runtime/listeners.d.ts.map +1 -0
  22. package/dist/runtime/protocols.d.ts +17 -0
  23. package/dist/runtime/protocols.d.ts.map +1 -0
  24. package/dist/runtime/threads.d.ts +35 -0
  25. package/dist/runtime/threads.d.ts.map +1 -0
  26. package/dist/runtime/utils.d.ts +10 -0
  27. package/dist/runtime/utils.d.ts.map +1 -0
  28. package/dist/runtime.d.ts +4 -22
  29. package/dist/runtime.d.ts.map +1 -1
  30. package/dist/storage/base.d.ts +19 -0
  31. package/dist/storage/base.d.ts.map +1 -0
  32. package/dist/storage/index.d.ts +3 -0
  33. package/dist/storage/index.d.ts.map +1 -0
  34. package/dist/storage/persona.d.ts +30 -0
  35. package/dist/storage/persona.d.ts.map +1 -0
  36. package/dist/types.d.ts +51 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +4 -3
  39. package/playground/src/chat.tsx +58 -65
  40. package/playground/src/components/assistant-ui/thread-list.tsx +45 -12
  41. package/playground/src/components/assistant-ui/thread.tsx +34 -96
  42. package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
  43. package/playground/src/components/chat/logging.tsx +53 -0
  44. package/playground/src/components/ui/input.tsx +21 -0
  45. package/playground/src/components/ui/separator.tsx +26 -0
  46. package/playground/src/components/ui/sheet.tsx +139 -0
  47. package/playground/src/components/ui/sidebar.tsx +619 -0
  48. package/playground/src/components/ui/skeleton.tsx +13 -0
  49. package/playground/src/components/ui/tooltip.tsx +0 -2
  50. package/playground/src/hooks/theme.ts +70 -0
  51. package/playground/src/hooks/use-mobile.ts +19 -0
  52. package/src/index.ts +1 -0
  53. package/src/messages.ts +98 -8
  54. package/src/protocol/webrtc.ts +1 -0
  55. package/src/protocol/websocket.ts +5 -2
  56. package/src/runtime/context.ts +88 -0
  57. package/src/runtime/handlers.ts +276 -0
  58. package/src/runtime/index.ts +6 -0
  59. package/src/runtime/listeners.ts +79 -0
  60. package/src/runtime/protocols.ts +169 -0
  61. package/src/runtime/threads.ts +120 -0
  62. package/src/runtime/utils.ts +46 -0
  63. package/src/runtime.tsx +226 -326
  64. package/src/storage/base.ts +21 -0
  65. package/src/storage/index.ts +2 -0
  66. package/src/storage/persona.ts +132 -0
  67. package/src/types.ts +64 -2
package/src/runtime.tsx CHANGED
@@ -1,80 +1,28 @@
1
- import { useState, useEffect, useCallback, PropsWithChildren, createContext, useContext, useMemo, useRef } from 'react';
1
+ import { useState, useEffect, useCallback, PropsWithChildren, useMemo, useRef } from 'react';
2
2
  import {
3
3
  useExternalStoreRuntime,
4
- AppendMessage,
5
4
  AssistantRuntimeProvider,
6
5
  CompositeAttachmentAdapter,
7
6
  SimpleImageAttachmentAdapter,
8
7
  } from '@assistant-ui/react';
9
- import {
10
- PersonaConfig,
11
- PersonaMessage,
12
- PersonaPacket,
13
- PersonaProtocol,
14
- PersonaProtocolBaseConfig,
15
- PersonaReasoning,
16
- PersonaResponse,
17
- PersonaTransaction,
18
- ProtocolStatus,
19
- Session,
20
- } from './types';
8
+ import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData } from './types';
21
9
  import { parseMessages, convertMessage } from './messages';
22
- import {
23
- PersonaRESTProtocol,
24
- PersonaRESTProtocolConfig,
25
- PersonaTransactionProtocol,
26
- PersonaWebRTCProtocol,
27
- PersonaWebRTCProtocolConfig,
28
- PersonaWebSocketProtocol,
29
- PersonaWebSocketProtocolConfig,
30
- } from './protocol';
31
-
32
- type PersonaRuntimeContextType = {
33
- protocols: PersonaProtocol[];
34
- protocolsStatus: Map<string, ProtocolStatus>;
35
- getMessages: () => PersonaMessage[];
36
- };
37
-
38
- const PersonaRuntimeContext = createContext<PersonaRuntimeContextType | undefined>(undefined);
39
-
40
- function fileToBase64(file: File): Promise<string> {
41
- return new Promise((resolve, reject) => {
42
- const reader = new FileReader();
43
- reader.readAsDataURL(file); // Converte il file in Data URL (base64)
44
-
45
- reader.onload = () => {
46
- //remove data url using ;base64, to split
47
- const base64 = reader.result as string;
48
- const base64WithoutPrefix = base64.split(';base64,')[1];
49
- resolve(base64WithoutPrefix);
50
- };
10
+ import { initializeProtocols } from './runtime/protocols';
11
+ import { createPacketListener } from './runtime/listeners';
12
+ import { createOnNewHandler, createOnEditHandler, createOnReloadHandler, createOnCancelHandler } from './runtime/handlers';
13
+ import { PersonaRuntimeContext } from './runtime/context';
14
+ import { createThreadListAdapter } from './runtime/threads';
15
+ import { PersonaSessionStorage } from './storage';
51
16
 
52
- reader.onerror = (error) => {
53
- reject(error);
54
- };
55
- });
56
- }
17
+ export {
18
+ usePersonaRuntime,
19
+ usePersonaRuntimeProtocol,
20
+ usePersonaRuntimeEndpoint,
21
+ usePersonaRuntimeWebRTCProtocol,
22
+ usePersonaRuntimeMessages,
23
+ } from './runtime/context';
57
24
 
58
- function getFirstAvailableProtocolEndpoint(protocols: PersonaProtocol[]): string | null {
59
- const extractors: { [key: string]: (protocol: PersonaProtocol) => string } = {
60
- rest: (protocol) => (protocol as PersonaRESTProtocol).config.apiUrl,
61
- webrtc: (protocol) =>
62
- (protocol as PersonaWebRTCProtocol).config.webrtcUrl.replace('/webrtc', '').replace('ws://', 'http://').replace('wss://', 'https://'),
63
- websocket: (protocol) =>
64
- (protocol as PersonaWebSocketProtocol).config.webSocketUrl
65
- .replace('/websocket', '')
66
- .replace('ws://', 'http://')
67
- .replace('wss://', 'https://'),
68
- };
69
-
70
- for (const protocol of protocols) {
71
- const extractor = extractors[protocol.getName()];
72
- if (extractor) {
73
- return extractor(protocol);
74
- }
75
- }
76
- return null;
77
- }
25
+ export type { PersonaMessage, PersonaResponse, ThreadData };
78
26
 
79
27
  function PersonaRuntimeProviderInner({
80
28
  dev = false,
@@ -82,239 +30,264 @@ function PersonaRuntimeProviderInner({
82
30
  protocols: _protocols,
83
31
  logger,
84
32
  children,
85
- session: defaultSession = 'new',
33
+ session: initialSession = 'new',
86
34
  transformMessages,
35
+ threads = false,
36
+ sessionStorage: _sessionStorage,
37
+ onThreadCreate,
38
+ onThreadSwitch,
39
+ onThreadRename,
40
+ onThreadArchive,
41
+ onThreadUnarchive,
42
+ onThreadDelete,
87
43
  ...config
88
44
  }: Readonly<PersonaConfig>) {
89
45
  const [isRunning, setIsRunning] = useState(false);
90
46
  const [messages, setMessages] = useState<PersonaMessage[]>([]);
91
- const [session, setSession] = useState<Session>(defaultSession);
92
47
  const [protocolsStatus, setProtocolsStatus] = useState<Map<string, ProtocolStatus>>(new Map());
93
48
  const didMount = useRef(false);
49
+ const isSwitchingThread = useRef(false);
50
+
51
+ // Initialize message storage
52
+ const sessionStorage = useMemo(
53
+ () =>
54
+ _sessionStorage ||
55
+ new PersonaSessionStorage({
56
+ apiKey: config.apiKey!,
57
+ endpoint: baseUrl || 'https://persona.applica.guru/api',
58
+ }),
59
+ [_sessionStorage, baseUrl, config.apiKey],
60
+ );
94
61
 
95
- const protocols = useMemo<PersonaProtocol[]>(() => {
96
- if (Array.isArray(_protocols)) {
97
- return _protocols;
98
- }
62
+ // Thread management state
63
+ const [threadList, setThreadList] = useState<ThreadData[]>([]);
64
+ const [currentThreadId, setCurrentThreadId] = useState<string>('');
99
65
 
100
- if (typeof _protocols === 'object' && _protocols !== null) {
101
- const baseEndpoint = dev ? 'localhost:8000' : baseUrl || 'persona.applica.guru/api';
102
- const baseEndpointProtocol = dev ? 'http' : 'https';
103
- const baseWebSocketProtocol = dev ? 'ws' : 'wss';
104
- let availableProtocols = Object.keys(_protocols)
105
- .map((key) => {
106
- switch (key) {
107
- case 'rest':
108
- const restConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
109
- if (restConfig === true) {
110
- return new PersonaRESTProtocol({
111
- apiUrl: `${baseEndpointProtocol}://${baseEndpoint}`,
112
- apiKey: config.apiKey,
113
- agentId: config.agentId,
114
- logger,
115
- });
116
- } else if (typeof restConfig === 'object' && restConfig !== null) {
117
- return new PersonaRESTProtocol(restConfig as PersonaRESTProtocolConfig);
118
- } else {
119
- return null;
120
- }
121
- case 'webrtc':
122
- const webrtcConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
123
- if (webrtcConfig === true) {
124
- return new PersonaWebRTCProtocol({
125
- webrtcUrl: `${baseWebSocketProtocol}://${baseEndpoint}/webrtc`,
126
- apiKey: config.apiKey,
127
- agentId: config.agentId,
128
- logger,
129
- });
130
- } else if (typeof webrtcConfig === 'object' && webrtcConfig !== null) {
131
- return new PersonaWebRTCProtocol(webrtcConfig as PersonaWebRTCProtocolConfig);
132
- } else {
133
- return null;
134
- }
135
- case 'websocket':
136
- const websocketConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
137
- if (websocketConfig === true) {
138
- return new PersonaWebSocketProtocol({
139
- webSocketUrl: `${baseWebSocketProtocol}://${baseEndpoint}/websocket`,
140
- apiKey: config.apiKey,
141
- agentId: config.agentId,
142
- logger,
143
- });
144
- } else if (typeof websocketConfig === 'object' && websocketConfig !== null) {
145
- return new PersonaWebSocketProtocol(websocketConfig as PersonaWebSocketProtocolConfig);
146
- } else {
147
- return null;
148
- }
149
- default:
150
- throw new Error(`Unknown protocol: ${key}`);
151
- }
152
- })
153
- .filter((protocol) => protocol !== null) as PersonaProtocol[];
154
-
155
- if (config.tools) {
156
- availableProtocols.push(
157
- new PersonaTransactionProtocol({
158
- apiUrl: `${baseEndpointProtocol}://${baseEndpoint}`,
159
- apiKey: config.apiKey,
160
- agentId: config.agentId,
161
- tools: config.tools, // Pass raw tools
162
- logger,
163
- }),
164
- );
165
- }
166
- return availableProtocols;
167
- }
168
- throw new Error('Invalid protocols configuration');
169
- }, []);
66
+ // Use refs to avoid re-renders and effect loops
67
+ const currentThreadRef = useRef<Session>(typeof initialSession === 'string' ? initialSession : 'new');
68
+ const hasInitialized = useRef(false);
170
69
 
70
+ // Load threads on mount (only once)
171
71
  useEffect(() => {
172
- if (didMount.current) return;
72
+ if (!threads || hasInitialized.current) {
73
+ return;
74
+ }
75
+ hasInitialized.current = true;
76
+
77
+ sessionStorage
78
+ .list(config.agentId, config.userId)
79
+ .then((threads) => {
80
+ if (threads?.length > 0) {
81
+ setThreadList(threads);
82
+ setCurrentThreadId(threads[0].threadId);
83
+ }
84
+ })
85
+ .catch((error) => logger?.error('Failed to load threads:', error));
86
+ }, [threads, sessionStorage, config.agentId, config.userId, logger]);
87
+
88
+ // Initialize protocols
89
+ const protocols = useMemo(
90
+ () =>
91
+ initializeProtocols(_protocols, {
92
+ dev: dev || false,
93
+ baseUrl,
94
+ apiKey: config.apiKey,
95
+ agentId: config.agentId,
96
+ userId: config.userId,
97
+ tools: config.tools,
98
+ logger,
99
+ }),
100
+ [_protocols, dev, baseUrl, config.apiKey, config.agentId, config.userId, config.tools, logger],
101
+ );
173
102
 
103
+ // Setup protocol listeners (only once on mount)
104
+ useEffect(() => {
105
+ if (didMount.current) {
106
+ return;
107
+ }
174
108
  didMount.current = true;
175
109
  logger?.debug(
176
- 'Initializing protocols: ',
177
- protocols.map((protocol) => protocol.getName()),
110
+ 'Setting up protocols:',
111
+ protocols.map((p) => p.getName()),
178
112
  );
179
113
  protocols.forEach((protocol) => {
180
- protocol.setSession(session);
114
+ protocol.setSession(currentThreadRef.current);
181
115
  protocol.clearListeners();
116
+
117
+ // Status change listener
182
118
  protocol.addStatusChangeListener((status: ProtocolStatus) => {
183
- logger?.debug(`${protocol.getName()} has notified new status: ${status}`);
184
- protocolsStatus.set(protocol.getName(), status);
185
- if (status === 'connected') {
119
+ logger?.debug(`${protocol.getName()} status: ${status}`);
120
+ setProtocolsStatus((prev) => {
121
+ const next = new Map(prev);
122
+ next.set(protocol.getName(), status);
123
+ return next;
124
+ });
125
+
126
+ // Only send initial packets on first connection (not during thread switches)
127
+ if (status === 'connected' && !isSwitchingThread.current) {
186
128
  if (config.context) {
187
- protocol.sendPacket({
188
- type: 'command',
189
- payload: {
190
- command: 'set_initial_context',
191
- arguments: config.context,
192
- },
193
- });
129
+ protocol
130
+ .sendPacket({
131
+ type: 'command',
132
+ payload: { command: 'set_initial_context', arguments: config.context },
133
+ })
134
+ .catch(() => {});
194
135
  }
136
+
195
137
  if (config.tools && Array.isArray(config.tools)) {
196
- protocol.sendPacket({
197
- type: 'command',
198
- payload: {
199
- command: 'set_local_tools',
200
- arguments: {
201
- tools: config.tools.map((tool) => tool.schema),
202
- },
203
- },
204
- });
138
+ protocol
139
+ .sendPacket({
140
+ type: 'command',
141
+ payload: { command: 'set_local_tools', arguments: { tools: config.tools.map((t) => t.schema) } },
142
+ })
143
+ .catch(() => {});
205
144
  }
206
145
  }
207
- setProtocolsStatus(new Map(protocolsStatus));
208
146
  });
209
- protocol.addPacketListener((message: PersonaPacket) => {
210
- if (message.type === 'message') {
211
- const personaMessage = message.payload as PersonaMessage;
212
- if (personaMessage?.finishReason === 'stop' && !personaMessage?.functionResponse && !personaMessage?.thought) {
213
- setIsRunning(false);
214
- }
215
147
 
216
- if (personaMessage.thought) {
217
- personaMessage.type = 'reasoning';
218
- }
148
+ // Packet listener
149
+ protocol.addPacketListener(createPacketListener(protocol, setMessages, setIsRunning, protocols, transformMessages));
219
150
 
220
- setMessages((currentConversation) => {
221
- const newMessages = parseMessages([...currentConversation, ...[{ ...personaMessage, protocol: protocol.getName() }]]);
222
- return transformMessages ? transformMessages(newMessages) : newMessages;
223
- });
224
- } else if (message.type === 'reasoning') {
225
- const reasoning = message.payload as PersonaReasoning;
226
- const personaMessage: PersonaMessage = { type: 'reasoning', text: reasoning.thought, role: 'assistant', finishReason: 'stop' };
227
-
228
- setMessages((currentConversation) => {
229
- const newMessages = parseMessages([...currentConversation, ...[{ ...personaMessage, protocol: protocol.getName() }]]);
230
- return transformMessages ? transformMessages(newMessages) : newMessages;
231
- });
232
- } else if (message.type === 'transaction') {
233
- protocols.filter((p) => p !== protocol).forEach((p) => p.onTransaction(message.payload as PersonaTransaction));
234
- }
235
- });
151
+ // Auto-connect if configured
236
152
  if (protocol.autostart && protocol.status === 'disconnected') {
237
- logger?.debug(`Connecting to protocol: ${protocol.getName()}`);
238
- protocol.connect(session);
153
+ logger?.debug(`Auto-connecting protocol: ${protocol.getName()}`);
154
+ protocol.connect(currentThreadRef.current).catch(() => {});
239
155
  setIsRunning(false);
240
156
  }
241
157
  });
242
- }, [session, protocols, logger, protocolsStatus, transformMessages]);
243
-
244
- const onNew = async (message: AppendMessage) => {
245
- if (message.content[0]?.type !== 'text') throw new Error('Only text messages are supported');
246
- const ready = protocols.some((protocol) => protocol.getName() !== 'transaction' && protocol.status === 'connected');
247
- if (!ready) {
248
- setMessages((currentConversation) => {
249
- const newMessages = [
250
- ...currentConversation,
251
- { role: 'assistant' as const, type: 'text' as const, text: 'No protocol is connected.' },
252
- ];
253
- return transformMessages ? transformMessages(newMessages) : newMessages;
254
- });
158
+ // eslint-disable-next-line react-hooks/exhaustive-deps
159
+ }, []); // Run only once
160
+
161
+ // Handle thread switching
162
+ useEffect(() => {
163
+ if (!threads || !currentThreadId) {
164
+ setMessages([]);
255
165
  return;
256
166
  }
257
167
 
258
- const input = message.content[0].text;
259
- setMessages((currentConversation) => {
260
- const newMessages = [...currentConversation, { role: 'user' as const, type: 'text' as const, text: input }];
261
- return transformMessages ? transformMessages(newMessages) : newMessages;
262
- });
263
- setIsRunning(true);
264
-
265
- const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
266
- const content: Array<PersonaMessage> = [];
267
- if (message.attachments) {
268
- for (const attachment of message.attachments) {
269
- if (attachment.contentType.startsWith('image/') && attachment.file) {
270
- content.push({
271
- role: 'user',
272
- image: {
273
- contentType: attachment.contentType,
274
- content: await fileToBase64(attachment.file),
275
- },
276
- text: '',
277
- type: 'text',
278
- });
168
+ if (isSwitchingThread.current || currentThreadId === currentThreadRef.current) {
169
+ return;
170
+ }
171
+
172
+ // Mark as switching to prevent concurrent switches
173
+ isSwitchingThread.current = true;
174
+ logger?.info('Switching to thread:', currentThreadId);
175
+
176
+ const switchThread = async () => {
177
+ try {
178
+ // Disconnect active protocols in parallel
179
+ const connectedProtocols = protocols.filter((p) => p.status === 'connected');
180
+ if (connectedProtocols.length > 0) {
181
+ await Promise.allSettled(connectedProtocols.map((p) => p.disconnect()));
182
+ }
183
+
184
+ // Update thread reference
185
+ currentThreadRef.current = currentThreadId;
186
+
187
+ // Set new session on all protocols
188
+ await Promise.allSettled(protocols.map((p) => p.setSession(currentThreadId)));
189
+
190
+ // Load messages from storage
191
+ const loadedMessages = await sessionStorage.sync(currentThreadId);
192
+ const processed = parseMessages(loadedMessages);
193
+ setMessages(transformMessages ? transformMessages(processed) : processed);
194
+ logger?.debug(`Loaded ${loadedMessages.length} messages for thread ${currentThreadId}`);
195
+
196
+ // Reconnect autostart protocols
197
+ const autostartProtocols = protocols.filter((p) => p.autostart);
198
+ if (autostartProtocols.length > 0) {
199
+ await Promise.allSettled(autostartProtocols.map((p) => p.connect(currentThreadId)));
279
200
  }
201
+ } catch (error) {
202
+ logger?.error('Failed to switch thread:', error);
203
+ setMessages([]);
204
+ } finally {
205
+ isSwitchingThread.current = false;
280
206
  }
281
- }
207
+ };
282
208
 
283
- if (message.content) {
284
- content.push({
285
- role: 'user',
286
- text: message.content[0].text,
287
- type: 'text',
288
- });
289
- }
290
- logger?.debug('Sending message:', content);
291
- await protocol?.sendPacket({ type: 'request', payload: content });
292
- };
209
+ switchThread();
210
+ }, [currentThreadId, threads, protocols, sessionStorage, transformMessages, logger]);
293
211
 
294
- const onCancel = useCallback(() => {
295
- setIsRunning(false);
296
- setMessages([]);
297
- setSession('new');
298
- return Promise.resolve();
299
- }, []);
212
+ // Message handlers
213
+ const onNew = useCallback(createOnNewHandler(protocols, setMessages, setIsRunning, transformMessages, logger), [
214
+ protocols,
215
+ transformMessages,
216
+ logger,
217
+ ]);
300
218
 
301
- const onReload = useCallback(() => {
302
- return Promise.resolve();
303
- }, []);
219
+ const onEdit = useCallback(createOnEditHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
220
+ protocols,
221
+ messages,
222
+ transformMessages,
223
+ logger,
224
+ ]);
304
225
 
305
- const getMessages = useCallback(() => {
306
- return messages;
307
- }, [messages]);
226
+ const onReload = useCallback(createOnReloadHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
227
+ protocols,
228
+ messages,
229
+ transformMessages,
230
+ logger,
231
+ ]);
232
+
233
+ const onCancel = useCallback(createOnCancelHandler(protocols, setMessages, setIsRunning, transformMessages), [
234
+ protocols,
235
+ transformMessages,
236
+ ]);
237
+
238
+ const handleSetMessages = useCallback(
239
+ (newMessages: readonly PersonaMessage[]) => {
240
+ const processed = parseMessages([...newMessages]);
241
+ setMessages(transformMessages ? transformMessages(processed) : processed);
242
+ },
243
+ [transformMessages],
244
+ );
308
245
 
246
+ const getMessages = useCallback(() => messages, [messages]);
247
+
248
+ // Thread list adapter
249
+ const threadListAdapter = useMemo(() => {
250
+ if (!threads) return undefined;
251
+
252
+ return createThreadListAdapter(
253
+ currentThreadId,
254
+ threadList,
255
+ setCurrentThreadId,
256
+ setThreadList,
257
+ sessionStorage,
258
+ onThreadCreate,
259
+ onThreadSwitch,
260
+ onThreadArchive,
261
+ onThreadUnarchive,
262
+ onThreadDelete,
263
+ logger,
264
+ );
265
+ }, [
266
+ threads,
267
+ currentThreadId,
268
+ threadList,
269
+ sessionStorage,
270
+ onThreadCreate,
271
+ onThreadSwitch,
272
+ onThreadArchive,
273
+ onThreadUnarchive,
274
+ onThreadDelete,
275
+ logger,
276
+ ]);
277
+
278
+ // Create runtime
309
279
  const runtime = useExternalStoreRuntime({
310
280
  isRunning,
311
281
  messages,
312
282
  convertMessage,
283
+ setMessages: handleSetMessages,
313
284
  onNew,
285
+ onEdit,
314
286
  onCancel,
315
287
  onReload,
316
288
  adapters: {
317
289
  attachments: new CompositeAttachmentAdapter([new SimpleImageAttachmentAdapter()]),
290
+ ...(threadListAdapter ? { threadList: threadListAdapter } : {}),
318
291
  },
319
292
  });
320
293
 
@@ -325,79 +298,6 @@ function PersonaRuntimeProviderInner({
325
298
  );
326
299
  }
327
300
 
328
- function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
301
+ export function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
329
302
  return <PersonaRuntimeProviderInner {...config}>{children}</PersonaRuntimeProviderInner>;
330
303
  }
331
-
332
- function usePersonaRuntime(): PersonaRuntimeContextType {
333
- const context = useContext(PersonaRuntimeContext);
334
- if (!context) {
335
- throw new Error('usePersonaRuntime must be used within a PersonaRuntimeProvider');
336
- }
337
- return context;
338
- }
339
-
340
- /**
341
- * Retrieves a specific protocol instance from the PersonaRuntimeContext.
342
- *
343
- * @param protocol - The name of the protocol to use.
344
- * @returns {PersonaProtocol | null} - The protocol instance or null if not found.
345
- * @throws {Error} - If the hook is used outside of a PersonaRuntimeProvider.
346
- */
347
- function usePersonaRuntimeProtocol(protocol: string): PersonaProtocol | null {
348
- const context = useContext(PersonaRuntimeContext);
349
- if (!context) {
350
- throw new Error('usePersonaRuntimeProtocol must be used within a PersonaRuntimeProvider');
351
- }
352
-
353
- const protocolInstance = context.protocols.find((p) => p.getName() === protocol);
354
- if (!protocolInstance) {
355
- return null;
356
- }
357
-
358
- const status = context.protocolsStatus.get(protocolInstance.getName());
359
-
360
- return {
361
- ...protocolInstance,
362
- connect: protocolInstance.connect.bind(protocolInstance),
363
- disconnect: protocolInstance.disconnect.bind(protocolInstance),
364
- sendPacket: protocolInstance.sendPacket.bind(protocolInstance),
365
- setSession: protocolInstance.setSession.bind(protocolInstance),
366
- addStatusChangeListener: protocolInstance.addStatusChangeListener.bind(protocolInstance),
367
- addPacketListener: protocolInstance.addPacketListener.bind(protocolInstance),
368
- getName: protocolInstance.getName.bind(protocolInstance),
369
- getPriority: protocolInstance.getPriority.bind(protocolInstance),
370
- status: status || protocolInstance.status,
371
- };
372
- }
373
-
374
- function usePersonaRuntimeEndpoint(): string | null {
375
- const context = useContext(PersonaRuntimeContext);
376
- if (!context) {
377
- throw new Error('usePersonaRuntimeEndpoint must be used within a PersonaRuntimeProvider');
378
- }
379
-
380
- return getFirstAvailableProtocolEndpoint(context.protocols);
381
- }
382
-
383
- function usePersonaRuntimeWebRTCProtocol(): PersonaWebRTCProtocol | null {
384
- return usePersonaRuntimeProtocol('webrtc') as PersonaWebRTCProtocol;
385
- }
386
-
387
- function usePersonaRuntimeMessages(): PersonaMessage[] {
388
- const context = useContext(PersonaRuntimeContext);
389
- if (!context) {
390
- throw new Error('usePersonaRuntimeMessages must be used within a PersonaRuntimeProvider');
391
- }
392
- return context.getMessages();
393
- }
394
-
395
- export {
396
- PersonaRuntimeProvider,
397
- usePersonaRuntimeEndpoint,
398
- usePersonaRuntime,
399
- usePersonaRuntimeProtocol,
400
- usePersonaRuntimeWebRTCProtocol,
401
- usePersonaRuntimeMessages,
402
- };
403
- export type { PersonaMessage, PersonaResponse };
@@ -0,0 +1,21 @@
1
+ import type { PersonaMessage, Session, ThreadData } from '../types';
2
+
3
+ /**
4
+ * Storage interface for messages with thread management support.
5
+ */
6
+ export interface SessionStorage {
7
+ /**
8
+ * Sync messages for a specific session (load existing messages)
9
+ */
10
+ sync(sessionId: Session): Promise<PersonaMessage[]>;
11
+
12
+ /**
13
+ * Remove messages for a specific session (if supported)
14
+ */
15
+ remove(sessionId: Session): Promise<void>;
16
+
17
+ /**
18
+ * List all threads for a specific user
19
+ */
20
+ list(agentId: string, userId?: string): Promise<ThreadData[]>;
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './base';
2
+ export * from './persona';