@runtypelabs/persona 1.36.0

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 (61) hide show
  1. package/README.md +1080 -0
  2. package/dist/index.cjs +140 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2626 -0
  5. package/dist/index.d.ts +2626 -0
  6. package/dist/index.global.js +1843 -0
  7. package/dist/index.global.js.map +1 -0
  8. package/dist/index.js +140 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/install.global.js +2 -0
  11. package/dist/install.global.js.map +1 -0
  12. package/dist/widget.css +1627 -0
  13. package/package.json +79 -0
  14. package/src/@types/idiomorph.d.ts +37 -0
  15. package/src/client.test.ts +387 -0
  16. package/src/client.ts +1589 -0
  17. package/src/components/composer-builder.ts +530 -0
  18. package/src/components/feedback.ts +379 -0
  19. package/src/components/forms.ts +170 -0
  20. package/src/components/header-builder.ts +455 -0
  21. package/src/components/header-layouts.ts +303 -0
  22. package/src/components/launcher.ts +193 -0
  23. package/src/components/message-bubble.ts +528 -0
  24. package/src/components/messages.ts +54 -0
  25. package/src/components/panel.ts +204 -0
  26. package/src/components/reasoning-bubble.ts +144 -0
  27. package/src/components/registry.ts +87 -0
  28. package/src/components/suggestions.ts +97 -0
  29. package/src/components/tool-bubble.ts +288 -0
  30. package/src/defaults.ts +321 -0
  31. package/src/index.ts +175 -0
  32. package/src/install.ts +284 -0
  33. package/src/plugins/registry.ts +77 -0
  34. package/src/plugins/types.ts +95 -0
  35. package/src/postprocessors.ts +194 -0
  36. package/src/runtime/init.ts +162 -0
  37. package/src/session.ts +376 -0
  38. package/src/styles/tailwind.css +20 -0
  39. package/src/styles/widget.css +1627 -0
  40. package/src/types.ts +1635 -0
  41. package/src/ui.ts +3341 -0
  42. package/src/utils/actions.ts +227 -0
  43. package/src/utils/attachment-manager.ts +384 -0
  44. package/src/utils/code-generators.test.ts +500 -0
  45. package/src/utils/code-generators.ts +1806 -0
  46. package/src/utils/component-middleware.ts +137 -0
  47. package/src/utils/component-parser.ts +119 -0
  48. package/src/utils/constants.ts +16 -0
  49. package/src/utils/content.ts +306 -0
  50. package/src/utils/dom.ts +25 -0
  51. package/src/utils/events.ts +41 -0
  52. package/src/utils/formatting.test.ts +166 -0
  53. package/src/utils/formatting.ts +470 -0
  54. package/src/utils/icons.ts +92 -0
  55. package/src/utils/message-id.ts +37 -0
  56. package/src/utils/morph.ts +36 -0
  57. package/src/utils/positioning.ts +17 -0
  58. package/src/utils/storage.ts +72 -0
  59. package/src/utils/theme.ts +105 -0
  60. package/src/widget.css +1 -0
  61. package/widget.css +1 -0
