@runtypelabs/persona 3.27.0 → 3.29.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.
- package/dist/codegen.cjs +80 -0
- package/dist/codegen.d.cts +152 -0
- package/dist/codegen.d.ts +152 -0
- package/dist/codegen.js +80 -0
- package/dist/index.cjs +35 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.global.js +20 -20
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +38 -38
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/src/codegen.test.ts +82 -0
- package/src/codegen.ts +20 -0
- package/src/utils/code-generators.ts +38 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"import": "./dist/theme-reference.js",
|
|
18
18
|
"require": "./dist/theme-reference.cjs"
|
|
19
19
|
},
|
|
20
|
+
"./codegen": {
|
|
21
|
+
"types": "./dist/codegen.d.ts",
|
|
22
|
+
"import": "./dist/codegen.js",
|
|
23
|
+
"require": "./dist/codegen.cjs"
|
|
24
|
+
},
|
|
20
25
|
"./theme-editor": {
|
|
21
26
|
"types": "./dist/theme-editor.d.ts",
|
|
22
27
|
"import": "./dist/theme-editor.js",
|
|
@@ -99,12 +104,13 @@
|
|
|
99
104
|
"access": "public"
|
|
100
105
|
},
|
|
101
106
|
"scripts": {
|
|
102
|
-
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:theme-ref && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:animations",
|
|
107
|
+
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:animations",
|
|
103
108
|
"build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
104
109
|
"build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
105
110
|
"build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
106
111
|
"build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --minify --dts --out-dir dist/animations --no-splitting",
|
|
107
112
|
"build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
|
|
113
|
+
"build:codegen": "tsup src/codegen.ts --format esm,cjs --minify --dts",
|
|
108
114
|
"build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
|
|
109
115
|
"build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader \".css=text\" && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
|
|
110
116
|
"build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generateCodeSnippet as fromSubpath } from "./codegen";
|
|
3
|
+
import { generateCodeSnippet as fromInternal } from "./utils/code-generators";
|
|
4
|
+
import { VERSION } from "./version";
|
|
5
|
+
|
|
6
|
+
// The `@runtypelabs/persona/codegen` subpath is the server/Worker-safe entry for
|
|
7
|
+
// snippet generation. It must expose the exact same generator as the internal
|
|
8
|
+
// module (and, transitively, the main barrel) — no fork, no drift.
|
|
9
|
+
describe("codegen subpath", () => {
|
|
10
|
+
const config = {
|
|
11
|
+
apiUrl: "https://api.example.com/chat",
|
|
12
|
+
clientToken: "ct_test_123",
|
|
13
|
+
launcher: { enabled: true, title: "Chat" },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it("re-exports the same generateCodeSnippet implementation", () => {
|
|
17
|
+
expect(fromSubpath).toBe(fromInternal);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("produces identical output via the subpath for every format", () => {
|
|
21
|
+
const formats = [
|
|
22
|
+
"esm",
|
|
23
|
+
"script-installer",
|
|
24
|
+
"script-manual",
|
|
25
|
+
"script-advanced",
|
|
26
|
+
"react-component",
|
|
27
|
+
"react-advanced",
|
|
28
|
+
] as const;
|
|
29
|
+
for (const format of formats) {
|
|
30
|
+
expect(fromSubpath(config, format)).toBe(fromInternal(config, format));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("pins the installer CDN url to the package version (not @latest)", () => {
|
|
35
|
+
const code = fromSubpath(config, "script-installer");
|
|
36
|
+
expect(code).toContain(`@runtypelabs/persona@${VERSION}/dist/install.global.js`);
|
|
37
|
+
expect(code).not.toContain("@latest");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("mount target option", () => {
|
|
41
|
+
it("defaults to body when no target is given", () => {
|
|
42
|
+
expect(fromSubpath(config, "react-component")).toContain("target: 'body'");
|
|
43
|
+
expect(fromSubpath(config, "script-manual")).toContain("target: 'body'");
|
|
44
|
+
// installer omits the target key entirely (installer falls back to body)
|
|
45
|
+
expect(fromSubpath(config, "script-installer")).not.toContain('"target"');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("emits the selector as the initAgentWidget target for code formats", () => {
|
|
49
|
+
const opts = { target: "#chat" };
|
|
50
|
+
expect(fromSubpath(config, "esm", opts)).toContain("target: '#chat'");
|
|
51
|
+
expect(fromSubpath(config, "react-component", opts)).toContain("target: '#chat'");
|
|
52
|
+
expect(fromSubpath(config, "script-manual", opts)).toContain("target: '#chat'");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("serializes the selector as a top-level installer option (sibling of config)", () => {
|
|
56
|
+
// install.ts reads the mount point from the TOP LEVEL of data-config, not
|
|
57
|
+
// from inside the widget config. Parse the emitted data-config and assert
|
|
58
|
+
// the structure so a regression to the nested form is caught.
|
|
59
|
+
const code = fromSubpath(config, "script-installer", { target: "#chat" });
|
|
60
|
+
const json = code.match(/data-config='([^']*)'/)?.[1];
|
|
61
|
+
expect(json).toBeTruthy();
|
|
62
|
+
const parsed = JSON.parse(json!);
|
|
63
|
+
expect(parsed.target).toBe("#chat");
|
|
64
|
+
expect(parsed.config).toBeTruthy();
|
|
65
|
+
// target must NOT be buried inside the widget config (where it's ignored)
|
|
66
|
+
expect(parsed.config.target).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps target as a top-level sibling alongside windowKey", () => {
|
|
70
|
+
const code = fromSubpath(config, "script-installer", { target: "#chat", windowKey: "myWidget" });
|
|
71
|
+
const parsed = JSON.parse(code.match(/data-config='([^']*)'/)![1]);
|
|
72
|
+
expect(parsed.target).toBe("#chat");
|
|
73
|
+
expect(parsed.windowKey).toBe("myWidget");
|
|
74
|
+
expect(parsed.config).toBeTruthy();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("escapes single quotes in the selector so it can't break the literal", () => {
|
|
78
|
+
const code = fromSubpath(config, "react-component", { target: "[data-x='y']" });
|
|
79
|
+
expect(code).toContain("target: '[data-x=\\'y\\']'");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/codegen.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure code-snippet generation entry (`@runtypelabs/persona/codegen`).
|
|
3
|
+
*
|
|
4
|
+
* `generateCodeSnippet` is pure string-templating — it builds install snippets
|
|
5
|
+
* from a widget config and depends only on a type import + the `VERSION`
|
|
6
|
+
* constant. The main `index.ts` barrel re-exports it too, but that barrel pulls
|
|
7
|
+
* in the full widget runtime (`index-core` → idiomorph / marked / DOM), which is
|
|
8
|
+
* unacceptable for server/Worker consumers that only need snippet strings.
|
|
9
|
+
*
|
|
10
|
+
* This subpath exposes the generator on its own (mirroring `theme-reference`) so
|
|
11
|
+
* those consumers import it without dragging in the browser runtime. Existing
|
|
12
|
+
* npm consumers keep using the barrel export unchanged.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { generateCodeSnippet } from "./utils/code-generators";
|
|
16
|
+
export type {
|
|
17
|
+
CodeFormat,
|
|
18
|
+
CodeGeneratorHooks,
|
|
19
|
+
CodeGeneratorOptions
|
|
20
|
+
} from "./utils/code-generators";
|
|
@@ -141,6 +141,16 @@ export type CodeGeneratorOptions = {
|
|
|
141
141
|
* Only affects script formats (script-installer, script-manual, script-advanced).
|
|
142
142
|
*/
|
|
143
143
|
windowKey?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* CSS selector for the mount target. When omitted, the widget mounts on
|
|
147
|
+
* `body` (the existing default). When provided, the generated snippet mounts
|
|
148
|
+
* into that element: ESM / React / manual / advanced formats emit it as the
|
|
149
|
+
* `target` argument to `initAgentWidget()`, and `script-installer` serializes
|
|
150
|
+
* it into `data-config` (the installer reads `config.target`).
|
|
151
|
+
* @default "body"
|
|
152
|
+
*/
|
|
153
|
+
target?: string;
|
|
144
154
|
};
|
|
145
155
|
|
|
146
156
|
// Internal type for normalized hooks (always strings)
|
|
@@ -540,6 +550,15 @@ function appendSerializableObjectBlock(
|
|
|
540
550
|
lines.push(`${indent}},`);
|
|
541
551
|
}
|
|
542
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Resolve the mount-target selector for a single-quoted JS string context.
|
|
555
|
+
* Defaults to `body` and escapes backslashes / single quotes so an arbitrary
|
|
556
|
+
* selector can't break out of the emitted `target: '...'` literal.
|
|
557
|
+
*/
|
|
558
|
+
function emitTargetSelector(options?: CodeGeneratorOptions): string {
|
|
559
|
+
return (options?.target ?? "body").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
560
|
+
}
|
|
561
|
+
|
|
543
562
|
export function generateCodeSnippet(
|
|
544
563
|
config: any,
|
|
545
564
|
format: CodeFormat = "esm",
|
|
@@ -580,7 +599,7 @@ function generateESMCode(config: any, options?: CodeGeneratorOptions): string {
|
|
|
580
599
|
"import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';",
|
|
581
600
|
"",
|
|
582
601
|
"initAgentWidget({",
|
|
583
|
-
|
|
602
|
+
` target: '${emitTargetSelector(options)}',`,
|
|
584
603
|
" config: {"
|
|
585
604
|
];
|
|
586
605
|
|
|
@@ -726,7 +745,7 @@ function generateReactComponentCode(config: any, options?: CodeGeneratorOptions)
|
|
|
726
745
|
" let handle: AgentWidgetInitHandle | null = null;",
|
|
727
746
|
"",
|
|
728
747
|
" handle = initAgentWidget({",
|
|
729
|
-
|
|
748
|
+
` target: '${emitTargetSelector(options)}',`,
|
|
730
749
|
" config: {"
|
|
731
750
|
];
|
|
732
751
|
|
|
@@ -990,7 +1009,7 @@ function generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions):
|
|
|
990
1009
|
" };",
|
|
991
1010
|
"",
|
|
992
1011
|
" handle = initAgentWidget({",
|
|
993
|
-
|
|
1012
|
+
` target: '${emitTargetSelector(options)}',`,
|
|
994
1013
|
" config: {"
|
|
995
1014
|
];
|
|
996
1015
|
|
|
@@ -1356,10 +1375,20 @@ function buildSerializableConfig(config: any): Record<string, any> {
|
|
|
1356
1375
|
function generateScriptInstallerCode(config: any, options?: CodeGeneratorOptions): string {
|
|
1357
1376
|
const serializableConfig = buildSerializableConfig(config);
|
|
1358
1377
|
|
|
1359
|
-
//
|
|
1360
|
-
// install
|
|
1361
|
-
|
|
1362
|
-
|
|
1378
|
+
// `windowKey` and `target` are INSTALLER options, not widget-config fields.
|
|
1379
|
+
// install.ts reads them from the top level of data-config: the nested-`config`
|
|
1380
|
+
// branch hoists every sibling key (Object.assign(scriptConfig, parsed)), while
|
|
1381
|
+
// a flat payload becomes the widget config wholesale and its siblings are
|
|
1382
|
+
// ignored. So whenever either is set we emit the nested `{ config, ...}` form
|
|
1383
|
+
// with the option as a SIBLING of `config` — nesting `target` inside the
|
|
1384
|
+
// widget config would leave the widget mounted on `body`.
|
|
1385
|
+
const needsWrapper = Boolean(options?.windowKey || options?.target);
|
|
1386
|
+
const payload = needsWrapper
|
|
1387
|
+
? {
|
|
1388
|
+
config: serializableConfig,
|
|
1389
|
+
...(options?.windowKey ? { windowKey: options.windowKey } : {}),
|
|
1390
|
+
...(options?.target ? { target: options.target } : {}),
|
|
1391
|
+
}
|
|
1363
1392
|
: serializableConfig;
|
|
1364
1393
|
|
|
1365
1394
|
// Escape single quotes in JSON for HTML attribute
|
|
@@ -1383,7 +1412,7 @@ function generateScriptManualCode(config: any, options?: CodeGeneratorOptions):
|
|
|
1383
1412
|
"<!-- Initialize widget -->",
|
|
1384
1413
|
"<script>",
|
|
1385
1414
|
" var handle = window.AgentWidget.initAgentWidget({",
|
|
1386
|
-
|
|
1415
|
+
` target: '${emitTargetSelector(options)}',`,
|
|
1387
1416
|
...(options?.windowKey ? [` windowKey: '${options.windowKey}',`] : []),
|
|
1388
1417
|
" config: {"
|
|
1389
1418
|
];
|
|
@@ -1735,7 +1764,7 @@ function generateScriptAdvancedCode(config: any, options?: CodeGeneratorOptions)
|
|
|
1735
1764
|
"",
|
|
1736
1765
|
" // Initialize widget",
|
|
1737
1766
|
" var handle = agentWidget.initAgentWidget({",
|
|
1738
|
-
|
|
1767
|
+
` target: '${emitTargetSelector(options)}',`,
|
|
1739
1768
|
" useShadowDom: false,",
|
|
1740
1769
|
...(options?.windowKey ? [` windowKey: '${options.windowKey}',`] : []),
|
|
1741
1770
|
" config: widgetConfig",
|