@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.
- package/.eslintrc.cjs +11 -0
- package/.github/copilot-instructions.md +3 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/CLAUDE.md +3 -0
- package/GEMINI.md +3 -0
- package/README.md +33 -0
- package/bitbucket-pipelines.yml +19 -0
- package/components.json +24 -0
- package/dist/bundle.cjs.js +28 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/bundle.es.js +5173 -0
- package/dist/bundle.es.js.map +1 -0
- package/dist/bundle.iife.js +28 -0
- package/dist/bundle.iife.js.map +1 -0
- package/dist/bundle.umd.js +28 -0
- package/dist/bundle.umd.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/logging.d.ts +18 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/messages.d.ts +9 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/projects.d.ts +17 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/protocol/base.d.ts +25 -0
- package/dist/protocol/base.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +6 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/rest.d.ts +25 -0
- package/dist/protocol/rest.d.ts.map +1 -0
- package/dist/protocol/transaction.d.ts +56 -0
- package/dist/protocol/transaction.d.ts.map +1 -0
- package/dist/protocol/webrtc.d.ts +60 -0
- package/dist/protocol/webrtc.d.ts.map +1 -0
- package/dist/protocol/websocket.d.ts +22 -0
- package/dist/protocol/websocket.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/file-attachment-adapter.d.ts +15 -0
- package/dist/runtime/file-attachment-adapter.d.ts.map +1 -0
- package/dist/runtime/handlers.d.ts +21 -0
- package/dist/runtime/handlers.d.ts.map +1 -0
- package/dist/runtime/listeners.d.ts +6 -0
- package/dist/runtime/listeners.d.ts.map +1 -0
- package/dist/runtime/protocols.d.ts +17 -0
- package/dist/runtime/protocols.d.ts.map +1 -0
- package/dist/runtime/threads.d.ts +34 -0
- package/dist/runtime/threads.d.ts.map +1 -0
- package/dist/runtime/utils.d.ts +10 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/storage/base.d.ts +9 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/persona.d.ts +29 -0
- package/dist/storage/persona.d.ts.map +1 -0
- package/dist/tools.d.ts +72 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/types.d.ts +237 -0
- package/dist/types.d.ts.map +1 -0
- package/docs/README.md +13 -0
- package/docs/api-reference.md +16 -0
- package/docs/contributing.md +21 -0
- package/docs/customization.md +74 -0
- package/docs/features.md +7 -0
- package/docs/installation.md +9 -0
- package/docs/messages.md +214 -0
- package/docs/protocols.md +39 -0
- package/docs/transactions.md +121 -0
- package/docs/usage.md +40 -0
- package/jsconfig.node.json +10 -0
- package/package.json +82 -0
- package/playground/index.html +14 -0
- package/playground/src/app.tsx +10 -0
- package/playground/src/chat.tsx +117 -0
- package/playground/src/components/assistant-ui/assistant-modal.tsx +57 -0
- package/playground/src/components/assistant-ui/attachment.tsx +197 -0
- package/playground/src/components/assistant-ui/markdown-text.tsx +228 -0
- package/playground/src/components/assistant-ui/thread-list.tsx +91 -0
- package/playground/src/components/assistant-ui/thread.tsx +302 -0
- package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
- package/playground/src/components/assistant-ui/tool-fallback.tsx +93 -0
- package/playground/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
- package/playground/src/components/chat/logging.tsx +53 -0
- package/playground/src/components/ui/avatar.tsx +51 -0
- package/playground/src/components/ui/button.tsx +60 -0
- package/playground/src/components/ui/dialog.tsx +141 -0
- package/playground/src/components/ui/input.tsx +21 -0
- package/playground/src/components/ui/separator.tsx +26 -0
- package/playground/src/components/ui/sheet.tsx +139 -0
- package/playground/src/components/ui/sidebar.tsx +619 -0
- package/playground/src/components/ui/skeleton.tsx +13 -0
- package/playground/src/components/ui/tooltip.tsx +59 -0
- package/playground/src/hooks/theme.ts +70 -0
- package/playground/src/hooks/use-mobile.ts +19 -0
- package/playground/src/lib/utils.ts +6 -0
- package/playground/src/main.tsx +10 -0
- package/playground/src/styles.css +120 -0
- package/playground/src/tools.ts +149 -0
- package/playground/src/vite-env.d.ts +1 -0
- package/preview-build.sh +23 -0
- package/src/index.ts +7 -0
- package/src/logging.ts +34 -0
- package/src/messages.ts +202 -0
- package/src/projects.ts +57 -0
- package/src/protocol/base.ts +73 -0
- package/src/protocol/index.ts +5 -0
- package/src/protocol/rest.ts +107 -0
- package/src/protocol/transaction.ts +182 -0
- package/src/protocol/webrtc.ts +379 -0
- package/src/protocol/websocket.ts +111 -0
- package/src/runtime/context.ts +88 -0
- package/src/runtime/file-attachment-adapter.ts +48 -0
- package/src/runtime/handlers.ts +322 -0
- package/src/runtime/index.ts +6 -0
- package/src/runtime/listeners.ts +79 -0
- package/src/runtime/protocols.ts +169 -0
- package/src/runtime/threads.ts +105 -0
- package/src/runtime/utils.ts +46 -0
- package/src/runtime.tsx +334 -0
- package/src/storage/base.ts +13 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/persona.ts +138 -0
- package/src/tools.ts +211 -0
- package/src/types.ts +284 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +15 -0
- 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,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
|
+
}
|