@applica-software-guru/persona-chat-sdk 0.1.102

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 (132) hide show
  1. package/.eslintrc.cjs +11 -0
  2. package/.github/copilot-instructions.md +3 -0
  3. package/.nvmrc +1 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc +8 -0
  6. package/CLAUDE.md +3 -0
  7. package/GEMINI.md +3 -0
  8. package/README.md +33 -0
  9. package/bitbucket-pipelines.yml +19 -0
  10. package/components.json +24 -0
  11. package/dist/bundle.cjs.js +28 -0
  12. package/dist/bundle.cjs.js.map +1 -0
  13. package/dist/bundle.es.js +5173 -0
  14. package/dist/bundle.es.js.map +1 -0
  15. package/dist/bundle.iife.js +28 -0
  16. package/dist/bundle.iife.js.map +1 -0
  17. package/dist/bundle.umd.js +28 -0
  18. package/dist/bundle.umd.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/logging.d.ts +18 -0
  22. package/dist/logging.d.ts.map +1 -0
  23. package/dist/messages.d.ts +9 -0
  24. package/dist/messages.d.ts.map +1 -0
  25. package/dist/projects.d.ts +17 -0
  26. package/dist/projects.d.ts.map +1 -0
  27. package/dist/protocol/base.d.ts +25 -0
  28. package/dist/protocol/base.d.ts.map +1 -0
  29. package/dist/protocol/index.d.ts +6 -0
  30. package/dist/protocol/index.d.ts.map +1 -0
  31. package/dist/protocol/rest.d.ts +25 -0
  32. package/dist/protocol/rest.d.ts.map +1 -0
  33. package/dist/protocol/transaction.d.ts +56 -0
  34. package/dist/protocol/transaction.d.ts.map +1 -0
  35. package/dist/protocol/webrtc.d.ts +60 -0
  36. package/dist/protocol/webrtc.d.ts.map +1 -0
  37. package/dist/protocol/websocket.d.ts +22 -0
  38. package/dist/protocol/websocket.d.ts.map +1 -0
  39. package/dist/runtime/context.d.ts +34 -0
  40. package/dist/runtime/context.d.ts.map +1 -0
  41. package/dist/runtime/file-attachment-adapter.d.ts +15 -0
  42. package/dist/runtime/file-attachment-adapter.d.ts.map +1 -0
  43. package/dist/runtime/handlers.d.ts +21 -0
  44. package/dist/runtime/handlers.d.ts.map +1 -0
  45. package/dist/runtime/listeners.d.ts +6 -0
  46. package/dist/runtime/listeners.d.ts.map +1 -0
  47. package/dist/runtime/protocols.d.ts +17 -0
  48. package/dist/runtime/protocols.d.ts.map +1 -0
  49. package/dist/runtime/threads.d.ts +34 -0
  50. package/dist/runtime/threads.d.ts.map +1 -0
  51. package/dist/runtime/utils.d.ts +10 -0
  52. package/dist/runtime/utils.d.ts.map +1 -0
  53. package/dist/runtime.d.ts +6 -0
  54. package/dist/runtime.d.ts.map +1 -0
  55. package/dist/storage/base.d.ts +9 -0
  56. package/dist/storage/base.d.ts.map +1 -0
  57. package/dist/storage/index.d.ts +3 -0
  58. package/dist/storage/index.d.ts.map +1 -0
  59. package/dist/storage/persona.d.ts +29 -0
  60. package/dist/storage/persona.d.ts.map +1 -0
  61. package/dist/tools.d.ts +72 -0
  62. package/dist/tools.d.ts.map +1 -0
  63. package/dist/types.d.ts +237 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/docs/README.md +13 -0
  66. package/docs/api-reference.md +16 -0
  67. package/docs/contributing.md +21 -0
  68. package/docs/customization.md +74 -0
  69. package/docs/features.md +7 -0
  70. package/docs/installation.md +9 -0
  71. package/docs/messages.md +214 -0
  72. package/docs/protocols.md +39 -0
  73. package/docs/transactions.md +121 -0
  74. package/docs/usage.md +40 -0
  75. package/jsconfig.node.json +10 -0
  76. package/package.json +82 -0
  77. package/playground/index.html +14 -0
  78. package/playground/src/app.tsx +10 -0
  79. package/playground/src/chat.tsx +117 -0
  80. package/playground/src/components/assistant-ui/assistant-modal.tsx +57 -0
  81. package/playground/src/components/assistant-ui/attachment.tsx +197 -0
  82. package/playground/src/components/assistant-ui/markdown-text.tsx +228 -0
  83. package/playground/src/components/assistant-ui/thread-list.tsx +91 -0
  84. package/playground/src/components/assistant-ui/thread.tsx +302 -0
  85. package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
  86. package/playground/src/components/assistant-ui/tool-fallback.tsx +93 -0
  87. package/playground/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
  88. package/playground/src/components/chat/logging.tsx +53 -0
  89. package/playground/src/components/ui/avatar.tsx +51 -0
  90. package/playground/src/components/ui/button.tsx +60 -0
  91. package/playground/src/components/ui/dialog.tsx +141 -0
  92. package/playground/src/components/ui/input.tsx +21 -0
  93. package/playground/src/components/ui/separator.tsx +26 -0
  94. package/playground/src/components/ui/sheet.tsx +139 -0
  95. package/playground/src/components/ui/sidebar.tsx +619 -0
  96. package/playground/src/components/ui/skeleton.tsx +13 -0
  97. package/playground/src/components/ui/tooltip.tsx +59 -0
  98. package/playground/src/hooks/theme.ts +70 -0
  99. package/playground/src/hooks/use-mobile.ts +19 -0
  100. package/playground/src/lib/utils.ts +6 -0
  101. package/playground/src/main.tsx +10 -0
  102. package/playground/src/styles.css +120 -0
  103. package/playground/src/tools.ts +149 -0
  104. package/playground/src/vite-env.d.ts +1 -0
  105. package/preview-build.sh +23 -0
  106. package/src/index.ts +7 -0
  107. package/src/logging.ts +34 -0
  108. package/src/messages.ts +202 -0
  109. package/src/projects.ts +57 -0
  110. package/src/protocol/base.ts +73 -0
  111. package/src/protocol/index.ts +5 -0
  112. package/src/protocol/rest.ts +107 -0
  113. package/src/protocol/transaction.ts +182 -0
  114. package/src/protocol/webrtc.ts +379 -0
  115. package/src/protocol/websocket.ts +111 -0
  116. package/src/runtime/context.ts +88 -0
  117. package/src/runtime/file-attachment-adapter.ts +48 -0
  118. package/src/runtime/handlers.ts +322 -0
  119. package/src/runtime/index.ts +6 -0
  120. package/src/runtime/listeners.ts +79 -0
  121. package/src/runtime/protocols.ts +169 -0
  122. package/src/runtime/threads.ts +105 -0
  123. package/src/runtime/utils.ts +46 -0
  124. package/src/runtime.tsx +334 -0
  125. package/src/storage/base.ts +13 -0
  126. package/src/storage/index.ts +2 -0
  127. package/src/storage/persona.ts +138 -0
  128. package/src/tools.ts +211 -0
  129. package/src/types.ts +284 -0
  130. package/tsconfig.json +36 -0
  131. package/tsconfig.node.json +15 -0
  132. package/vite.config.ts +74 -0
