@runtypelabs/persona 3.19.0 → 3.20.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/README.md +44 -1
- package/dist/index.cjs +27 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -3
- package/dist/index.d.ts +103 -3
- package/dist/index.global.js +65 -65
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +27 -27
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +80 -11
- package/dist/theme-editor.d.cts +73 -0
- package/dist/theme-editor.d.ts +73 -0
- package/dist/theme-editor.js +80 -11
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/session.test.ts +123 -0
- package/src/session.ts +58 -4
- package/src/types.ts +75 -0
- package/src/ui.ts +18 -0
- package/src/utils/component-middleware.test.ts +134 -0
- package/src/utils/component-middleware.ts +44 -13
package/dist/theme-editor.cjs
CHANGED
|
@@ -7505,7 +7505,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7505
7505
|
createdAt,
|
|
7506
7506
|
sequence,
|
|
7507
7507
|
streaming = false,
|
|
7508
|
-
voiceProcessing
|
|
7508
|
+
voiceProcessing,
|
|
7509
|
+
rawContent
|
|
7509
7510
|
} = options;
|
|
7510
7511
|
const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7511
7512
|
const message = {
|
|
@@ -7518,7 +7519,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7518
7519
|
// Only include optional fields if provided
|
|
7519
7520
|
...llmContent !== void 0 && { llmContent },
|
|
7520
7521
|
...contentParts !== void 0 && { contentParts },
|
|
7521
|
-
...voiceProcessing !== void 0 && { voiceProcessing }
|
|
7522
|
+
...voiceProcessing !== void 0 && { voiceProcessing },
|
|
7523
|
+
...rawContent !== void 0 && { rawContent }
|
|
7522
7524
|
};
|
|
7523
7525
|
this.upsertMessage(message);
|
|
7524
7526
|
return message;
|
|
@@ -7583,7 +7585,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7583
7585
|
id,
|
|
7584
7586
|
createdAt,
|
|
7585
7587
|
sequence,
|
|
7586
|
-
streaming = false
|
|
7588
|
+
streaming = false,
|
|
7589
|
+
voiceProcessing,
|
|
7590
|
+
rawContent
|
|
7587
7591
|
} = options;
|
|
7588
7592
|
const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7589
7593
|
const message = {
|
|
@@ -7594,7 +7598,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7594
7598
|
sequence: sequence != null ? sequence : this.nextSequence(),
|
|
7595
7599
|
streaming,
|
|
7596
7600
|
...llmContent !== void 0 && { llmContent },
|
|
7597
|
-
...contentParts !== void 0 && { contentParts }
|
|
7601
|
+
...contentParts !== void 0 && { contentParts },
|
|
7602
|
+
...voiceProcessing !== void 0 && { voiceProcessing },
|
|
7603
|
+
...rawContent !== void 0 && { rawContent }
|
|
7598
7604
|
};
|
|
7599
7605
|
results.push(message);
|
|
7600
7606
|
}
|
|
@@ -7602,6 +7608,48 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7602
7608
|
this.callbacks.onMessagesChanged([...this.messages]);
|
|
7603
7609
|
return results;
|
|
7604
7610
|
}
|
|
7611
|
+
/**
|
|
7612
|
+
* Convenience method for injecting a registered component directive as
|
|
7613
|
+
* an assistant message — the same shape Persona produces from a streamed
|
|
7614
|
+
* `{ "text": "...", "component": "...", "props": {...} }` payload.
|
|
7615
|
+
*
|
|
7616
|
+
* Sets `content` to `text`, `rawContent` to the JSON directive (so
|
|
7617
|
+
* `extractComponentDirectiveFromMessage` can find it), and forwards
|
|
7618
|
+
* `llmContent` / `id` / `createdAt` / `sequence`.
|
|
7619
|
+
*
|
|
7620
|
+
* @example
|
|
7621
|
+
* session.injectComponentDirective({
|
|
7622
|
+
* component: "DynamicForm",
|
|
7623
|
+
* props: { title: "Book a demo", fields: [...] },
|
|
7624
|
+
* text: "Share your details to book a demo.",
|
|
7625
|
+
* llmContent: "[Showed booking form]"
|
|
7626
|
+
* });
|
|
7627
|
+
*/
|
|
7628
|
+
injectComponentDirective(options) {
|
|
7629
|
+
const {
|
|
7630
|
+
component,
|
|
7631
|
+
props = {},
|
|
7632
|
+
text = "",
|
|
7633
|
+
llmContent,
|
|
7634
|
+
id,
|
|
7635
|
+
createdAt,
|
|
7636
|
+
sequence
|
|
7637
|
+
} = options;
|
|
7638
|
+
const directive = {
|
|
7639
|
+
text,
|
|
7640
|
+
component,
|
|
7641
|
+
props
|
|
7642
|
+
};
|
|
7643
|
+
return this.injectMessage({
|
|
7644
|
+
role: "assistant",
|
|
7645
|
+
content: text,
|
|
7646
|
+
rawContent: JSON.stringify(directive),
|
|
7647
|
+
...llmContent !== void 0 && { llmContent },
|
|
7648
|
+
...id !== void 0 && { id },
|
|
7649
|
+
...createdAt !== void 0 && { createdAt },
|
|
7650
|
+
...sequence !== void 0 && { sequence }
|
|
7651
|
+
});
|
|
7652
|
+
}
|
|
7605
7653
|
async sendMessage(rawInput, options) {
|
|
7606
7654
|
var _a, _b, _c, _d, _e;
|
|
7607
7655
|
const input = rawInput.trim();
|
|
@@ -15326,24 +15374,39 @@ function renderComponentDirective(directive, options) {
|
|
|
15326
15374
|
return null;
|
|
15327
15375
|
}
|
|
15328
15376
|
}
|
|
15377
|
+
function selectDirectiveSource(message) {
|
|
15378
|
+
if (typeof message.rawContent === "string" && message.rawContent.length > 0) {
|
|
15379
|
+
return message.rawContent;
|
|
15380
|
+
}
|
|
15381
|
+
if (typeof message.content === "string") {
|
|
15382
|
+
const trimmed = message.content.trim();
|
|
15383
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
15384
|
+
return message.content;
|
|
15385
|
+
}
|
|
15386
|
+
}
|
|
15387
|
+
return null;
|
|
15388
|
+
}
|
|
15329
15389
|
function hasComponentDirective(message) {
|
|
15330
|
-
|
|
15390
|
+
const source = selectDirectiveSource(message);
|
|
15391
|
+
if (!source) return false;
|
|
15331
15392
|
try {
|
|
15332
|
-
const parsed = JSON.parse(
|
|
15393
|
+
const parsed = JSON.parse(source);
|
|
15333
15394
|
return typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string";
|
|
15334
15395
|
} catch {
|
|
15335
15396
|
return false;
|
|
15336
15397
|
}
|
|
15337
15398
|
}
|
|
15338
15399
|
function extractComponentDirectiveFromMessage(message) {
|
|
15339
|
-
|
|
15400
|
+
const source = selectDirectiveSource(message);
|
|
15401
|
+
if (!source) return null;
|
|
15340
15402
|
try {
|
|
15341
|
-
const parsed = JSON.parse(
|
|
15403
|
+
const parsed = JSON.parse(source);
|
|
15342
15404
|
if (typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string") {
|
|
15405
|
+
const directive = parsed;
|
|
15343
15406
|
return {
|
|
15344
|
-
component:
|
|
15345
|
-
props:
|
|
15346
|
-
raw:
|
|
15407
|
+
component: directive.component,
|
|
15408
|
+
props: directive.props && typeof directive.props === "object" && directive.props !== null ? directive.props : {},
|
|
15409
|
+
raw: source
|
|
15347
15410
|
};
|
|
15348
15411
|
}
|
|
15349
15412
|
} catch {
|
|
@@ -20743,6 +20806,12 @@ var createAgentExperience = (mount, initialConfig, runtimeOptions) => {
|
|
|
20743
20806
|
}
|
|
20744
20807
|
return session.injectMessageBatch(optionsList);
|
|
20745
20808
|
},
|
|
20809
|
+
injectComponentDirective(options) {
|
|
20810
|
+
if (!open && isPanelToggleable()) {
|
|
20811
|
+
setOpenState(true, "system");
|
|
20812
|
+
}
|
|
20813
|
+
return session.injectComponentDirective(options);
|
|
20814
|
+
},
|
|
20746
20815
|
/** @deprecated Use injectMessage() instead */
|
|
20747
20816
|
injectTestMessage(event) {
|
|
20748
20817
|
if (!open && isPanelToggleable()) {
|
package/dist/theme-editor.d.cts
CHANGED
|
@@ -4097,6 +4097,22 @@ type InjectMessageOptions = {
|
|
|
4097
4097
|
* Consumers can detect this in `messageTransform` to render custom UI.
|
|
4098
4098
|
*/
|
|
4099
4099
|
voiceProcessing?: boolean;
|
|
4100
|
+
/**
|
|
4101
|
+
* Raw structured payload (typically a JSON string) representing the
|
|
4102
|
+
* full directive that produced this message — e.g. `{ "text": "...",
|
|
4103
|
+
* "component": "Foo", "props": {...} }`.
|
|
4104
|
+
*
|
|
4105
|
+
* Mirrors the field populated by stream parsers during normal LLM
|
|
4106
|
+
* responses. Set this when injecting a message that should render as a
|
|
4107
|
+
* component directive (`hasComponentDirective` /
|
|
4108
|
+
* `extractComponentDirectiveFromMessage` look at `rawContent` first).
|
|
4109
|
+
*
|
|
4110
|
+
* Priority for the API payload remains:
|
|
4111
|
+
* `contentParts > llmContent > rawContent > content`. Pass `llmContent`
|
|
4112
|
+
* alongside `rawContent` if the LLM should see something other than the
|
|
4113
|
+
* raw directive.
|
|
4114
|
+
*/
|
|
4115
|
+
rawContent?: string;
|
|
4100
4116
|
};
|
|
4101
4117
|
/**
|
|
4102
4118
|
* Options for injecting assistant messages (most common case).
|
|
@@ -4113,6 +4129,57 @@ type InjectUserMessageOptions = Omit<InjectMessageOptions, "role">;
|
|
|
4113
4129
|
* Role defaults to 'system'.
|
|
4114
4130
|
*/
|
|
4115
4131
|
type InjectSystemMessageOptions = Omit<InjectMessageOptions, "role">;
|
|
4132
|
+
/**
|
|
4133
|
+
* Options for injecting an assistant message that renders as a component
|
|
4134
|
+
* directive — sugar over `injectAssistantMessage` for the common case of
|
|
4135
|
+
* "render this registered component, same as if the LLM had emitted it".
|
|
4136
|
+
*
|
|
4137
|
+
* Equivalent to calling `injectAssistantMessage({ content: text, rawContent:
|
|
4138
|
+
* JSON.stringify({ text, component, props }), llmContent })`.
|
|
4139
|
+
*
|
|
4140
|
+
* @example
|
|
4141
|
+
* widget.injectComponentDirective({
|
|
4142
|
+
* component: "DynamicForm",
|
|
4143
|
+
* props: { title: "Book a demo", fields: [...] },
|
|
4144
|
+
* text: "Share your details to book a demo.",
|
|
4145
|
+
* llmContent: "[Showed booking form]"
|
|
4146
|
+
* });
|
|
4147
|
+
*/
|
|
4148
|
+
type InjectComponentDirectiveOptions = {
|
|
4149
|
+
/**
|
|
4150
|
+
* Name of a renderer registered via `componentRegistry.register(...)`.
|
|
4151
|
+
*/
|
|
4152
|
+
component: string;
|
|
4153
|
+
/**
|
|
4154
|
+
* Props passed to the component renderer.
|
|
4155
|
+
*/
|
|
4156
|
+
props?: Record<string, unknown>;
|
|
4157
|
+
/**
|
|
4158
|
+
* Bubble copy displayed above (or with) the rendered component.
|
|
4159
|
+
* Mirrors the `text` field in a streamed JSON directive.
|
|
4160
|
+
* @default ""
|
|
4161
|
+
*/
|
|
4162
|
+
text?: string;
|
|
4163
|
+
/**
|
|
4164
|
+
* Content sent to the LLM in API requests. When omitted, the raw
|
|
4165
|
+
* directive JSON is what the LLM would see (per the standard
|
|
4166
|
+
* priority chain). Provide a redacted/short version to avoid sending
|
|
4167
|
+
* the full directive in subsequent turns.
|
|
4168
|
+
*/
|
|
4169
|
+
llmContent?: string;
|
|
4170
|
+
/**
|
|
4171
|
+
* Optional message ID. If omitted, an assistant id is auto-generated.
|
|
4172
|
+
*/
|
|
4173
|
+
id?: string;
|
|
4174
|
+
/**
|
|
4175
|
+
* Optional creation timestamp (ISO string). If omitted, uses current time.
|
|
4176
|
+
*/
|
|
4177
|
+
createdAt?: string;
|
|
4178
|
+
/**
|
|
4179
|
+
* Optional sequence number for ordering.
|
|
4180
|
+
*/
|
|
4181
|
+
sequence?: number;
|
|
4182
|
+
};
|
|
4116
4183
|
type PersonaArtifactRecord = {
|
|
4117
4184
|
id: string;
|
|
4118
4185
|
artifactType: PersonaArtifactKind;
|
|
@@ -4480,6 +4547,12 @@ type Controller = {
|
|
|
4480
4547
|
* Inject multiple messages in a single batch with one sort and one render pass.
|
|
4481
4548
|
*/
|
|
4482
4549
|
injectMessageBatch: (optionsList: InjectMessageOptions[]) => AgentWidgetMessage[];
|
|
4550
|
+
/**
|
|
4551
|
+
* Convenience method for injecting an assistant message that renders as a
|
|
4552
|
+
* registered component — same shape Persona produces from a streamed
|
|
4553
|
+
* `{ "text": "...", "component": "...", "props": {...} }` payload.
|
|
4554
|
+
*/
|
|
4555
|
+
injectComponentDirective: (options: InjectComponentDirectiveOptions) => AgentWidgetMessage;
|
|
4483
4556
|
/**
|
|
4484
4557
|
* @deprecated Use injectMessage() instead.
|
|
4485
4558
|
*/
|
package/dist/theme-editor.d.ts
CHANGED
|
@@ -4097,6 +4097,22 @@ type InjectMessageOptions = {
|
|
|
4097
4097
|
* Consumers can detect this in `messageTransform` to render custom UI.
|
|
4098
4098
|
*/
|
|
4099
4099
|
voiceProcessing?: boolean;
|
|
4100
|
+
/**
|
|
4101
|
+
* Raw structured payload (typically a JSON string) representing the
|
|
4102
|
+
* full directive that produced this message — e.g. `{ "text": "...",
|
|
4103
|
+
* "component": "Foo", "props": {...} }`.
|
|
4104
|
+
*
|
|
4105
|
+
* Mirrors the field populated by stream parsers during normal LLM
|
|
4106
|
+
* responses. Set this when injecting a message that should render as a
|
|
4107
|
+
* component directive (`hasComponentDirective` /
|
|
4108
|
+
* `extractComponentDirectiveFromMessage` look at `rawContent` first).
|
|
4109
|
+
*
|
|
4110
|
+
* Priority for the API payload remains:
|
|
4111
|
+
* `contentParts > llmContent > rawContent > content`. Pass `llmContent`
|
|
4112
|
+
* alongside `rawContent` if the LLM should see something other than the
|
|
4113
|
+
* raw directive.
|
|
4114
|
+
*/
|
|
4115
|
+
rawContent?: string;
|
|
4100
4116
|
};
|
|
4101
4117
|
/**
|
|
4102
4118
|
* Options for injecting assistant messages (most common case).
|
|
@@ -4113,6 +4129,57 @@ type InjectUserMessageOptions = Omit<InjectMessageOptions, "role">;
|
|
|
4113
4129
|
* Role defaults to 'system'.
|
|
4114
4130
|
*/
|
|
4115
4131
|
type InjectSystemMessageOptions = Omit<InjectMessageOptions, "role">;
|
|
4132
|
+
/**
|
|
4133
|
+
* Options for injecting an assistant message that renders as a component
|
|
4134
|
+
* directive — sugar over `injectAssistantMessage` for the common case of
|
|
4135
|
+
* "render this registered component, same as if the LLM had emitted it".
|
|
4136
|
+
*
|
|
4137
|
+
* Equivalent to calling `injectAssistantMessage({ content: text, rawContent:
|
|
4138
|
+
* JSON.stringify({ text, component, props }), llmContent })`.
|
|
4139
|
+
*
|
|
4140
|
+
* @example
|
|
4141
|
+
* widget.injectComponentDirective({
|
|
4142
|
+
* component: "DynamicForm",
|
|
4143
|
+
* props: { title: "Book a demo", fields: [...] },
|
|
4144
|
+
* text: "Share your details to book a demo.",
|
|
4145
|
+
* llmContent: "[Showed booking form]"
|
|
4146
|
+
* });
|
|
4147
|
+
*/
|
|
4148
|
+
type InjectComponentDirectiveOptions = {
|
|
4149
|
+
/**
|
|
4150
|
+
* Name of a renderer registered via `componentRegistry.register(...)`.
|
|
4151
|
+
*/
|
|
4152
|
+
component: string;
|
|
4153
|
+
/**
|
|
4154
|
+
* Props passed to the component renderer.
|
|
4155
|
+
*/
|
|
4156
|
+
props?: Record<string, unknown>;
|
|
4157
|
+
/**
|
|
4158
|
+
* Bubble copy displayed above (or with) the rendered component.
|
|
4159
|
+
* Mirrors the `text` field in a streamed JSON directive.
|
|
4160
|
+
* @default ""
|
|
4161
|
+
*/
|
|
4162
|
+
text?: string;
|
|
4163
|
+
/**
|
|
4164
|
+
* Content sent to the LLM in API requests. When omitted, the raw
|
|
4165
|
+
* directive JSON is what the LLM would see (per the standard
|
|
4166
|
+
* priority chain). Provide a redacted/short version to avoid sending
|
|
4167
|
+
* the full directive in subsequent turns.
|
|
4168
|
+
*/
|
|
4169
|
+
llmContent?: string;
|
|
4170
|
+
/**
|
|
4171
|
+
* Optional message ID. If omitted, an assistant id is auto-generated.
|
|
4172
|
+
*/
|
|
4173
|
+
id?: string;
|
|
4174
|
+
/**
|
|
4175
|
+
* Optional creation timestamp (ISO string). If omitted, uses current time.
|
|
4176
|
+
*/
|
|
4177
|
+
createdAt?: string;
|
|
4178
|
+
/**
|
|
4179
|
+
* Optional sequence number for ordering.
|
|
4180
|
+
*/
|
|
4181
|
+
sequence?: number;
|
|
4182
|
+
};
|
|
4116
4183
|
type PersonaArtifactRecord = {
|
|
4117
4184
|
id: string;
|
|
4118
4185
|
artifactType: PersonaArtifactKind;
|
|
@@ -4480,6 +4547,12 @@ type Controller = {
|
|
|
4480
4547
|
* Inject multiple messages in a single batch with one sort and one render pass.
|
|
4481
4548
|
*/
|
|
4482
4549
|
injectMessageBatch: (optionsList: InjectMessageOptions[]) => AgentWidgetMessage[];
|
|
4550
|
+
/**
|
|
4551
|
+
* Convenience method for injecting an assistant message that renders as a
|
|
4552
|
+
* registered component — same shape Persona produces from a streamed
|
|
4553
|
+
* `{ "text": "...", "component": "...", "props": {...} }` payload.
|
|
4554
|
+
*/
|
|
4555
|
+
injectComponentDirective: (options: InjectComponentDirectiveOptions) => AgentWidgetMessage;
|
|
4483
4556
|
/**
|
|
4484
4557
|
* @deprecated Use injectMessage() instead.
|
|
4485
4558
|
*/
|
package/dist/theme-editor.js
CHANGED
|
@@ -7394,7 +7394,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7394
7394
|
createdAt,
|
|
7395
7395
|
sequence,
|
|
7396
7396
|
streaming = false,
|
|
7397
|
-
voiceProcessing
|
|
7397
|
+
voiceProcessing,
|
|
7398
|
+
rawContent
|
|
7398
7399
|
} = options;
|
|
7399
7400
|
const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7400
7401
|
const message = {
|
|
@@ -7407,7 +7408,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7407
7408
|
// Only include optional fields if provided
|
|
7408
7409
|
...llmContent !== void 0 && { llmContent },
|
|
7409
7410
|
...contentParts !== void 0 && { contentParts },
|
|
7410
|
-
...voiceProcessing !== void 0 && { voiceProcessing }
|
|
7411
|
+
...voiceProcessing !== void 0 && { voiceProcessing },
|
|
7412
|
+
...rawContent !== void 0 && { rawContent }
|
|
7411
7413
|
};
|
|
7412
7414
|
this.upsertMessage(message);
|
|
7413
7415
|
return message;
|
|
@@ -7472,7 +7474,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7472
7474
|
id,
|
|
7473
7475
|
createdAt,
|
|
7474
7476
|
sequence,
|
|
7475
|
-
streaming = false
|
|
7477
|
+
streaming = false,
|
|
7478
|
+
voiceProcessing,
|
|
7479
|
+
rawContent
|
|
7476
7480
|
} = options;
|
|
7477
7481
|
const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7478
7482
|
const message = {
|
|
@@ -7483,7 +7487,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7483
7487
|
sequence: sequence != null ? sequence : this.nextSequence(),
|
|
7484
7488
|
streaming,
|
|
7485
7489
|
...llmContent !== void 0 && { llmContent },
|
|
7486
|
-
...contentParts !== void 0 && { contentParts }
|
|
7490
|
+
...contentParts !== void 0 && { contentParts },
|
|
7491
|
+
...voiceProcessing !== void 0 && { voiceProcessing },
|
|
7492
|
+
...rawContent !== void 0 && { rawContent }
|
|
7487
7493
|
};
|
|
7488
7494
|
results.push(message);
|
|
7489
7495
|
}
|
|
@@ -7491,6 +7497,48 @@ var AgentWidgetSession = class _AgentWidgetSession {
|
|
|
7491
7497
|
this.callbacks.onMessagesChanged([...this.messages]);
|
|
7492
7498
|
return results;
|
|
7493
7499
|
}
|
|
7500
|
+
/**
|
|
7501
|
+
* Convenience method for injecting a registered component directive as
|
|
7502
|
+
* an assistant message — the same shape Persona produces from a streamed
|
|
7503
|
+
* `{ "text": "...", "component": "...", "props": {...} }` payload.
|
|
7504
|
+
*
|
|
7505
|
+
* Sets `content` to `text`, `rawContent` to the JSON directive (so
|
|
7506
|
+
* `extractComponentDirectiveFromMessage` can find it), and forwards
|
|
7507
|
+
* `llmContent` / `id` / `createdAt` / `sequence`.
|
|
7508
|
+
*
|
|
7509
|
+
* @example
|
|
7510
|
+
* session.injectComponentDirective({
|
|
7511
|
+
* component: "DynamicForm",
|
|
7512
|
+
* props: { title: "Book a demo", fields: [...] },
|
|
7513
|
+
* text: "Share your details to book a demo.",
|
|
7514
|
+
* llmContent: "[Showed booking form]"
|
|
7515
|
+
* });
|
|
7516
|
+
*/
|
|
7517
|
+
injectComponentDirective(options) {
|
|
7518
|
+
const {
|
|
7519
|
+
component,
|
|
7520
|
+
props = {},
|
|
7521
|
+
text = "",
|
|
7522
|
+
llmContent,
|
|
7523
|
+
id,
|
|
7524
|
+
createdAt,
|
|
7525
|
+
sequence
|
|
7526
|
+
} = options;
|
|
7527
|
+
const directive = {
|
|
7528
|
+
text,
|
|
7529
|
+
component,
|
|
7530
|
+
props
|
|
7531
|
+
};
|
|
7532
|
+
return this.injectMessage({
|
|
7533
|
+
role: "assistant",
|
|
7534
|
+
content: text,
|
|
7535
|
+
rawContent: JSON.stringify(directive),
|
|
7536
|
+
...llmContent !== void 0 && { llmContent },
|
|
7537
|
+
...id !== void 0 && { id },
|
|
7538
|
+
...createdAt !== void 0 && { createdAt },
|
|
7539
|
+
...sequence !== void 0 && { sequence }
|
|
7540
|
+
});
|
|
7541
|
+
}
|
|
7494
7542
|
async sendMessage(rawInput, options) {
|
|
7495
7543
|
var _a, _b, _c, _d, _e;
|
|
7496
7544
|
const input = rawInput.trim();
|
|
@@ -15329,24 +15377,39 @@ function renderComponentDirective(directive, options) {
|
|
|
15329
15377
|
return null;
|
|
15330
15378
|
}
|
|
15331
15379
|
}
|
|
15380
|
+
function selectDirectiveSource(message) {
|
|
15381
|
+
if (typeof message.rawContent === "string" && message.rawContent.length > 0) {
|
|
15382
|
+
return message.rawContent;
|
|
15383
|
+
}
|
|
15384
|
+
if (typeof message.content === "string") {
|
|
15385
|
+
const trimmed = message.content.trim();
|
|
15386
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
15387
|
+
return message.content;
|
|
15388
|
+
}
|
|
15389
|
+
}
|
|
15390
|
+
return null;
|
|
15391
|
+
}
|
|
15332
15392
|
function hasComponentDirective(message) {
|
|
15333
|
-
|
|
15393
|
+
const source = selectDirectiveSource(message);
|
|
15394
|
+
if (!source) return false;
|
|
15334
15395
|
try {
|
|
15335
|
-
const parsed = JSON.parse(
|
|
15396
|
+
const parsed = JSON.parse(source);
|
|
15336
15397
|
return typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string";
|
|
15337
15398
|
} catch {
|
|
15338
15399
|
return false;
|
|
15339
15400
|
}
|
|
15340
15401
|
}
|
|
15341
15402
|
function extractComponentDirectiveFromMessage(message) {
|
|
15342
|
-
|
|
15403
|
+
const source = selectDirectiveSource(message);
|
|
15404
|
+
if (!source) return null;
|
|
15343
15405
|
try {
|
|
15344
|
-
const parsed = JSON.parse(
|
|
15406
|
+
const parsed = JSON.parse(source);
|
|
15345
15407
|
if (typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string") {
|
|
15408
|
+
const directive = parsed;
|
|
15346
15409
|
return {
|
|
15347
|
-
component:
|
|
15348
|
-
props:
|
|
15349
|
-
raw:
|
|
15410
|
+
component: directive.component,
|
|
15411
|
+
props: directive.props && typeof directive.props === "object" && directive.props !== null ? directive.props : {},
|
|
15412
|
+
raw: source
|
|
15350
15413
|
};
|
|
15351
15414
|
}
|
|
15352
15415
|
} catch {
|
|
@@ -20746,6 +20809,12 @@ var createAgentExperience = (mount, initialConfig, runtimeOptions) => {
|
|
|
20746
20809
|
}
|
|
20747
20810
|
return session.injectMessageBatch(optionsList);
|
|
20748
20811
|
},
|
|
20812
|
+
injectComponentDirective(options) {
|
|
20813
|
+
if (!open && isPanelToggleable()) {
|
|
20814
|
+
setOpenState(true, "system");
|
|
20815
|
+
}
|
|
20816
|
+
return session.injectComponentDirective(options);
|
|
20817
|
+
},
|
|
20749
20818
|
/** @deprecated Use injectMessage() instead */
|
|
20750
20819
|
injectTestMessage(event) {
|
|
20751
20820
|
if (!open && isPanelToggleable()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.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",
|
package/src/index.ts
CHANGED
package/src/session.test.ts
CHANGED
|
@@ -244,6 +244,129 @@ describe('AgentWidgetSession - Message Injection', () => {
|
|
|
244
244
|
expect(messages[0].content).toBe('Legacy message');
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
describe('rawContent forwarding', () => {
|
|
249
|
+
it('preserves rawContent on injected messages', () => {
|
|
250
|
+
const directive = JSON.stringify({
|
|
251
|
+
text: 'Booking form',
|
|
252
|
+
component: 'BookingForm',
|
|
253
|
+
props: { title: 'Schedule' }
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const result = session.injectMessage({
|
|
257
|
+
role: 'assistant',
|
|
258
|
+
content: 'Booking form',
|
|
259
|
+
rawContent: directive
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.rawContent).toBe(directive);
|
|
263
|
+
expect(messages[0].rawContent).toBe(directive);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('forwards rawContent through injectAssistantMessage', () => {
|
|
267
|
+
const directive = JSON.stringify({ text: 'Form', component: 'Form', props: {} });
|
|
268
|
+
|
|
269
|
+
const result = session.injectAssistantMessage({
|
|
270
|
+
content: 'Form',
|
|
271
|
+
rawContent: directive
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(result.rawContent).toBe(directive);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('forwards rawContent through injectMessageBatch', () => {
|
|
278
|
+
const directiveA = JSON.stringify({ text: 'A', component: 'CompA', props: {} });
|
|
279
|
+
const directiveB = JSON.stringify({ text: 'B', component: 'CompB', props: {} });
|
|
280
|
+
|
|
281
|
+
const results = session.injectMessageBatch([
|
|
282
|
+
{ role: 'assistant', content: 'A', rawContent: directiveA },
|
|
283
|
+
{ role: 'assistant', content: 'B', rawContent: directiveB }
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
expect(results[0].rawContent).toBe(directiveA);
|
|
287
|
+
expect(results[1].rawContent).toBe(directiveB);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('omits rawContent when not provided', () => {
|
|
291
|
+
const result = session.injectMessage({
|
|
292
|
+
role: 'assistant',
|
|
293
|
+
content: 'plain'
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(result.rawContent).toBeUndefined();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('injectComponentDirective', () => {
|
|
301
|
+
it('builds rawContent from component + props + text', () => {
|
|
302
|
+
const result = session.injectComponentDirective({
|
|
303
|
+
component: 'DynamicForm',
|
|
304
|
+
props: { title: 'Book a demo', fields: [{ label: 'Email' }] },
|
|
305
|
+
text: 'Share your details to book a demo.'
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect(result.role).toBe('assistant');
|
|
309
|
+
expect(result.content).toBe('Share your details to book a demo.');
|
|
310
|
+
expect(result.id).toMatch(/^ast_/);
|
|
311
|
+
expect(result.rawContent).toBeDefined();
|
|
312
|
+
|
|
313
|
+
const parsed = JSON.parse(result.rawContent as string);
|
|
314
|
+
expect(parsed).toEqual({
|
|
315
|
+
text: 'Share your details to book a demo.',
|
|
316
|
+
component: 'DynamicForm',
|
|
317
|
+
props: { title: 'Book a demo', fields: [{ label: 'Email' }] }
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('defaults text to empty string and props to {}', () => {
|
|
322
|
+
const result = session.injectComponentDirective({ component: 'DynamicForm' });
|
|
323
|
+
|
|
324
|
+
expect(result.content).toBe('');
|
|
325
|
+
const parsed = JSON.parse(result.rawContent as string);
|
|
326
|
+
expect(parsed).toEqual({ text: '', component: 'DynamicForm', props: {} });
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('forwards llmContent for redacted LLM context', () => {
|
|
330
|
+
const result = session.injectComponentDirective({
|
|
331
|
+
component: 'DynamicForm',
|
|
332
|
+
props: { title: 'Book a demo' },
|
|
333
|
+
text: 'Booking form below.',
|
|
334
|
+
llmContent: '[Showed booking form]'
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(result.llmContent).toBe('[Showed booking form]');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('honors custom id, createdAt, sequence', () => {
|
|
341
|
+
const result = session.injectComponentDirective({
|
|
342
|
+
component: 'DynamicForm',
|
|
343
|
+
id: 'my-form-1',
|
|
344
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
345
|
+
sequence: 999
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
expect(result.id).toBe('my-form-1');
|
|
349
|
+
expect(result.createdAt).toBe('2026-01-01T00:00:00.000Z');
|
|
350
|
+
expect(result.sequence).toBe(999);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('upserts an existing directive by id', () => {
|
|
354
|
+
session.injectComponentDirective({
|
|
355
|
+
component: 'DynamicForm',
|
|
356
|
+
props: { title: 'v1' },
|
|
357
|
+
id: 'reuse-me'
|
|
358
|
+
});
|
|
359
|
+
session.injectComponentDirective({
|
|
360
|
+
component: 'DynamicForm',
|
|
361
|
+
props: { title: 'v2' },
|
|
362
|
+
id: 'reuse-me'
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(messages).toHaveLength(1);
|
|
366
|
+
const parsed = JSON.parse(messages[0].rawContent as string);
|
|
367
|
+
expect(parsed.props.title).toBe('v2');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
247
370
|
});
|
|
248
371
|
|
|
249
372
|
describe('AgentWidgetSession - cancel()', () => {
|