@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,322 @@
1
+ import { AppendMessage } from '@assistant-ui/react';
2
+ import { StartRunConfig } from '@assistant-ui/react/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore';
3
+ import { PersonaProtocol, PersonaMessage, TransformMessages } from '../types';
4
+ import type { PersonaLogger } from '../logging';
5
+ import { parseMessages } from '../messages';
6
+ import { fileToBase64 } from './utils';
7
+
8
+ /**
9
+ * Creates the onNew handler for new messages
10
+ */
11
+ export function createOnNewHandler(
12
+ protocols: PersonaProtocol[],
13
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
14
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
15
+ transformMessages?: TransformMessages,
16
+ logger?: PersonaLogger,
17
+ ) {
18
+ return async (message: AppendMessage) => {
19
+ const textPart = (message.content as Array<{ type: string; text?: string }>).find((c) => c.type === 'text');
20
+ const hasAttachments = message.attachments && message.attachments.length > 0;
21
+
22
+ if (!textPart && !hasAttachments) {
23
+ throw new Error('Message must contain text or attachments');
24
+ }
25
+
26
+ const ready = protocols.some((protocol) => protocol.getName() !== 'transaction' && protocol.status === 'connected');
27
+
28
+ if (!ready) {
29
+ setMessages((currentConversation) => {
30
+ const errorMessage: PersonaMessage = {
31
+ role: 'assistant',
32
+ type: 'text',
33
+ text: 'No protocol is connected.',
34
+ createdAt: new Date(),
35
+ finishReason: 'stop',
36
+ };
37
+ const newMessages = [...currentConversation, errorMessage];
38
+ return transformMessages ? transformMessages(newMessages) : newMessages;
39
+ });
40
+ return;
41
+ }
42
+
43
+ const input = textPart?.type === 'text' ? textPart.text ?? '' : '';
44
+ const userMessage: PersonaMessage = {
45
+ role: 'user',
46
+ type: 'text',
47
+ text: input,
48
+ createdAt: new Date(),
49
+ };
50
+
51
+ setMessages((currentConversation) => {
52
+ const newMessages = parseMessages([...currentConversation, userMessage]);
53
+ return transformMessages ? transformMessages(newMessages) : newMessages;
54
+ });
55
+
56
+ setIsRunning(true);
57
+
58
+ const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
59
+
60
+ const content: Array<PersonaMessage> = [];
61
+
62
+ if (message.attachments) {
63
+ for (const attachment of message.attachments) {
64
+ if (attachment.contentType.startsWith('image/') && attachment.file) {
65
+ content.push({
66
+ role: 'user',
67
+ image: {
68
+ contentType: attachment.contentType,
69
+ content: await fileToBase64(attachment.file),
70
+ },
71
+ text: '',
72
+ type: 'text',
73
+ createdAt: new Date(),
74
+ });
75
+ } else if (attachment.content) {
76
+ // Check for text content (from SimpleTextAttachmentAdapter)
77
+ const textPart = attachment.content.find((c) => c.type === 'text');
78
+ if (textPart && 'text' in textPart) {
79
+ content.push({
80
+ role: 'user',
81
+ file: {
82
+ name: attachment.name,
83
+ contentType: attachment.contentType,
84
+ },
85
+ text: textPart.text,
86
+ type: 'text',
87
+ createdAt: new Date(),
88
+ });
89
+ }
90
+ // Check for file content with base64 data (from SimpleFileAttachmentAdapter)
91
+ const filePart = attachment.content.find((c) => c.type === 'file');
92
+ if (filePart && 'data' in filePart) {
93
+ content.push({
94
+ role: 'user',
95
+ file: {
96
+ name: attachment.name,
97
+ base64: (filePart as { data: string }).data,
98
+ contentType: attachment.contentType,
99
+ },
100
+ text: '',
101
+ type: 'text',
102
+ createdAt: new Date(),
103
+ });
104
+ }
105
+ } else if (attachment.file) {
106
+ // Fallback: any file with no processed content — read as base64
107
+ content.push({
108
+ role: 'user',
109
+ file: {
110
+ name: attachment.name,
111
+ base64: await fileToBase64(attachment.file),
112
+ contentType: attachment.contentType || 'application/octet-stream',
113
+ },
114
+ text: '',
115
+ type: 'text',
116
+ createdAt: new Date(),
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ if (input) {
123
+ content.push({
124
+ role: 'user',
125
+ text: input,
126
+ type: 'text',
127
+ createdAt: new Date(),
128
+ });
129
+ }
130
+
131
+ logger?.debug('Sending message:', content);
132
+ await protocol?.sendPacket({ type: 'request', payload: content });
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Creates the onEdit handler for editing messages
138
+ */
139
+ export function createOnEditHandler(
140
+ protocols: PersonaProtocol[],
141
+ messages: PersonaMessage[],
142
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
143
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
144
+ transformMessages?: TransformMessages,
145
+ logger?: PersonaLogger,
146
+ ) {
147
+ return async (message: AppendMessage) => {
148
+ if (message.content[0]?.type !== 'text') {
149
+ throw new Error('Only text messages are supported');
150
+ }
151
+
152
+ const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
153
+
154
+ if (!protocol) {
155
+ logger?.debug('No protocol available for edit');
156
+ return;
157
+ }
158
+
159
+ const parentId = message.parentId;
160
+ if (!parentId) {
161
+ logger?.debug('No parent ID provided for edit');
162
+ return;
163
+ }
164
+
165
+ const parentIndex = messages.findIndex((m) => m.id === parentId);
166
+ if (parentIndex === -1) {
167
+ logger?.debug('Parent message not found:', parentId);
168
+ return;
169
+ }
170
+
171
+ const messagesToKeep = messages.slice(0, parentIndex + 1);
172
+ const editedMessage: PersonaMessage = {
173
+ role: 'user',
174
+ text: message.content[0].text,
175
+ type: 'text',
176
+ createdAt: new Date(),
177
+ };
178
+
179
+ const newMessages = parseMessages([...messagesToKeep, editedMessage]);
180
+ setMessages(transformMessages ? transformMessages(newMessages) : newMessages);
181
+
182
+ setIsRunning(true);
183
+
184
+ const content: Array<PersonaMessage> = [
185
+ {
186
+ role: 'user',
187
+ text: message.content[0].text,
188
+ type: 'text',
189
+ createdAt: new Date(),
190
+ },
191
+ ];
192
+
193
+ logger?.debug('Sending edited message:', content);
194
+ await protocol.sendPacket({ type: 'request', payload: content });
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Creates the onReload handler for regenerating messages
200
+ */
201
+ export function createOnReloadHandler(
202
+ protocols: PersonaProtocol[],
203
+ messages: PersonaMessage[],
204
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
205
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
206
+ transformMessages?: TransformMessages,
207
+ logger?: PersonaLogger,
208
+ ) {
209
+ return async (parentId: string | null, config: StartRunConfig) => {
210
+ const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
211
+
212
+ if (!protocol) {
213
+ logger?.debug('No protocol available for reload');
214
+ return;
215
+ }
216
+
217
+ const actualParentId = config.parentId ?? parentId;
218
+ if (!actualParentId) {
219
+ logger?.debug('No parent ID provided for reload');
220
+ return;
221
+ }
222
+
223
+ const parentIndex = messages.findIndex((msg) => msg.id === actualParentId);
224
+ if (parentIndex === -1) {
225
+ logger?.debug('Parent message not found:', actualParentId);
226
+ return;
227
+ }
228
+
229
+ const parentMessage = messages[parentIndex];
230
+
231
+ setMessages((currentMessages) => {
232
+ const messagesToKeep = currentMessages.slice(0, parentIndex + 1);
233
+ return transformMessages ? transformMessages(messagesToKeep) : messagesToKeep;
234
+ });
235
+
236
+ let userMessage: PersonaMessage | undefined;
237
+ if (parentMessage.role === 'assistant' || parentMessage.role === 'function') {
238
+ for (let i = parentIndex - 1; i >= 0; i--) {
239
+ if (messages[i].role === 'user') {
240
+ userMessage = messages[i];
241
+ break;
242
+ }
243
+ }
244
+ } else if (parentMessage.role === 'user') {
245
+ userMessage = parentMessage;
246
+ }
247
+
248
+ if (!userMessage) {
249
+ logger?.debug('No user message found to regenerate from');
250
+ return;
251
+ }
252
+
253
+ setIsRunning(true);
254
+
255
+ const content: Array<PersonaMessage> = [];
256
+
257
+ if (userMessage.image) {
258
+ content.push({
259
+ role: 'user',
260
+ image: userMessage.image,
261
+ text: '',
262
+ type: 'text',
263
+ createdAt: new Date(),
264
+ });
265
+ }
266
+
267
+ content.push({
268
+ role: 'user',
269
+ text: userMessage.text,
270
+ type: 'text',
271
+ createdAt: new Date(),
272
+ });
273
+
274
+ logger?.debug('Regenerating message with config:', {
275
+ actualParentId,
276
+ sourceId: config.sourceId,
277
+ custom: config.runConfig.custom,
278
+ });
279
+ await protocol.sendPacket({ type: 'request', payload: content });
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Creates the onCancel handler for cancelling message generation
285
+ */
286
+ export function createOnCancelHandler(
287
+ protocols: PersonaProtocol[],
288
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
289
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
290
+ transformMessages?: TransformMessages,
291
+ ) {
292
+ return () => {
293
+ setIsRunning(false);
294
+
295
+ setMessages((currentMessages) => {
296
+ const lastMessage = currentMessages[currentMessages.length - 1];
297
+ if (lastMessage?.role === 'assistant' && !lastMessage.finishReason) {
298
+ const updatedMessages = [...currentMessages];
299
+ updatedMessages[updatedMessages.length - 1] = {
300
+ ...lastMessage,
301
+ finishReason: 'stop',
302
+ status: { type: 'incomplete' },
303
+ };
304
+ return transformMessages ? transformMessages(updatedMessages) : updatedMessages;
305
+ }
306
+ return currentMessages;
307
+ });
308
+
309
+ protocols.forEach((protocol) => {
310
+ if (protocol.status === 'connected') {
311
+ protocol
312
+ .sendPacket({
313
+ type: 'command',
314
+ payload: { command: 'set_initial_context', arguments: { cancel: true } },
315
+ })
316
+ .catch(() => {});
317
+ }
318
+ });
319
+
320
+ return Promise.resolve();
321
+ };
322
+ }
@@ -0,0 +1,6 @@
1
+ export * from './context';
2
+ export * from './utils';
3
+ export * from './protocols';
4
+ export * from './handlers';
5
+ export * from './listeners';
6
+ export * from './threads';
@@ -0,0 +1,79 @@
1
+ import { PersonaPacket, PersonaMessage, PersonaReasoning, PersonaTransaction, PersonaProtocol, TransformMessages } from '../types';
2
+ import { parseMessages } from '../messages';
3
+
4
+ /**
5
+ * Creates packet listener for a protocol
6
+ */
7
+ export function createPacketListener(
8
+ protocol: PersonaProtocol,
9
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
10
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
11
+ protocols: PersonaProtocol[],
12
+ transformMessages?: TransformMessages,
13
+ ) {
14
+ return (message: PersonaPacket) => {
15
+ if (message.type === 'message') {
16
+ handleMessagePacket(message.payload as PersonaMessage, protocol, setMessages, setIsRunning, transformMessages);
17
+ } else if (message.type === 'reasoning') {
18
+ handleReasoningPacket(message.payload as PersonaReasoning, protocol, setMessages, transformMessages);
19
+ } else if (message.type === 'transaction') {
20
+ handleTransactionPacket(message.payload as PersonaTransaction, protocol, protocols);
21
+ }
22
+ };
23
+ }
24
+
25
+ function handleMessagePacket(
26
+ personaMessage: PersonaMessage,
27
+ protocol: PersonaProtocol,
28
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
29
+ setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
30
+ transformMessages?: TransformMessages,
31
+ ) {
32
+ // Ensure message has proper timestamp
33
+ if (!personaMessage.createdAt) {
34
+ personaMessage.createdAt = new Date();
35
+ }
36
+
37
+ // Handle finish reason and update isRunning state
38
+ if (personaMessage?.finishReason === 'stop' && !personaMessage?.functionResponse && !personaMessage?.thought) {
39
+ setIsRunning(false);
40
+ }
41
+
42
+ // Convert thought to reasoning type
43
+ if (personaMessage.thought) {
44
+ personaMessage.type = 'reasoning';
45
+ }
46
+
47
+ // Add protocol information
48
+ personaMessage.protocol = protocol.getName();
49
+
50
+ setMessages((currentConversation) => {
51
+ const newMessages = parseMessages([...currentConversation, personaMessage]);
52
+ return transformMessages ? transformMessages(newMessages) : newMessages;
53
+ });
54
+ }
55
+
56
+ function handleReasoningPacket(
57
+ reasoning: PersonaReasoning,
58
+ protocol: PersonaProtocol,
59
+ setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
60
+ transformMessages?: TransformMessages,
61
+ ) {
62
+ const personaMessage: PersonaMessage = {
63
+ type: 'reasoning',
64
+ text: reasoning.thought,
65
+ role: 'assistant',
66
+ finishReason: 'stop',
67
+ protocol: protocol.getName(),
68
+ createdAt: new Date(),
69
+ };
70
+
71
+ setMessages((currentConversation) => {
72
+ const newMessages = parseMessages([...currentConversation, personaMessage]);
73
+ return transformMessages ? transformMessages(newMessages) : newMessages;
74
+ });
75
+ }
76
+
77
+ function handleTransactionPacket(transaction: PersonaTransaction, currentProtocol: PersonaProtocol, protocols: PersonaProtocol[]) {
78
+ protocols.filter((p) => p !== currentProtocol).forEach((p) => p.onTransaction(transaction));
79
+ }
@@ -0,0 +1,169 @@
1
+ import { PersonaProtocol, PersonaProtocolBaseConfig, PersonaConfig } from '../types';
2
+ import type { PersonaLogger } from '../logging';
3
+ import {
4
+ PersonaRESTProtocol,
5
+ PersonaRESTProtocolConfig,
6
+ PersonaTransactionProtocol,
7
+ PersonaWebRTCProtocol,
8
+ PersonaWebRTCProtocolConfig,
9
+ PersonaWebSocketProtocol,
10
+ PersonaWebSocketProtocolConfig,
11
+ } from '../protocol';
12
+
13
+ type ProtocolsConfig = PersonaConfig['protocols'];
14
+
15
+ /**
16
+ * Initializes protocols based on configuration
17
+ */
18
+ export function initializeProtocols(
19
+ _protocols: ProtocolsConfig,
20
+ config: {
21
+ dev: boolean;
22
+ baseUrl?: string;
23
+ apiKey: string;
24
+ agentId: string;
25
+ userId?: string;
26
+ tools?: PersonaConfig['tools'];
27
+ logger?: PersonaLogger;
28
+ },
29
+ ): PersonaProtocol[] {
30
+ if (Array.isArray(_protocols)) {
31
+ return _protocols;
32
+ }
33
+
34
+ if (typeof _protocols === 'object' && _protocols !== null) {
35
+ const baseEndpoint = config.dev ? 'localhost:8000' : config.baseUrl || 'persona.applica.guru/api';
36
+ const baseEndpointProtocol = config.dev ? 'http' : 'https';
37
+ const baseWebSocketProtocol = config.dev ? 'ws' : 'wss';
38
+
39
+ let availableProtocols = Object.keys(_protocols)
40
+ .map((key) => {
41
+ switch (key) {
42
+ case 'rest':
43
+ return createRESTProtocol(_protocols[key], {
44
+ baseEndpointProtocol,
45
+ baseEndpoint,
46
+ apiKey: config.apiKey,
47
+ agentId: config.agentId,
48
+ userId: config.userId,
49
+ logger: config.logger,
50
+ });
51
+
52
+ case 'webrtc':
53
+ return createWebRTCProtocol(_protocols[key], {
54
+ baseWebSocketProtocol,
55
+ baseEndpoint,
56
+ apiKey: config.apiKey,
57
+ agentId: config.agentId,
58
+ userId: config.userId,
59
+ logger: config.logger,
60
+ });
61
+
62
+ case 'websocket':
63
+ return createWebSocketProtocol(_protocols[key], {
64
+ baseWebSocketProtocol,
65
+ baseEndpoint,
66
+ apiKey: config.apiKey,
67
+ agentId: config.agentId,
68
+ userId: config.userId,
69
+ logger: config.logger,
70
+ });
71
+
72
+ default:
73
+ throw new Error(`Unknown protocol: ${key}`);
74
+ }
75
+ })
76
+ .filter((protocol) => protocol !== null) as PersonaProtocol[];
77
+
78
+ if (config.tools) {
79
+ availableProtocols.push(
80
+ new PersonaTransactionProtocol({
81
+ apiUrl: `${baseEndpointProtocol}://${baseEndpoint}`,
82
+ apiKey: config.apiKey,
83
+ agentId: config.agentId,
84
+ tools: config.tools,
85
+ logger: config.logger,
86
+ }),
87
+ );
88
+ }
89
+
90
+ return availableProtocols;
91
+ }
92
+
93
+ throw new Error('Invalid protocols configuration');
94
+ }
95
+
96
+ function createRESTProtocol(
97
+ config: PersonaProtocolBaseConfig | boolean | undefined,
98
+ baseConfig: {
99
+ baseEndpointProtocol: string;
100
+ baseEndpoint: string;
101
+ apiKey: string;
102
+ agentId: string;
103
+ userId?: string;
104
+ logger?: PersonaLogger;
105
+ },
106
+ ): PersonaRESTProtocol | null {
107
+ if (config === true) {
108
+ return new PersonaRESTProtocol({
109
+ apiUrl: `${baseConfig.baseEndpointProtocol}://${baseConfig.baseEndpoint}`,
110
+ apiKey: baseConfig.apiKey,
111
+ agentId: baseConfig.agentId,
112
+ userId: baseConfig.userId,
113
+ logger: baseConfig.logger,
114
+ });
115
+ } else if (typeof config === 'object' && config !== null) {
116
+ return new PersonaRESTProtocol(config as PersonaRESTProtocolConfig);
117
+ }
118
+ return null;
119
+ }
120
+
121
+ function createWebRTCProtocol(
122
+ config: PersonaProtocolBaseConfig | boolean | undefined,
123
+ baseConfig: {
124
+ baseWebSocketProtocol: string;
125
+ baseEndpoint: string;
126
+ apiKey: string;
127
+ agentId: string;
128
+ userId?: string;
129
+ logger?: PersonaLogger;
130
+ },
131
+ ): PersonaWebRTCProtocol | null {
132
+ if (config === true) {
133
+ return new PersonaWebRTCProtocol({
134
+ webrtcUrl: `${baseConfig.baseWebSocketProtocol}://${baseConfig.baseEndpoint}/webrtc`,
135
+ apiKey: baseConfig.apiKey,
136
+ agentId: baseConfig.agentId,
137
+ userId: baseConfig.userId,
138
+ logger: baseConfig.logger,
139
+ });
140
+ } else if (typeof config === 'object' && config !== null) {
141
+ return new PersonaWebRTCProtocol(config as PersonaWebRTCProtocolConfig);
142
+ }
143
+ return null;
144
+ }
145
+
146
+ function createWebSocketProtocol(
147
+ config: PersonaProtocolBaseConfig | boolean | undefined,
148
+ baseConfig: {
149
+ baseWebSocketProtocol: string;
150
+ baseEndpoint: string;
151
+ apiKey: string;
152
+ agentId: string;
153
+ userId?: string;
154
+ logger?: PersonaLogger;
155
+ },
156
+ ): PersonaWebSocketProtocol | null {
157
+ if (config === true) {
158
+ return new PersonaWebSocketProtocol({
159
+ webSocketUrl: `${baseConfig.baseWebSocketProtocol}://${baseConfig.baseEndpoint}/websocket`,
160
+ apiKey: baseConfig.apiKey,
161
+ agentId: baseConfig.agentId,
162
+ userId: baseConfig.userId,
163
+ logger: baseConfig.logger,
164
+ });
165
+ } else if (typeof config === 'object' && config !== null) {
166
+ return new PersonaWebSocketProtocol(config as PersonaWebSocketProtocolConfig);
167
+ }
168
+ return null;
169
+ }
@@ -0,0 +1,105 @@
1
+ import { ThreadData } from '../types';
2
+ import type { SessionStorage } from '../storage';
3
+
4
+ const DEFAULT_THREAD_ID = 'DEFAULT_THREAD_ID';
5
+ /**
6
+ * Generates a new session ID
7
+ */
8
+ export function generateSessionId(): string {
9
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
10
+ let sid = '';
11
+ for (let i = 0; i < 8; i++) {
12
+ sid += chars.charAt(Math.floor(Math.random() * chars.length));
13
+ }
14
+ return sid;
15
+ }
16
+
17
+ /**
18
+ * Creates the thread list adapter for multi-thread support
19
+ */
20
+ export function createThreadListAdapter(
21
+ currentThreadId: string,
22
+ threadList: ThreadData[],
23
+ setCurrentThreadId: (id: string) => void,
24
+ setThreadList: React.Dispatch<React.SetStateAction<ThreadData[]>>,
25
+ storage: SessionStorage,
26
+ onThreadCreate?: (threadId: string) => void,
27
+ onThreadSwitch?: (threadId: string) => void,
28
+ onThreadArchive?: (threadId: string) => void,
29
+ onThreadUnarchive?: (threadId: string) => void,
30
+ onThreadDelete?: (threadId: string) => void,
31
+ ) {
32
+ return {
33
+ threadId: currentThreadId,
34
+ threads: threadList
35
+ .filter((t) => t.status === 'regular')
36
+ .map((t) => ({
37
+ id: t.threadId,
38
+ threadId: t.threadId,
39
+ title: t.title || `Session ${t.threadId}`,
40
+ status: t.status as 'regular',
41
+ })),
42
+ archivedThreads: threadList
43
+ .filter((t) => t.status === 'archived')
44
+ .map((t) => ({
45
+ id: t.threadId,
46
+ threadId: t.threadId,
47
+ title: t.title || `Archived Chat ${t.threadId}`,
48
+ status: t.status as 'archived',
49
+ })),
50
+
51
+ onSwitchToNewThread: () => {
52
+ const newId = generateSessionId();
53
+ const newThread = {
54
+ threadId: newId,
55
+ status: 'regular' as const,
56
+ title: `Session ${newId}`,
57
+ };
58
+
59
+ setThreadList((prev) => [newThread, ...prev]);
60
+ setCurrentThreadId(newId);
61
+ onThreadCreate?.(newId);
62
+ },
63
+
64
+ onSwitchToThread: (threadId: string) => {
65
+ setCurrentThreadId(threadId);
66
+ onThreadSwitch?.(threadId);
67
+ },
68
+
69
+ onArchive: (threadId: string) => {
70
+ setThreadList((p) => p.map((t) => (t.threadId === threadId ? { ...t, status: 'archived' as const } : t)));
71
+ onThreadArchive?.(threadId);
72
+ if (threadId === DEFAULT_THREAD_ID) {
73
+ return;
74
+ }
75
+ storage.archive(threadId);
76
+ },
77
+
78
+ onUnarchive: (threadId: string) => {
79
+ setThreadList((prev) => prev.map((t) => (t.threadId === threadId ? { ...t, status: 'regular' as const } : t)));
80
+ onThreadUnarchive?.(threadId);
81
+ },
82
+
83
+ onDelete: async (threadId: string) => {
84
+ setThreadList((prev) => prev.filter((t) => t.threadId !== threadId));
85
+ if (currentThreadId === threadId) {
86
+ const remaining = threadList.filter((t) => t.threadId !== threadId && t.status === 'regular');
87
+ if (remaining.length > 0) {
88
+ setCurrentThreadId(remaining[0].threadId);
89
+ }
90
+ }
91
+ onThreadDelete?.(threadId);
92
+ if (threadId === DEFAULT_THREAD_ID) {
93
+ return;
94
+ }
95
+ await storage.delete(threadId);
96
+ },
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Generates a default thread ID
102
+ */
103
+ export function generateDefaultThreadId(): string {
104
+ return `thread-${Date.now()}`;
105
+ }