@@ -0,0 +1,46 @@
1
+ import type { PersonaProtocol } from '../types';
2
+ import { PersonaRESTProtocol, PersonaWebRTCProtocol, PersonaWebSocketProtocol } from '../protocol';
3
+
4
+ /**
5
+ * Converts a File object to a base64 string
6
+ */
7
+ export function fileToBase64(file: File): Promise<string> {
8
+ return new Promise((resolve, reject) => {
9
+ const reader = new FileReader();
10
+ reader.readAsDataURL(file);
11
+
12
+ reader.onload = () => {
13
+ const base64 = reader.result as string;
14
+ const base64WithoutPrefix = base64.split(';base64,')[1];
15
+ resolve(base64WithoutPrefix);
16
+ };
17
+
18
+ reader.onerror = (error) => {
19
+ reject(error);
20
+ };
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Extracts the first available protocol endpoint URL
26
+ */
27
+ export function getFirstAvailableProtocolEndpoint(protocols: PersonaProtocol[]): string | null {
28
+ const extractors: { [key: string]: (protocol: PersonaProtocol) => string } = {
29
+ rest: (protocol) => (protocol as PersonaRESTProtocol).config.apiUrl,
30
+ webrtc: (protocol) =>
31
+ (protocol as PersonaWebRTCProtocol).config.webrtcUrl.replace('/webrtc', '').replace('ws://', 'http://').replace('wss://', 'https://'),
32
+ websocket: (protocol) =>
33
+ (protocol as PersonaWebSocketProtocol).config.webSocketUrl
34
+ .replace('/websocket', '')
35
+ .replace('ws://', 'http://')
36
+ .replace('wss://', 'https://'),
37
+ };
38
+
39
+ for (const protocol of protocols) {
40
+ const extractor = extractors[protocol.getName()];
41
+ if (extractor) {
42
+ return extractor(protocol);
43
+ }
44
+ }
45
+ return null;
46
+ }
@@ -0,0 +1,334 @@
1
+ import { useState, useEffect, useCallback, PropsWithChildren, useMemo, useRef } from 'react';
2
+ import {
3
+ useExternalStoreRuntime,
4
+ AssistantRuntimeProvider,
5
+ CompositeAttachmentAdapter,
6
+ SimpleImageAttachmentAdapter,
7
+ SimpleTextAttachmentAdapter,
8
+ } from '@assistant-ui/react';
9
+ import { SimpleFileAttachmentAdapter } from './runtime/file-attachment-adapter';
10
+ import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData } from './types';
11
+ import { parseMessages, convertMessage } from './messages';
12
+ import { initializeProtocols } from './runtime/protocols';
13
+ import { createPacketListener } from './runtime/listeners';
14
+ import { createOnNewHandler, createOnEditHandler, createOnReloadHandler, createOnCancelHandler } from './runtime/handlers';
15
+ import { PersonaRuntimeContext } from './runtime/context';
16
+ import { createThreadListAdapter } from './runtime/threads';
17
+ import { PersonaSessionStorage } from './storage';
18
+
19
+ export {
20
+ usePersonaRuntime,
21
+ usePersonaRuntimeProtocol,
22
+ usePersonaRuntimeEndpoint,
23
+ usePersonaRuntimeWebRTCProtocol,
24
+ usePersonaRuntimeMessages,
25
+ } from './runtime/context';
26
+
27
+ export type { PersonaMessage, PersonaResponse, ThreadData };
28
+
29
+ function PersonaRuntimeProviderInner({
30
+ dev = false,
31
+ baseUrl,
32
+ protocols: _protocols,
33
+ logger,
34
+ children,
35
+ session: initialSession = 'new',
36
+ transformMessages,
37
+ threads = false,
38
+ sessionStorage: _sessionStorage,
39
+ onThreadCreate,
40
+ onThreadSwitch,
41
+ onThreadRename,
42
+ onThreadArchive,
43
+ onThreadUnarchive,
44
+ onThreadDelete,
45
+ enableAttachments = false,
46
+ ...config
47
+ }: Readonly<PersonaConfig>) {
48
+ const [isRunning, setIsRunning] = useState(false);
49
+ const [messages, setMessages] = useState<PersonaMessage[]>([]);
50
+ const [protocolsStatus, setProtocolsStatus] = useState<Map<string, ProtocolStatus>>(new Map());
51
+ const didMount = useRef(false);
52
+ const isSwitchingThread = useRef(false);
53
+
54
+ // Initialize message storage
55
+ const sessionStorage = useMemo(
56
+ () =>
57
+ _sessionStorage ||
58
+ new PersonaSessionStorage({
59
+ apiKey: config.apiKey!,
60
+ endpoint: baseUrl || 'https://persona.applica.guru/api',
61
+ }),
62
+ [_sessionStorage, baseUrl, config.apiKey],
63
+ );
64
+
65
+ // Thread management state
66
+ const [threadList, setThreadList] = useState<ThreadData[]>([]);
67
+ const [currentThreadId, setCurrentThreadId] = useState<string>('');
68
+
69
+ // Use refs to avoid re-renders and effect loops
70
+ const currentThreadRef = useRef<Session>(typeof initialSession === 'string' ? initialSession : 'new');
71
+ const hasInitialized = useRef(false);
72
+
73
+ // Load threads on mount (only once)
74
+ useEffect(() => {
75
+ if (!threads || hasInitialized.current) {
76
+ return;
77
+ }
78
+ hasInitialized.current = true;
79
+
80
+ sessionStorage
81
+ .list(config.agentId, config.userId)
82
+ .then((threads) => {
83
+ if (threads?.length > 0) {
84
+ setThreadList(threads);
85
+ setCurrentThreadId(threads[0].threadId);
86
+ }
87
+ })
88
+ .catch((error) => logger?.error('Failed to load threads:', error));
89
+ }, [threads, sessionStorage, config.agentId, config.userId, logger]);
90
+
91
+ // Initialize protocols
92
+ const protocols = useMemo(
93
+ () =>
94
+ initializeProtocols(_protocols, {
95
+ dev: dev || false,
96
+ baseUrl,
97
+ apiKey: config.apiKey,
98
+ agentId: config.agentId,
99
+ userId: config.userId,
100
+ tools: config.tools,
101
+ logger,
102
+ }),
103
+ [_protocols, dev, baseUrl, config.apiKey, config.agentId, config.userId, config.tools, logger],
104
+ );
105
+
106
+ // Setup protocol listeners (only once on mount)
107
+ useEffect(() => {
108
+ if (didMount.current) {
109
+ return;
110
+ }
111
+ didMount.current = true;
112
+ logger?.debug(
113
+ 'Setting up protocols:',
114
+ protocols.map((p) => p.getName()),
115
+ );
116
+ protocols.forEach((protocol) => {
117
+ protocol.setSession(currentThreadRef.current);
118
+ protocol.clearListeners();
119
+
120
+ // Status change listener
121
+ protocol.addStatusChangeListener((status: ProtocolStatus) => {
122
+ logger?.debug(`${protocol.getName()} status: ${status}`);
123
+ setProtocolsStatus((prev) => {
124
+ const next = new Map(prev);
125
+ next.set(protocol.getName(), status);
126
+ return next;
127
+ });
128
+
129
+ // Only send initial packets on first connection (not during thread switches)
130
+ if (status === 'connected' && !isSwitchingThread.current) {
131
+ if (config.context) {
132
+ protocol
133
+ .sendPacket({
134
+ type: 'command',
135
+ payload: { command: 'set_initial_context', arguments: config.context },
136
+ })
137
+ .catch(() => {});
138
+ }
139
+
140
+ if (config.tools && Array.isArray(config.tools)) {
141
+ protocol
142
+ .sendPacket({
143
+ type: 'command',
144
+ payload: { command: 'set_local_tools', arguments: { tools: config.tools.map((t) => t.schema) } },
145
+ })
146
+ .catch(() => {});
147
+ }
148
+ }
149
+ });
150
+
151
+ // Packet listener
152
+ protocol.addPacketListener(createPacketListener(protocol, setMessages, setIsRunning, protocols, transformMessages));
153
+
154
+ // Auto-connect if configured
155
+ if (protocol.autostart && protocol.status === 'disconnected') {
156
+ logger?.debug(`Auto-connecting protocol: ${protocol.getName()}`);
157
+ protocol.connect(currentThreadRef.current).catch(() => {});
158
+ setIsRunning(false);
159
+ }
160
+ });
161
+ // eslint-disable-next-line react-hooks/exhaustive-deps
162
+ }, []); // Run only once
163
+
164
+ // Load messages for an existing session on mount (without threads)
165
+ useEffect(() => {
166
+ if (threads || !initialSession || initialSession === 'new') {
167
+ return;
168
+ }
169
+
170
+ sessionStorage
171
+ .sync(initialSession)
172
+ .then((loadedMessages) => {
173
+ if (loadedMessages.length > 0) {
174
+ const processed = parseMessages(loadedMessages);
175
+ setMessages(transformMessages ? transformMessages(processed) : processed);
176
+ logger?.debug(`Loaded ${loadedMessages.length} messages for session ${initialSession}`);
177
+ }
178
+ })
179
+ .catch((error) => {
180
+ logger?.error('Failed to load session messages:', error);
181
+ });
182
+ // eslint-disable-next-line react-hooks/exhaustive-deps
183
+ }, []); // Run only once on mount
184
+
185
+ // Handle thread switching
186
+ useEffect(() => {
187
+ if (!threads || !currentThreadId) {
188
+ setMessages([]);
189
+ return;
190
+ }
191
+
192
+ if (isSwitchingThread.current || currentThreadId === currentThreadRef.current) {
193
+ return;
194
+ }
195
+
196
+ // Mark as switching to prevent concurrent switches
197
+ isSwitchingThread.current = true;
198
+ logger?.info('Switching to thread:', currentThreadId);
199
+
200
+ const switchThread = async () => {
201
+ try {
202
+ // Disconnect active protocols in parallel
203
+ const connectedProtocols = protocols.filter((p) => p.status === 'connected');
204
+ if (connectedProtocols.length > 0) {
205
+ await Promise.allSettled(connectedProtocols.map((p) => p.disconnect()));
206
+ }
207
+
208
+ // Update thread reference
209
+ currentThreadRef.current = currentThreadId;
210
+
211
+ // Set new session on all protocols
212
+ await Promise.allSettled(protocols.map((p) => p.setSession(currentThreadId)));
213
+
214
+ // Load messages from storage
215
+ const loadedMessages = await sessionStorage.sync(currentThreadId);
216
+ const processed = parseMessages(loadedMessages);
217
+ setMessages(transformMessages ? transformMessages(processed) : processed);
218
+ logger?.debug(`Loaded ${loadedMessages.length} messages for thread ${currentThreadId}`);
219
+
220
+ // Reconnect autostart protocols
221
+ const autostartProtocols = protocols.filter((p) => p.autostart);
222
+ if (autostartProtocols.length > 0) {
223
+ await Promise.allSettled(autostartProtocols.map((p) => p.connect(currentThreadId)));
224
+ }
225
+ } catch (error) {
226
+ logger?.error('Failed to switch thread:', error);
227
+ setMessages([]);
228
+ } finally {
229
+ isSwitchingThread.current = false;
230
+ }
231
+ };
232
+
233
+ switchThread();
234
+ }, [currentThreadId, threads, protocols, sessionStorage, transformMessages, logger]);
235
+
236
+ // Message handlers
237
+ const onNew = useCallback(createOnNewHandler(protocols, setMessages, setIsRunning, transformMessages, logger), [
238
+ protocols,
239
+ transformMessages,
240
+ logger,
241
+ ]);
242
+
243
+ const onEdit = useCallback(createOnEditHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
244
+ protocols,
245
+ messages,
246
+ transformMessages,
247
+ logger,
248
+ ]);
249
+
250
+ const onReload = useCallback(createOnReloadHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
251
+ protocols,
252
+ messages,
253
+ transformMessages,
254
+ logger,
255
+ ]);
256
+
257
+ const onCancel = useCallback(createOnCancelHandler(protocols, setMessages, setIsRunning, transformMessages), [
258
+ protocols,
259
+ transformMessages,
260
+ ]);
261
+
262
+ const handleSetMessages = useCallback(
263
+ (newMessages: readonly PersonaMessage[]) => {
264
+ const processed = parseMessages([...newMessages]);
265
+ setMessages(transformMessages ? transformMessages(processed) : processed);
266
+ },
267
+ [transformMessages],
268
+ );
269
+
270
+ const getMessages = useCallback(() => messages, [messages]);
271
+
272
+ // Thread list adapter
273
+ const threadListAdapter = useMemo(() => {
274
+ if (!threads) return undefined;
275
+
276
+ return createThreadListAdapter(
277
+ currentThreadId,
278
+ threadList,
279
+ setCurrentThreadId,
280
+ setThreadList,
281
+ sessionStorage,
282
+ onThreadCreate,
283
+ onThreadSwitch,
284
+ onThreadArchive,
285
+ onThreadUnarchive,
286
+ onThreadDelete,
287
+ );
288
+ }, [
289
+ threads,
290
+ currentThreadId,
291
+ threadList,
292
+ sessionStorage,
293
+ onThreadCreate,
294
+ onThreadSwitch,
295
+ onThreadArchive,
296
+ onThreadUnarchive,
297
+ onThreadDelete,
298
+ logger,
299
+ ]);
300
+
301
+ // Create runtime
302
+ const runtime = useExternalStoreRuntime({
303
+ isRunning,
304
+ messages,
305
+ convertMessage,
306
+ setMessages: handleSetMessages,
307
+ onNew,
308
+ onEdit,
309
+ onCancel,
310
+ onReload,
311
+ adapters: {
312
+ ...(enableAttachments
313
+ ? {
314
+ attachments: new CompositeAttachmentAdapter([
315
+ new SimpleImageAttachmentAdapter(),
316
+ new SimpleTextAttachmentAdapter(),
317
+ new SimpleFileAttachmentAdapter(),
318
+ ]),
319
+ }
320
+ : {}),
321
+ ...(threadListAdapter ? { threadList: threadListAdapter } : {}),
322
+ },
323
+ });
324
+
325
+ return (
326
+ <PersonaRuntimeContext.Provider value={{ protocols, protocolsStatus, getMessages }}>
327
+ <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>
328
+ </PersonaRuntimeContext.Provider>
329
+ );
330
+ }
331
+
332
+ export function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
333
+ return <PersonaRuntimeProviderInner {...config}>{children}</PersonaRuntimeProviderInner>;
334
+ }
@@ -0,0 +1,13 @@
1
+ import type { PersonaMessage, Session, ThreadData } from '../types';
2
+
3
+ export interface SessionStorage {
4
+ sync(sessionId: Session): Promise<PersonaMessage[]>;
5
+
6
+ archive(sessionId: Session): Promise<void>;
7
+
8
+ unarchive(sessionId: Session): Promise<void>;
9
+
10
+ delete(sessionId: Session): Promise<void>;
11
+
12
+ list(agentId: string, userId?: string): Promise<ThreadData[]>;
13
+ }
@@ -0,0 +1,2 @@
1
+ export * from './base';
2
+ export * from './persona';
@@ -0,0 +1,138 @@
1
+ import type { PersonaMessage, Session, ThreadData } from '../types';
2
+ import type { SessionStorage } from './base';
3
+ import type { PersonaLogger } from '../logging';
4
+
5
+ type PersonaSessionStorageOptions = {
6
+ endpoint: string;
7
+ apiKey?: string;
8
+ pageSize?: number;
9
+ logger?: PersonaLogger;
10
+ };
11
+
12
+ export class PersonaSessionStorage implements SessionStorage {
13
+ private endpoint: string;
14
+ private apiKey?: string;
15
+ private pageSize: number;
16
+ private logger?: PersonaLogger;
17
+
18
+ constructor(opts: PersonaSessionStorageOptions) {
19
+ this.endpoint = opts.endpoint.replace(/\/$/, '');
20
+ this.apiKey = opts.apiKey;
21
+ this.pageSize = opts.pageSize ?? 200;
22
+ this.logger = opts.logger;
23
+ }
24
+
25
+ private buildHeaders() {
26
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
27
+ if (this.apiKey) headers['x-persona-apikey'] = this.apiKey;
28
+ return headers;
29
+ }
30
+
31
+ /**
32
+ * Fetch messages for a session from the Persona REST API
33
+ */
34
+ async sync(sessionId: Session): Promise<PersonaMessage[]> {
35
+ if (!sessionId) return [];
36
+ const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/messages?page=1&size=${this.pageSize}`;
37
+
38
+ try {
39
+ const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() });
40
+ if (!res.ok) {
41
+ // Log and return empty list on error
42
+ this.logger?.warn('PersonaSessionStorage: failed to fetch messages', res.status, await res.text());
43
+ return [];
44
+ }
45
+
46
+ const data = await res.json();
47
+ if (!Array.isArray(data.items)) return [];
48
+
49
+ // Try to normalize createdAt strings to Date objects
50
+ const messages: PersonaMessage[] = data.items.map((m: any) => {
51
+ const msg: PersonaMessage = {
52
+ id: m.id ?? m.messageId ?? undefined,
53
+ protocol: m.protocol,
54
+ thought: m.thought,
55
+ text: m.text ?? '',
56
+ image: m.image,
57
+ type: m.type ?? 'text',
58
+ role: m.role ?? 'assistant',
59
+ file: m.file,
60
+ sources: m.sources,
61
+ sessionId: m.sessionId ?? sessionId,
62
+ finishReason: m.finishReason,
63
+ functionCalls: m.functionCalls,
64
+ functionResponse: m.functionResponse,
65
+ status: m.status,
66
+ createdAt: m.createdAt ? new Date(m.createdAt) : undefined,
67
+ metadata: m.metadata,
68
+ };
69
+ return msg;
70
+ });
71
+
72
+ return messages;
73
+ } catch (err) {
74
+ // Network or parsing error
75
+ this.logger?.warn('PersonaSessionStorage: error fetching messages', err);
76
+ return [];
77
+ }
78
+ }
79
+
80
+ async archive(sessionId: Session): Promise<void> {
81
+ return this._visiblility(sessionId, 'archived');
82
+ }
83
+
84
+ async unarchive(sessionId: Session): Promise<void> {
85
+ return this._visiblility(sessionId, 'active');
86
+ }
87
+
88
+ async delete(sessionId: Session): Promise<void> {
89
+ return this._visiblility(sessionId, 'deleted');
90
+ }
91
+
92
+ async _visiblility(sessionId: Session, visibility: 'active' | 'deleted' | 'archived'): Promise<void> {
93
+ if (!sessionId) return;
94
+ const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/visibility`;
95
+ try {
96
+ const body = JSON.stringify({ visibility });
97
+ const res = await fetch(url, { method: 'PUT', headers: this.buildHeaders(), body });
98
+ if (!res.ok) {
99
+ const text = await res.text();
100
+ this.logger?.warn('PersonaSessionStorage: failed to set session visibility', res.status, text);
101
+ throw new Error(`Failed to set session visibility: ${res.status}`);
102
+ }
103
+ return;
104
+ } catch (err) {
105
+ throw err;
106
+ }
107
+ }
108
+
109
+ async clearAll(): Promise<void> {
110
+ return Promise.reject(new Error('PersonaAPIMessageStorage: clearAll not supported'));
111
+ }
112
+
113
+ async list(agentId: string, userId?: string): Promise<ThreadData[]> {
114
+ const params = new URLSearchParams({ page: '1', size: '100', visibility: 'active' });
115
+ if (userId) params.set('userId', userId);
116
+ if (agentId) params.set('agentId', agentId);
117
+
118
+ const url = `${this.endpoint}/sessions?${params.toString()}`;
119
+ try {
120
+ const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() });
121
+ if (!res.ok) {
122
+ return [];
123
+ }
124
+
125
+ const data = await res.json();
126
+ if (!Array.isArray(data.items)) return [];
127
+ const threads: ThreadData[] = data.items.map((item: any) => ({
128
+ threadId: item.code || item.id,
129
+ title: item.title || `Session ${item.code || item.id}`,
130
+ status: item.status === 'archived' ? 'archived' : 'regular',
131
+ }));
132
+
133
+ return threads;
134
+ } catch (err) {
135
+ return [];
136
+ }
137
+ }
138
+ }