@runtypelabs/persona 3.9.1 → 3.10.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/index.cjs +46 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.global.js +67 -65
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -44
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +828 -212
- package/dist/theme-editor.d.cts +128 -3
- package/dist/theme-editor.d.ts +128 -3
- package/dist/theme-editor.js +824 -212
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +8 -0
- package/dist/theme-reference.d.ts +8 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +124 -0
- package/package.json +1 -1
- package/src/client.test.ts +312 -1
- package/src/client.ts +247 -24
- package/src/components/messages.ts +1 -1
- package/src/components/reasoning-bubble.ts +117 -28
- package/src/components/tool-bubble.ts +162 -28
- package/src/defaults.ts +13 -1
- package/src/styles/widget.css +124 -0
- package/src/theme-editor/index.ts +5 -0
- package/src/theme-editor/preview-utils.test.ts +58 -0
- package/src/theme-editor/preview-utils.ts +220 -4
- package/src/theme-editor/sections.test.ts +20 -0
- package/src/theme-editor/sections.ts +10 -0
- package/src/theme-reference.ts +8 -3
- package/src/tool-call-display-defaults.test.ts +23 -0
- package/src/types.ts +126 -0
- package/src/ui.scroll.test.ts +104 -0
- package/src/ui.tool-display.test.ts +204 -0
- package/src/ui.ts +103 -3
- package/src/utils/message-fingerprint.test.ts +17 -0
- package/src/utils/message-fingerprint.ts +13 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
import type { AgentWidgetController } from "./ui";
|
|
7
|
+
|
|
8
|
+
const createMount = () => {
|
|
9
|
+
const mount = document.createElement("div");
|
|
10
|
+
document.body.appendChild(mount);
|
|
11
|
+
return mount;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const injectToolMessage = (
|
|
15
|
+
controller: AgentWidgetController,
|
|
16
|
+
{
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
status = "running",
|
|
20
|
+
chunks = [],
|
|
21
|
+
}: {
|
|
22
|
+
id: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
status?: "pending" | "running" | "complete";
|
|
25
|
+
chunks?: string[];
|
|
26
|
+
}
|
|
27
|
+
) => {
|
|
28
|
+
controller.injectTestMessage({
|
|
29
|
+
type: "message",
|
|
30
|
+
message: {
|
|
31
|
+
id,
|
|
32
|
+
role: "assistant",
|
|
33
|
+
content: "",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
streaming: status !== "complete",
|
|
36
|
+
variant: "tool",
|
|
37
|
+
toolCall: {
|
|
38
|
+
id,
|
|
39
|
+
name,
|
|
40
|
+
status,
|
|
41
|
+
chunks,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const injectReasoningMessage = (
|
|
48
|
+
controller: AgentWidgetController,
|
|
49
|
+
{
|
|
50
|
+
id,
|
|
51
|
+
status = "streaming",
|
|
52
|
+
chunks = [],
|
|
53
|
+
}: {
|
|
54
|
+
id: string;
|
|
55
|
+
status?: "pending" | "streaming" | "complete";
|
|
56
|
+
chunks?: string[];
|
|
57
|
+
}
|
|
58
|
+
) => {
|
|
59
|
+
controller.injectTestMessage({
|
|
60
|
+
type: "message",
|
|
61
|
+
message: {
|
|
62
|
+
id,
|
|
63
|
+
role: "assistant",
|
|
64
|
+
content: "",
|
|
65
|
+
createdAt: new Date().toISOString(),
|
|
66
|
+
streaming: status !== "complete",
|
|
67
|
+
variant: "reasoning",
|
|
68
|
+
reasoning: {
|
|
69
|
+
id,
|
|
70
|
+
status,
|
|
71
|
+
chunks,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
describe("createAgentExperience tool call display modes", () => {
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
80
|
+
cb(0);
|
|
81
|
+
return 1;
|
|
82
|
+
});
|
|
83
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
84
|
+
window.scrollTo = vi.fn();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
document.body.innerHTML = "";
|
|
89
|
+
vi.restoreAllMocks();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps collapsed tool rows on the generic summary by default", () => {
|
|
93
|
+
const mount = createMount();
|
|
94
|
+
const controller = createAgentExperience(mount, {
|
|
95
|
+
apiUrl: "https://api.example.com/chat",
|
|
96
|
+
launcher: { enabled: false },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
injectToolMessage(controller, {
|
|
100
|
+
id: "tool-1",
|
|
101
|
+
name: "Get platform documentation",
|
|
102
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const header = mount.querySelector(".persona-tool-bubble button[data-expand-header='true']");
|
|
106
|
+
expect(header?.textContent).toContain("Using tool...");
|
|
107
|
+
expect(header?.textContent).not.toContain("Get platform documentation");
|
|
108
|
+
|
|
109
|
+
controller.destroy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows the tool name in collapsed rows when configured", () => {
|
|
113
|
+
const mount = createMount();
|
|
114
|
+
const controller = createAgentExperience(mount, {
|
|
115
|
+
apiUrl: "https://api.example.com/chat",
|
|
116
|
+
launcher: { enabled: false },
|
|
117
|
+
features: {
|
|
118
|
+
toolCallDisplay: {
|
|
119
|
+
collapsedMode: "tool-name",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
} as any);
|
|
123
|
+
|
|
124
|
+
injectToolMessage(controller, {
|
|
125
|
+
id: "tool-1",
|
|
126
|
+
name: "Get platform documentation",
|
|
127
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const header = mount.querySelector(".persona-tool-bubble button[data-expand-header='true']");
|
|
131
|
+
expect(header?.textContent).toContain("Get platform documentation");
|
|
132
|
+
|
|
133
|
+
controller.destroy();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders a collapsed preview for active tool rows when enabled", () => {
|
|
137
|
+
const mount = createMount();
|
|
138
|
+
const controller = createAgentExperience(mount, {
|
|
139
|
+
apiUrl: "https://api.example.com/chat",
|
|
140
|
+
launcher: { enabled: false },
|
|
141
|
+
features: {
|
|
142
|
+
toolCallDisplay: {
|
|
143
|
+
activePreview: true,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
} as any);
|
|
147
|
+
|
|
148
|
+
injectToolMessage(controller, {
|
|
149
|
+
id: "tool-1",
|
|
150
|
+
name: "Get platform documentation",
|
|
151
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const preview = mount.querySelector("[data-persona-collapsed-preview='tool']");
|
|
155
|
+
expect(preview?.textContent).toContain("Loaded tools, used Runtype integration");
|
|
156
|
+
|
|
157
|
+
controller.destroy();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("renders a collapsed preview for active reasoning rows when enabled", () => {
|
|
161
|
+
const mount = createMount();
|
|
162
|
+
const controller = createAgentExperience(mount, {
|
|
163
|
+
apiUrl: "https://api.example.com/chat",
|
|
164
|
+
launcher: { enabled: false },
|
|
165
|
+
features: {
|
|
166
|
+
reasoningDisplay: {
|
|
167
|
+
activePreview: true,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
} as any);
|
|
171
|
+
|
|
172
|
+
injectReasoningMessage(controller, {
|
|
173
|
+
id: "reason-1",
|
|
174
|
+
chunks: ["Now let me get the Persona embed documentation and builtin tools catalog."],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const preview = mount.querySelector("[data-persona-collapsed-preview='reasoning']");
|
|
178
|
+
expect(preview?.textContent).toContain("Now let me get the Persona embed documentation");
|
|
179
|
+
|
|
180
|
+
controller.destroy();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("groups consecutive tool calls when enabled", () => {
|
|
184
|
+
const mount = createMount();
|
|
185
|
+
const controller = createAgentExperience(mount, {
|
|
186
|
+
apiUrl: "https://api.example.com/chat",
|
|
187
|
+
launcher: { enabled: false },
|
|
188
|
+
features: {
|
|
189
|
+
toolCallDisplay: {
|
|
190
|
+
grouped: true,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
} as any);
|
|
194
|
+
|
|
195
|
+
injectToolMessage(controller, { id: "tool-1", name: "Load tools", chunks: ["Loaded tools"] });
|
|
196
|
+
injectToolMessage(controller, { id: "tool-2", name: "Get docs", chunks: ["Fetched docs"] });
|
|
197
|
+
|
|
198
|
+
const group = mount.querySelector("[data-persona-tool-group='true']");
|
|
199
|
+
expect(group).not.toBeNull();
|
|
200
|
+
expect(group?.textContent).toContain("Called 2 tools");
|
|
201
|
+
|
|
202
|
+
controller.destroy();
|
|
203
|
+
});
|
|
204
|
+
});
|
package/src/ui.ts
CHANGED
|
@@ -2213,6 +2213,18 @@ export const createAgentExperience = (
|
|
|
2213
2213
|
};
|
|
2214
2214
|
|
|
2215
2215
|
const inlineLoadingRenderer = getInlineLoadingIndicatorRenderer();
|
|
2216
|
+
const appendRenderedValue = (
|
|
2217
|
+
containerEl: HTMLElement,
|
|
2218
|
+
value: HTMLElement | string | null | undefined
|
|
2219
|
+
): boolean => {
|
|
2220
|
+
if (value == null) return false;
|
|
2221
|
+
if (typeof value === "string") {
|
|
2222
|
+
containerEl.textContent = value;
|
|
2223
|
+
return true;
|
|
2224
|
+
}
|
|
2225
|
+
containerEl.appendChild(value);
|
|
2226
|
+
return true;
|
|
2227
|
+
};
|
|
2216
2228
|
|
|
2217
2229
|
// Track active message IDs for cache pruning
|
|
2218
2230
|
const activeMessageIds = new Set<string>();
|
|
@@ -2255,7 +2267,7 @@ export const createAgentExperience = (
|
|
|
2255
2267
|
if (!showReasoning) return;
|
|
2256
2268
|
bubble = matchingPlugin.renderReasoning({
|
|
2257
2269
|
message,
|
|
2258
|
-
defaultRenderer: () => createReasoningBubble(message),
|
|
2270
|
+
defaultRenderer: () => createReasoningBubble(message, config),
|
|
2259
2271
|
config
|
|
2260
2272
|
});
|
|
2261
2273
|
} else if (message.variant === "tool" && message.toolCall && matchingPlugin.renderToolCall) {
|
|
@@ -2371,7 +2383,7 @@ export const createAgentExperience = (
|
|
|
2371
2383
|
if (!bubble) {
|
|
2372
2384
|
if (message.variant === "reasoning" && message.reasoning) {
|
|
2373
2385
|
if (!showReasoning) return;
|
|
2374
|
-
bubble = createReasoningBubble(message);
|
|
2386
|
+
bubble = createReasoningBubble(message, config);
|
|
2375
2387
|
} else if (message.variant === "tool" && message.toolCall) {
|
|
2376
2388
|
if (!showToolCalls) return;
|
|
2377
2389
|
bubble = createToolBubble(message, config);
|
|
@@ -2428,6 +2440,86 @@ export const createAgentExperience = (
|
|
|
2428
2440
|
tempContainer.appendChild(wrapper);
|
|
2429
2441
|
});
|
|
2430
2442
|
|
|
2443
|
+
if (config.features?.toolCallDisplay?.grouped) {
|
|
2444
|
+
const toolGroups: AgentWidgetMessage[][] = [];
|
|
2445
|
+
let currentGroup: AgentWidgetMessage[] = [];
|
|
2446
|
+
|
|
2447
|
+
messages.forEach((message) => {
|
|
2448
|
+
if (message.variant === "tool" && message.toolCall && showToolCalls) {
|
|
2449
|
+
currentGroup.push(message);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (currentGroup.length > 1) {
|
|
2453
|
+
toolGroups.push(currentGroup);
|
|
2454
|
+
}
|
|
2455
|
+
currentGroup = [];
|
|
2456
|
+
});
|
|
2457
|
+
if (currentGroup.length > 1) {
|
|
2458
|
+
toolGroups.push(currentGroup);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
toolGroups.forEach((group, groupIndex) => {
|
|
2462
|
+
const wrappers = group
|
|
2463
|
+
.map((groupMessage) =>
|
|
2464
|
+
Array.from(tempContainer.children).find(
|
|
2465
|
+
(child) =>
|
|
2466
|
+
child instanceof HTMLElement &&
|
|
2467
|
+
child.getAttribute("data-wrapper-id") === groupMessage.id
|
|
2468
|
+
) as HTMLElement | undefined
|
|
2469
|
+
)
|
|
2470
|
+
.filter((wrapper): wrapper is HTMLElement => Boolean(wrapper));
|
|
2471
|
+
|
|
2472
|
+
if (wrappers.length < 2) {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
const groupWrapper = document.createElement("div");
|
|
2477
|
+
groupWrapper.className = "persona-flex";
|
|
2478
|
+
groupWrapper.id = `wrapper-tool-group-${groupIndex}-${group[0].id}`;
|
|
2479
|
+
groupWrapper.setAttribute("data-wrapper-id", `tool-group-${groupIndex}-${group[0].id}`);
|
|
2480
|
+
|
|
2481
|
+
const groupContainer = document.createElement("div");
|
|
2482
|
+
groupContainer.className =
|
|
2483
|
+
"persona-tool-group persona-flex persona-w-full persona-flex-col persona-gap-2";
|
|
2484
|
+
groupContainer.setAttribute("data-persona-tool-group", "true");
|
|
2485
|
+
|
|
2486
|
+
const summary = document.createElement("div");
|
|
2487
|
+
summary.className =
|
|
2488
|
+
"persona-tool-group-summary persona-text-xs persona-text-persona-muted";
|
|
2489
|
+
|
|
2490
|
+
const defaultSummary = `Called ${group.length} tools`;
|
|
2491
|
+
const renderedSummary = config.toolCall?.renderGroupedSummary?.({
|
|
2492
|
+
messages: group,
|
|
2493
|
+
toolCalls: group
|
|
2494
|
+
.map((groupMessage) => groupMessage.toolCall)
|
|
2495
|
+
.filter((toolCall): toolCall is NonNullable<typeof group[number]["toolCall"]> => Boolean(toolCall)),
|
|
2496
|
+
defaultSummary,
|
|
2497
|
+
config,
|
|
2498
|
+
});
|
|
2499
|
+
if (!appendRenderedValue(summary, renderedSummary)) {
|
|
2500
|
+
summary.textContent = defaultSummary;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
const stack = document.createElement("div");
|
|
2504
|
+
stack.className = "persona-tool-group-stack persona-flex persona-flex-col";
|
|
2505
|
+
|
|
2506
|
+
groupContainer.append(summary, stack);
|
|
2507
|
+
groupWrapper.appendChild(groupContainer);
|
|
2508
|
+
wrappers[0].before(groupWrapper);
|
|
2509
|
+
|
|
2510
|
+
wrappers.forEach((wrapper, wrapperIndex) => {
|
|
2511
|
+
const item = document.createElement("div");
|
|
2512
|
+
item.className = "persona-tool-group-item persona-relative";
|
|
2513
|
+
item.setAttribute("data-persona-tool-group-item", "true");
|
|
2514
|
+
if (wrapperIndex < wrappers.length - 1) {
|
|
2515
|
+
item.setAttribute("data-persona-tool-group-connector", "true");
|
|
2516
|
+
}
|
|
2517
|
+
item.appendChild(wrapper);
|
|
2518
|
+
stack.appendChild(item);
|
|
2519
|
+
});
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2431
2523
|
// Remove cache entries for messages that no longer exist
|
|
2432
2524
|
pruneCache(messageCache, activeMessageIds);
|
|
2433
2525
|
|
|
@@ -3853,6 +3945,10 @@ export const createAgentExperience = (
|
|
|
3853
3945
|
const previousColorScheme = config.colorScheme;
|
|
3854
3946
|
const previousLoadingIndicator = config.loadingIndicator;
|
|
3855
3947
|
const previousIterationDisplay = config.iterationDisplay;
|
|
3948
|
+
const previousShowReasoning = config.features?.showReasoning;
|
|
3949
|
+
const previousShowToolCalls = config.features?.showToolCalls;
|
|
3950
|
+
const previousToolCallDisplay = config.features?.toolCallDisplay;
|
|
3951
|
+
const previousReasoningDisplay = config.features?.reasoningDisplay;
|
|
3856
3952
|
config = { ...config, ...nextConfig };
|
|
3857
3953
|
// applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
|
|
3858
3954
|
applyFullHeightStyles();
|
|
@@ -4093,8 +4189,12 @@ export const createAgentExperience = (
|
|
|
4093
4189
|
|| config.loadingIndicator?.renderIdle !== previousLoadingIndicator?.renderIdle
|
|
4094
4190
|
|| config.loadingIndicator?.showBubble !== previousLoadingIndicator?.showBubble;
|
|
4095
4191
|
const iterationDisplayChanged = config.iterationDisplay !== previousIterationDisplay;
|
|
4192
|
+
const featuresChanged = (config.features?.showReasoning ?? true) !== (previousShowReasoning ?? true)
|
|
4193
|
+
|| (config.features?.showToolCalls ?? true) !== (previousShowToolCalls ?? true)
|
|
4194
|
+
|| JSON.stringify(config.features?.toolCallDisplay) !== JSON.stringify(previousToolCallDisplay)
|
|
4195
|
+
|| JSON.stringify(config.features?.reasoningDisplay) !== JSON.stringify(previousReasoningDisplay);
|
|
4096
4196
|
const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged
|
|
4097
|
-
|| loadingIndicatorChanged || iterationDisplayChanged;
|
|
4197
|
+
|| loadingIndicatorChanged || iterationDisplayChanged || featuresChanged;
|
|
4098
4198
|
if (messagesConfigChanged && session) {
|
|
4099
4199
|
configVersion++;
|
|
4100
4200
|
renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);
|
|
@@ -90,6 +90,23 @@ describe("computeMessageFingerprint", () => {
|
|
|
90
90
|
expect(fp1).not.toBe(fp2);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
it("changes when toolCall chunks change", () => {
|
|
94
|
+
const fp1 = computeMessageFingerprint(
|
|
95
|
+
makeMessage({ toolCall: { status: "running", chunks: ["Loaded tools"] } }),
|
|
96
|
+
0
|
|
97
|
+
);
|
|
98
|
+
const fp2 = computeMessageFingerprint(
|
|
99
|
+
makeMessage({
|
|
100
|
+
toolCall: {
|
|
101
|
+
status: "running",
|
|
102
|
+
chunks: ["Loaded tools", "\nFetched platform documentation"],
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
0
|
|
106
|
+
);
|
|
107
|
+
expect(fp1).not.toBe(fp2);
|
|
108
|
+
});
|
|
109
|
+
|
|
93
110
|
it("changes when reasoning chunks change", () => {
|
|
94
111
|
const fp1 = computeMessageFingerprint(makeMessage({ reasoning: { chunks: ["step 1"] } }), 0);
|
|
95
112
|
const fp2 = computeMessageFingerprint(makeMessage({ reasoning: { chunks: ["step 1", "step 2"] } }), 0);
|
|
@@ -16,7 +16,12 @@ export type FingerprintableMessage = {
|
|
|
16
16
|
rawContent?: string;
|
|
17
17
|
llmContent?: string;
|
|
18
18
|
approval?: { status?: string; [key: string]: unknown };
|
|
19
|
-
toolCall?: {
|
|
19
|
+
toolCall?: {
|
|
20
|
+
status?: string;
|
|
21
|
+
chunks?: string[];
|
|
22
|
+
args?: unknown;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
20
25
|
reasoning?: { chunks?: string[]; status?: string; [key: string]: unknown };
|
|
21
26
|
contentParts?: unknown[];
|
|
22
27
|
};
|
|
@@ -48,6 +53,13 @@ export function computeMessageFingerprint(
|
|
|
48
53
|
message.llmContent?.length ?? 0,
|
|
49
54
|
message.approval?.status ?? "",
|
|
50
55
|
message.toolCall?.status ?? "",
|
|
56
|
+
message.toolCall?.chunks?.length ?? 0,
|
|
57
|
+
message.toolCall?.chunks?.[message.toolCall.chunks.length - 1]?.slice(-32) ?? "",
|
|
58
|
+
typeof message.toolCall?.args === "string"
|
|
59
|
+
? message.toolCall.args.length
|
|
60
|
+
: message.toolCall?.args
|
|
61
|
+
? JSON.stringify(message.toolCall.args).length
|
|
62
|
+
: 0,
|
|
51
63
|
message.reasoning?.chunks?.length ?? 0,
|
|
52
64
|
message.contentParts?.length ?? 0,
|
|
53
65
|
configVersion,
|