@@ -0,0 +1,500 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateCodeSnippet, type CodeFormat, type CodeGeneratorHooks, type CodeGeneratorOptions } from "./code-generators";
3
+
4
+ // =============================================================================
5
+ // Test Fixtures
6
+ // =============================================================================
7
+
8
+ const minimalConfig = {
9
+ apiUrl: "https://api.example.com/chat",
10
+ };
11
+
12
+ const fullConfig = {
13
+ apiUrl: "https://api.example.com/chat",
14
+ flowId: "test-flow-123",
15
+ theme: {
16
+ primaryColor: "#007bff",
17
+ fontFamily: "Inter, sans-serif",
18
+ },
19
+ messageActions: {
20
+ enableCopy: true,
21
+ enableFeedback: true,
22
+ feedbackType: "thumbs",
23
+ },
24
+ };
25
+
26
+ // =============================================================================
27
+ // Hook Serialization Tests
28
+ // =============================================================================
29
+
30
+ describe("Hook Serialization", () => {
31
+ describe("string hooks", () => {
32
+ it("should pass string hooks through unchanged in ESM format", () => {
33
+ const hooks: CodeGeneratorHooks = {
34
+ getHeaders: "async () => ({ 'Authorization': 'Bearer token' })",
35
+ };
36
+
37
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks });
38
+
39
+ expect(code).toContain("getHeaders: async () => ({ 'Authorization': 'Bearer token' })");
40
+ });
41
+
42
+ it("should pass string hooks through unchanged in React format", () => {
43
+ const hooks: CodeGeneratorHooks = {
44
+ getHeaders: "async () => ({ 'X-Custom': 'value' })",
45
+ };
46
+
47
+ const code = generateCodeSnippet(minimalConfig, "react-component", { hooks });
48
+
49
+ expect(code).toContain("getHeaders: async () => ({ 'X-Custom': 'value' })");
50
+ });
51
+ });
52
+
53
+ describe("function hooks", () => {
54
+ it("should serialize single function to string", () => {
55
+ const hooks: CodeGeneratorHooks = {
56
+ getHeaders: async () => ({ 'Authorization': 'Bearer token' }),
57
+ };
58
+
59
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks });
60
+
61
+ // Function should be serialized
62
+ expect(code).toContain("getHeaders:");
63
+ expect(code).toContain("Authorization");
64
+ });
65
+
66
+ it("should serialize arrow function with body", () => {
67
+ const hooks: CodeGeneratorHooks = {
68
+ onFeedback: (feedback) => {
69
+ console.log('Feedback received:', feedback);
70
+ },
71
+ };
72
+
73
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks });
74
+
75
+ expect(code).toContain("onFeedback:");
76
+ expect(code).toContain("console.log");
77
+ });
78
+
79
+ it("should serialize array of functions", () => {
80
+ const hooks: CodeGeneratorHooks = {
81
+ actionHandlers: [
82
+ (action: any, ctx: any) => {
83
+ if (action.type === 'custom') {
84
+ return { handled: true };
85
+ }
86
+ },
87
+ (action: any, ctx: any) => {
88
+ if (action.type === 'another') {
89
+ return { handled: true, displayText: 'Done' };
90
+ }
91
+ },
92
+ ],
93
+ };
94
+
95
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks });
96
+
97
+ // Should contain the actionHandlers array with serialized functions
98
+ expect(code).toContain("actionHandlers:");
99
+ // When functions are serialized, they contain the function body
100
+ expect(code).toContain("custom");
101
+ expect(code).toContain("another");
102
+ });
103
+ });
104
+ });
105
+
106
+ // =============================================================================
107
+ // Format-Specific Hook Injection Tests
108
+ // =============================================================================
109
+
110
+ describe("ESM Format Hooks", () => {
111
+ it("should inject getHeaders hook", () => {
112
+ const code = generateCodeSnippet(minimalConfig, "esm", {
113
+ hooks: {
114
+ getHeaders: "async () => ({ 'X-API-Key': 'secret123' })",
115
+ },
116
+ });
117
+
118
+ expect(code).toContain("getHeaders: async () => ({ 'X-API-Key': 'secret123' })");
119
+ });
120
+
121
+ it("should inject postprocessMessage hook", () => {
122
+ const code = generateCodeSnippet(minimalConfig, "esm", {
123
+ hooks: {
124
+ postprocessMessage: "({ text }) => text.toUpperCase()",
125
+ },
126
+ });
127
+
128
+ expect(code).toContain("postprocessMessage: ({ text }) => text.toUpperCase()");
129
+ });
130
+
131
+ it("should inject requestMiddleware hook", () => {
132
+ const code = generateCodeSnippet(minimalConfig, "esm", {
133
+ hooks: {
134
+ requestMiddleware: "({ payload }) => ({ ...payload, timestamp: Date.now() })",
135
+ },
136
+ });
137
+
138
+ expect(code).toContain("requestMiddleware: ({ payload }) => ({ ...payload, timestamp: Date.now() })");
139
+ });
140
+
141
+ it("should inject streamParser hook", () => {
142
+ const code = generateCodeSnippet(minimalConfig, "esm", {
143
+ hooks: {
144
+ streamParser: "() => createCustomParser()",
145
+ },
146
+ });
147
+
148
+ expect(code).toContain("streamParser: () => createCustomParser()");
149
+ });
150
+
151
+ it("should inject multiple hooks simultaneously", () => {
152
+ const code = generateCodeSnippet(minimalConfig, "esm", {
153
+ hooks: {
154
+ getHeaders: "async () => ({ 'Auth': 'token' })",
155
+ postprocessMessage: "({ text }) => text",
156
+ requestMiddleware: "({ payload }) => payload",
157
+ },
158
+ });
159
+
160
+ expect(code).toContain("getHeaders: async () => ({ 'Auth': 'token' })");
161
+ expect(code).toContain("postprocessMessage: ({ text }) => text");
162
+ expect(code).toContain("requestMiddleware: ({ payload }) => payload");
163
+ });
164
+ });
165
+
166
+ describe("React Component Format Hooks", () => {
167
+ it("should inject hooks in React component format", () => {
168
+ const code = generateCodeSnippet(minimalConfig, "react-component", {
169
+ hooks: {
170
+ getHeaders: "async () => ({ 'Authorization': 'Bearer xyz' })",
171
+ },
172
+ });
173
+
174
+ expect(code).toContain("getHeaders: async () => ({ 'Authorization': 'Bearer xyz' })");
175
+ expect(code).toContain("import");
176
+ expect(code).toContain("useEffect");
177
+ });
178
+
179
+ it("should inject onFeedback and onCopy hooks in messageActions", () => {
180
+ const code = generateCodeSnippet(fullConfig, "react-component", {
181
+ hooks: {
182
+ onFeedback: "(feedback) => console.log('feedback', feedback)",
183
+ onCopy: "(msg) => console.log('copied', msg)",
184
+ },
185
+ });
186
+
187
+ expect(code).toContain("onFeedback: (feedback) => console.log('feedback', feedback)");
188
+ expect(code).toContain("onCopy: (msg) => console.log('copied', msg)");
189
+ });
190
+ });
191
+
192
+ describe("React Advanced Format Hooks", () => {
193
+ it("should inject custom action handlers alongside defaults", () => {
194
+ const code = generateCodeSnippet(minimalConfig, "react-advanced", {
195
+ hooks: {
196
+ actionHandlers: `[(action, ctx) => {
197
+ if (action.type === 'my_action') return { handled: true };
198
+ }]`,
199
+ },
200
+ });
201
+
202
+ // Should contain both custom handler and default nav_then_click handler
203
+ expect(code).toContain("my_action");
204
+ expect(code).toContain("nav_then_click");
205
+ });
206
+
207
+ it("should inject custom action parsers alongside defaults", () => {
208
+ const code = generateCodeSnippet(minimalConfig, "react-advanced", {
209
+ hooks: {
210
+ actionParsers: `[(ctx) => {
211
+ if (ctx.text.includes('SPECIAL')) return { type: 'special' };
212
+ }]`,
213
+ },
214
+ });
215
+
216
+ expect(code).toContain("SPECIAL");
217
+ });
218
+
219
+ it("should merge requestMiddleware with DOM context collection", () => {
220
+ const code = generateCodeSnippet(minimalConfig, "react-advanced", {
221
+ hooks: {
222
+ requestMiddleware: "({ payload }) => ({ ...payload, extra: 'data' })",
223
+ },
224
+ });
225
+
226
+ // Should contain both custom middleware and DOM context collection
227
+ expect(code).toContain("extra: 'data'");
228
+ // TypeScript version uses collectDOMContext()
229
+ expect(code).toContain("collectDOMContext");
230
+ });
231
+
232
+ it("should inject context providers", () => {
233
+ const code = generateCodeSnippet(minimalConfig, "react-advanced", {
234
+ hooks: {
235
+ contextProviders: `[() => ({ userAgent: navigator.userAgent })]`,
236
+ },
237
+ });
238
+
239
+ expect(code).toContain("userAgent: navigator.userAgent");
240
+ });
241
+ });
242
+
243
+ describe("Script Manual Format Hooks", () => {
244
+ it("should inject hooks in script-manual format", () => {
245
+ const code = generateCodeSnippet(minimalConfig, "script-manual", {
246
+ hooks: {
247
+ getHeaders: "async function() { return { 'X-Key': 'value' }; }",
248
+ },
249
+ });
250
+
251
+ expect(code).toContain("getHeaders: async function() { return { 'X-Key': 'value' }; }");
252
+ expect(code).toContain("<script");
253
+ });
254
+
255
+ it("should inject postprocessMessage hook", () => {
256
+ const code = generateCodeSnippet(minimalConfig, "script-manual", {
257
+ hooks: {
258
+ postprocessMessage: "function({ text }) { return text.trim(); }",
259
+ },
260
+ });
261
+
262
+ expect(code).toContain("postprocessMessage: function({ text }) { return text.trim(); }");
263
+ });
264
+ });
265
+
266
+ describe("Script Advanced Format Hooks", () => {
267
+ it("should inject custom action handlers alongside defaults", () => {
268
+ const code = generateCodeSnippet(minimalConfig, "script-advanced", {
269
+ hooks: {
270
+ actionHandlers: `[function(action, ctx) {
271
+ if (action.type === 'custom_action') return { handled: true };
272
+ }]`,
273
+ },
274
+ });
275
+
276
+ // Should contain both custom handler and default nav_then_click handler
277
+ expect(code).toContain("custom_action");
278
+ expect(code).toContain("nav_then_click");
279
+ });
280
+
281
+ it("should merge requestMiddleware with DOM context collection", () => {
282
+ const code = generateCodeSnippet(minimalConfig, "script-advanced", {
283
+ hooks: {
284
+ requestMiddleware: "function(ctx) { ctx.payload.custom = true; return ctx.payload; }",
285
+ },
286
+ });
287
+
288
+ // Should contain both custom middleware and DOM context collection
289
+ expect(code).toContain("custom = true");
290
+ // ES5 version uses domContextProvider()
291
+ expect(code).toContain("domContextProvider");
292
+ });
293
+ });
294
+
295
+ describe("Script Installer Format", () => {
296
+ it("should not inject hooks (JSON-only config)", () => {
297
+ const code = generateCodeSnippet(minimalConfig, "script-installer", {
298
+ hooks: {
299
+ getHeaders: "async () => ({ 'Auth': 'token' })",
300
+ },
301
+ });
302
+
303
+ // Script installer uses JSON config, should not have function hooks
304
+ expect(code).not.toContain("getHeaders:");
305
+ expect(code).toContain("data-config");
306
+ });
307
+ });
308
+
309
+ // =============================================================================
310
+ // Client Token Emission Tests
311
+ // =============================================================================
312
+
313
+ describe("Client Token Config", () => {
314
+ const clientTokenConfig = {
315
+ ...minimalConfig,
316
+ clientToken: "ct_test_123",
317
+ };
318
+
319
+ it("should include clientToken in ESM format", () => {
320
+ const code = generateCodeSnippet(clientTokenConfig, "esm");
321
+ expect(code).toContain('clientToken: "ct_test_123"');
322
+ });
323
+
324
+ it("should include clientToken in React component format", () => {
325
+ const code = generateCodeSnippet(clientTokenConfig, "react-component");
326
+ expect(code).toContain('clientToken: "ct_test_123"');
327
+ });
328
+
329
+ it("should include clientToken in React advanced format", () => {
330
+ const code = generateCodeSnippet(clientTokenConfig, "react-advanced");
331
+ expect(code).toContain('clientToken: "ct_test_123"');
332
+ });
333
+
334
+ it("should include clientToken in script-manual format", () => {
335
+ const code = generateCodeSnippet(clientTokenConfig, "script-manual");
336
+ expect(code).toContain('clientToken: "ct_test_123"');
337
+ });
338
+
339
+ it("should include clientToken in script-advanced format (CONFIG JSON)", () => {
340
+ const code = generateCodeSnippet(clientTokenConfig, "script-advanced");
341
+ expect(code).toContain('"clientToken": "ct_test_123"');
342
+ });
343
+
344
+ it("should include clientToken in script-installer format (data-config JSON)", () => {
345
+ const code = generateCodeSnippet(clientTokenConfig, "script-installer");
346
+ expect(code).toContain('"clientToken":"ct_test_123"');
347
+ });
348
+ });
349
+
350
+ // =============================================================================
351
+ // Edge Cases and Error Handling
352
+ // =============================================================================
353
+
354
+ describe("Edge Cases", () => {
355
+ it("should handle empty hooks object", () => {
356
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks: {} });
357
+
358
+ // Should generate valid code without any hooks
359
+ expect(code).toContain("initAgentWidget");
360
+ expect(code).not.toContain("getHeaders:");
361
+ });
362
+
363
+ it("should handle undefined hooks", () => {
364
+ const code = generateCodeSnippet(minimalConfig, "esm", { hooks: undefined });
365
+
366
+ expect(code).toContain("initAgentWidget");
367
+ });
368
+
369
+ it("should handle options without hooks", () => {
370
+ const code = generateCodeSnippet(minimalConfig, "esm", { includeHookComments: false });
371
+
372
+ expect(code).toContain("initAgentWidget");
373
+ });
374
+
375
+ it("should handle all formats without errors", () => {
376
+ const formats: CodeFormat[] = [
377
+ "esm",
378
+ "script-installer",
379
+ "script-manual",
380
+ "script-advanced",
381
+ "react-component",
382
+ "react-advanced",
383
+ ];
384
+
385
+ const hooks: CodeGeneratorHooks = {
386
+ getHeaders: "async () => ({})",
387
+ };
388
+
389
+ for (const format of formats) {
390
+ expect(() => generateCodeSnippet(minimalConfig, format, { hooks })).not.toThrow();
391
+ }
392
+ });
393
+
394
+ it("should preserve special characters in hook strings", () => {
395
+ const code = generateCodeSnippet(minimalConfig, "esm", {
396
+ hooks: {
397
+ getHeaders: "async () => ({ 'Content-Type': 'application/json; charset=utf-8' })",
398
+ },
399
+ });
400
+
401
+ expect(code).toContain("application/json; charset=utf-8");
402
+ });
403
+
404
+ it("should handle multiline hook strings", () => {
405
+ const code = generateCodeSnippet(minimalConfig, "esm", {
406
+ hooks: {
407
+ onFeedback: `(feedback) => {
408
+ const { type, messageId } = feedback;
409
+ fetch('/api/feedback', {
410
+ method: 'POST',
411
+ body: JSON.stringify({ type, messageId })
412
+ });
413
+ }`,
414
+ },
415
+ });
416
+
417
+ expect(code).toContain("fetch('/api/feedback'");
418
+ expect(code).toContain("JSON.stringify");
419
+ });
420
+ });
421
+
422
+ // =============================================================================
423
+ // Integration Tests
424
+ // =============================================================================
425
+
426
+ describe("Full Config with Hooks Integration", () => {
427
+ it("should generate complete ESM code with config and hooks", () => {
428
+ const code = generateCodeSnippet(fullConfig, "esm", {
429
+ hooks: {
430
+ getHeaders: "async () => ({ 'Authorization': `Bearer ${localStorage.getItem('token')}` })",
431
+ onFeedback: "(feedback) => analytics.track('feedback', feedback)",
432
+ postprocessMessage: "({ text }) => DOMPurify.sanitize(text)",
433
+ },
434
+ });
435
+
436
+ // Config properties that are included in generated code
437
+ expect(code).toContain("#007bff");
438
+ expect(code).toContain("Inter, sans-serif");
439
+
440
+ // Hooks
441
+ expect(code).toContain("localStorage.getItem('token')");
442
+ expect(code).toContain("analytics.track");
443
+ expect(code).toContain("DOMPurify.sanitize");
444
+ });
445
+
446
+ it("should generate complete React Advanced code with all hook types", () => {
447
+ const code = generateCodeSnippet(fullConfig, "react-advanced", {
448
+ hooks: {
449
+ getHeaders: "async () => ({ 'X-Session': sessionStorage.getItem('session') })",
450
+ onFeedback: "(f) => console.log(f)",
451
+ onCopy: "(m) => navigator.clipboard.writeText(m.content)",
452
+ requestMiddleware: "({ payload }) => ({ ...payload, version: '1.0' })",
453
+ actionHandlers: `[(action) => {
454
+ if (action.type === 'redirect') {
455
+ window.location.href = action.payload.url;
456
+ return { handled: true };
457
+ }
458
+ }]`,
459
+ postprocessMessage: "({ text }) => text.replace(/\\n/g, '<br>')",
460
+ },
461
+ });
462
+
463
+ // Should contain all hooks
464
+ expect(code).toContain("X-Session");
465
+ expect(code).toContain("console.log(f)");
466
+ expect(code).toContain("navigator.clipboard");
467
+ expect(code).toContain("version: '1.0'");
468
+ expect(code).toContain("action.type === 'redirect'");
469
+ expect(code).toContain(".replace(/\\n/g");
470
+ });
471
+ });
472
+
473
+ // =============================================================================
474
+ // Backward Compatibility Tests
475
+ // =============================================================================
476
+
477
+ describe("Backward Compatibility", () => {
478
+ it("should work without options parameter", () => {
479
+ const code = generateCodeSnippet(minimalConfig, "esm");
480
+
481
+ expect(code).toContain("initAgentWidget");
482
+ expect(code).toContain(minimalConfig.apiUrl);
483
+ });
484
+
485
+ it("should work with only format parameter", () => {
486
+ const formats: CodeFormat[] = ["esm", "react-component", "script-manual"];
487
+
488
+ for (const format of formats) {
489
+ const code = generateCodeSnippet(minimalConfig, format);
490
+ expect(code.length).toBeGreaterThan(0);
491
+ }
492
+ });
493
+
494
+ it("should default to esm format when not specified", () => {
495
+ const code = generateCodeSnippet(minimalConfig);
496
+
497
+ expect(code).toContain("import");
498
+ expect(code).toContain("from");
499
+ });
500
+ });