@runtypelabs/persona 3.15.1 → 3.16.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 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.global.js +61 -61
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -46
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +169 -64
- package/dist/theme-editor.d.cts +11 -0
- package/dist/theme-editor.d.ts +11 -0
- package/dist/theme-editor.js +169 -64
- package/package.json +1 -1
- package/src/client.ts +169 -2
- package/src/components/header-builder.ts +18 -7
- package/src/components/header-layouts.ts +3 -1
- package/src/defaults.ts +6 -0
- package/src/types.ts +11 -0
- package/src/ui.ts +4 -2
package/src/client.ts
CHANGED
|
@@ -1057,6 +1057,16 @@ export class AgentWidgetClient {
|
|
|
1057
1057
|
let didSplitByPartId = false;
|
|
1058
1058
|
const reasoningMessages = new Map<string, AgentWidgetMessage>();
|
|
1059
1059
|
const toolMessages = new Map<string, AgentWidgetMessage>();
|
|
1060
|
+
// Messages produced by steps inside a nested flow executed as a tool.
|
|
1061
|
+
// Keyed by `${parentToolId}::${nestedStepId}::${partId}` so each nested
|
|
1062
|
+
// step (send-stream, prompt) gets its own assistant message, and prompts
|
|
1063
|
+
// with inner tool calls split into one message per text segment — still
|
|
1064
|
+
// attributable to the parent tool call.
|
|
1065
|
+
const nestedStepMessages = new Map<string, AgentWidgetMessage>();
|
|
1066
|
+
// Most-recent partId seen for a given `${toolId}::${stepId}` scope, used
|
|
1067
|
+
// to seal the previous segment when a new partId arrives within the
|
|
1068
|
+
// same nested prompt step.
|
|
1069
|
+
const nestedPartIdByStep = new Map<string, string>();
|
|
1060
1070
|
const reasoningContext = {
|
|
1061
1071
|
lastId: null as string | null,
|
|
1062
1072
|
byStep: new Map<string, string>()
|
|
@@ -1066,6 +1076,49 @@ export class AgentWidgetClient {
|
|
|
1066
1076
|
byCall: new Map<string, string>()
|
|
1067
1077
|
};
|
|
1068
1078
|
|
|
1079
|
+
// Nested message key. partId defaults to "" so steps without segmentation
|
|
1080
|
+
// (e.g. send-stream) still have a deterministic single key.
|
|
1081
|
+
const getNestedStepKey = (
|
|
1082
|
+
toolId: string,
|
|
1083
|
+
stepId: string,
|
|
1084
|
+
partId: string = ""
|
|
1085
|
+
) => `${toolId}::${stepId}::${partId}`;
|
|
1086
|
+
|
|
1087
|
+
// Prefix used to sweep every nested message belonging to a single
|
|
1088
|
+
// (toolId, stepId) scope — needed on step_complete to seal any segments
|
|
1089
|
+
// that are still streaming.
|
|
1090
|
+
const getNestedStepPrefix = (toolId: string, stepId: string) =>
|
|
1091
|
+
`${toolId}::${stepId}::`;
|
|
1092
|
+
|
|
1093
|
+
const ensureNestedStepMessage = (
|
|
1094
|
+
toolId: string,
|
|
1095
|
+
stepId: string,
|
|
1096
|
+
partId: string,
|
|
1097
|
+
executionId?: string
|
|
1098
|
+
): AgentWidgetMessage => {
|
|
1099
|
+
const key = getNestedStepKey(toolId, stepId, partId);
|
|
1100
|
+
const existing = nestedStepMessages.get(key);
|
|
1101
|
+
if (existing) return existing;
|
|
1102
|
+
const idSuffix = partId ? `-${partId}` : "";
|
|
1103
|
+
const message: AgentWidgetMessage = {
|
|
1104
|
+
id: `nested-${toolId}-${stepId}${idSuffix}`,
|
|
1105
|
+
role: "assistant",
|
|
1106
|
+
content: "",
|
|
1107
|
+
createdAt: new Date().toISOString(),
|
|
1108
|
+
streaming: true,
|
|
1109
|
+
sequence: nextSequence(),
|
|
1110
|
+
...(partId ? { partId } : {}),
|
|
1111
|
+
agentMetadata: {
|
|
1112
|
+
executionId,
|
|
1113
|
+
parentToolId: toolId,
|
|
1114
|
+
parentStepId: stepId,
|
|
1115
|
+
},
|
|
1116
|
+
};
|
|
1117
|
+
nestedStepMessages.set(key, message);
|
|
1118
|
+
emitMessage(message);
|
|
1119
|
+
return message;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1069
1122
|
const normalizeKey = (value: unknown): string | null => {
|
|
1070
1123
|
if (value === null || value === undefined) return null;
|
|
1071
1124
|
try {
|
|
@@ -1669,7 +1722,13 @@ export class AgentWidgetClient {
|
|
|
1669
1722
|
toolContext.byCall.delete(callKey);
|
|
1670
1723
|
}
|
|
1671
1724
|
} else if (payloadType === "text_start") {
|
|
1672
|
-
// Lifecycle event: a new text segment is beginning (emitted at tool boundaries)
|
|
1725
|
+
// Lifecycle event: a new text segment is beginning (emitted at tool boundaries).
|
|
1726
|
+
// When toolContext is present this fired inside a nested flow — it must not
|
|
1727
|
+
// seal or rotate the outer assistant message. Nested prompt segmentation is
|
|
1728
|
+
// handled via nestedStepMessages keyed by (toolId, stepId).
|
|
1729
|
+
if ((payload as any).toolContext?.toolId) {
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1673
1732
|
const incomingPartId = payload.partId;
|
|
1674
1733
|
if (incomingPartId !== undefined && partIdState.current !== null && incomingPartId !== partIdState.current) {
|
|
1675
1734
|
const prev = assistantMessage as AgentWidgetMessage | null;
|
|
@@ -1685,7 +1744,13 @@ export class AgentWidgetClient {
|
|
|
1685
1744
|
partIdState.current = incomingPartId;
|
|
1686
1745
|
}
|
|
1687
1746
|
} else if (payloadType === "text_end") {
|
|
1688
|
-
// Lifecycle event: current text segment ended (tool call about to start)
|
|
1747
|
+
// Lifecycle event: current text segment ended (tool call about to start).
|
|
1748
|
+
// When toolContext is present the boundary belongs to a nested flow — leave
|
|
1749
|
+
// outer assistant state alone so the outer stream is never interrupted by
|
|
1750
|
+
// nested activity.
|
|
1751
|
+
if ((payload as any).toolContext?.toolId) {
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1689
1754
|
// Seal the current assistant message so the next segment gets a new one
|
|
1690
1755
|
const prev = assistantMessage as AgentWidgetMessage | null;
|
|
1691
1756
|
if (prev) {
|
|
@@ -1704,6 +1769,77 @@ export class AgentWidgetClient {
|
|
|
1704
1769
|
continue;
|
|
1705
1770
|
}
|
|
1706
1771
|
|
|
1772
|
+
// Nested flow routing: when toolContext is present, this step_delta
|
|
1773
|
+
// originated inside a nested flow executed as a tool. Surface it as
|
|
1774
|
+
// its own assistant message keyed by the nested step id, so authors
|
|
1775
|
+
// who add send-stream / prompt steps inside their flow see them as
|
|
1776
|
+
// real messages in the timeline, in order — rather than merging
|
|
1777
|
+
// into the outer assistant bubble or getting buried in the tool
|
|
1778
|
+
// card. Each nested step id gets its own message; the parent tool
|
|
1779
|
+
// bubble continues to represent the invocation via tool_* events.
|
|
1780
|
+
const nestedToolCtx = (payload as any).toolContext as
|
|
1781
|
+
| { toolId?: string; stepId?: string; executionId?: string }
|
|
1782
|
+
| undefined;
|
|
1783
|
+
if (nestedToolCtx?.toolId) {
|
|
1784
|
+
const nestedStepId = String(
|
|
1785
|
+
payload.id ?? nestedToolCtx.stepId ?? `step-${nextSequence()}`
|
|
1786
|
+
);
|
|
1787
|
+
const incomingPartId =
|
|
1788
|
+
payload.partId !== undefined && payload.partId !== null
|
|
1789
|
+
? String(payload.partId)
|
|
1790
|
+
: "";
|
|
1791
|
+
const stepScopeKey = `${nestedToolCtx.toolId}::${nestedStepId}`;
|
|
1792
|
+
const prevPartId = nestedPartIdByStep.get(stepScopeKey);
|
|
1793
|
+
|
|
1794
|
+
// If partId changed within this nested step (prompt with inner
|
|
1795
|
+
// tool call emitting a new text segment), seal the previous
|
|
1796
|
+
// segment's message so each segment renders as its own bubble.
|
|
1797
|
+
if (
|
|
1798
|
+
incomingPartId !== "" &&
|
|
1799
|
+
prevPartId !== undefined &&
|
|
1800
|
+
prevPartId !== "" &&
|
|
1801
|
+
prevPartId !== incomingPartId
|
|
1802
|
+
) {
|
|
1803
|
+
const prev = nestedStepMessages.get(
|
|
1804
|
+
getNestedStepKey(
|
|
1805
|
+
nestedToolCtx.toolId,
|
|
1806
|
+
nestedStepId,
|
|
1807
|
+
prevPartId
|
|
1808
|
+
)
|
|
1809
|
+
);
|
|
1810
|
+
if (prev && prev.streaming !== false) {
|
|
1811
|
+
prev.streaming = false;
|
|
1812
|
+
emitMessage(prev);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (incomingPartId !== "") {
|
|
1816
|
+
nestedPartIdByStep.set(stepScopeKey, incomingPartId);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const nestedMsg = ensureNestedStepMessage(
|
|
1820
|
+
nestedToolCtx.toolId,
|
|
1821
|
+
nestedStepId,
|
|
1822
|
+
incomingPartId,
|
|
1823
|
+
nestedToolCtx.executionId
|
|
1824
|
+
);
|
|
1825
|
+
const nestedChunk =
|
|
1826
|
+
payload.text ??
|
|
1827
|
+
payload.delta ??
|
|
1828
|
+
payload.content ??
|
|
1829
|
+
payload.chunk ??
|
|
1830
|
+
"";
|
|
1831
|
+
if (nestedChunk) {
|
|
1832
|
+
nestedMsg.content += String(nestedChunk);
|
|
1833
|
+
nestedMsg.streaming = true;
|
|
1834
|
+
emitMessage(nestedMsg);
|
|
1835
|
+
}
|
|
1836
|
+
if (payload.isComplete) {
|
|
1837
|
+
nestedMsg.streaming = false;
|
|
1838
|
+
emitMessage(nestedMsg);
|
|
1839
|
+
}
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1707
1843
|
// partId-based segmentation: when partId changes, seal current message
|
|
1708
1844
|
// and start a new one so text and tools render in chronological order
|
|
1709
1845
|
const incomingPartId = payload.partId;
|
|
@@ -1927,6 +2063,37 @@ export class AgentWidgetClient {
|
|
|
1927
2063
|
// Skip tool-related completions - they're handled by tool_complete
|
|
1928
2064
|
continue;
|
|
1929
2065
|
}
|
|
2066
|
+
|
|
2067
|
+
// Nested flow: seal every segment message produced by this nested
|
|
2068
|
+
// step (a single nested prompt step may have produced multiple
|
|
2069
|
+
// messages, one per partId, when inner tool calls split it). The
|
|
2070
|
+
// outer assistantMessage state is untouched so reconciliation for
|
|
2071
|
+
// the outer flow still works.
|
|
2072
|
+
const nestedCompleteCtx = (payload as any).toolContext as
|
|
2073
|
+
| { toolId?: string; stepId?: string; executionId?: string }
|
|
2074
|
+
| undefined;
|
|
2075
|
+
if (nestedCompleteCtx?.toolId) {
|
|
2076
|
+
const nestedStepId = String(
|
|
2077
|
+
payload.id ?? nestedCompleteCtx.stepId ?? ""
|
|
2078
|
+
);
|
|
2079
|
+
if (nestedStepId) {
|
|
2080
|
+
const prefix = getNestedStepPrefix(
|
|
2081
|
+
nestedCompleteCtx.toolId,
|
|
2082
|
+
nestedStepId
|
|
2083
|
+
);
|
|
2084
|
+
for (const [key, msg] of nestedStepMessages) {
|
|
2085
|
+
if (key.startsWith(prefix) && msg.streaming !== false) {
|
|
2086
|
+
msg.streaming = false;
|
|
2087
|
+
emitMessage(msg);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
nestedPartIdByStep.delete(
|
|
2091
|
+
`${nestedCompleteCtx.toolId}::${nestedStepId}`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1930
2097
|
if (didSplitByPartId) {
|
|
1931
2098
|
// Sealed segment(s) — do not create a second bubble from step_complete.
|
|
1932
2099
|
// Merge authoritative final response into the last sealed segment (fixes async lag).
|
|
@@ -159,9 +159,11 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
159
159
|
clearChatButton.style.color =
|
|
160
160
|
clearChatIconColor || HEADER_THEME_CSS.actionIconColor;
|
|
161
161
|
|
|
162
|
-
// Add icon
|
|
162
|
+
// Add icon. display:block eliminates inline-baseline spacing that can
|
|
163
|
+
// push the icon a fractional pixel off-center inside the button.
|
|
163
164
|
const iconSvg = renderLucideIcon(clearChatIconName, "20px", "currentColor", 1);
|
|
164
165
|
if (iconSvg) {
|
|
166
|
+
iconSvg.style.display = "block";
|
|
165
167
|
clearChatButton.appendChild(iconSvg);
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -276,15 +278,17 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
// Create close button wrapper for tooltip positioning
|
|
280
|
-
//
|
|
281
|
+
// Create close button wrapper for tooltip positioning.
|
|
282
|
+
// Mirrors the clear-chat wrapper's inline-flex centering so both
|
|
283
|
+
// header action buttons vertically align identically within the
|
|
284
|
+
// header's flex row.
|
|
281
285
|
const closeButtonWrapper = createElement(
|
|
282
286
|
"div",
|
|
283
287
|
closeButtonPlacement === "top-right"
|
|
284
288
|
? "persona-absolute persona-top-4 persona-right-4 persona-z-50"
|
|
285
289
|
: clearChatEnabled && clearChatPlacement === "inline"
|
|
286
|
-
? ""
|
|
287
|
-
: "persona-ml-auto"
|
|
290
|
+
? "persona-relative persona-inline-flex persona-items-center persona-justify-center"
|
|
291
|
+
: "persona-relative persona-ml-auto persona-inline-flex persona-items-center persona-justify-center"
|
|
288
292
|
);
|
|
289
293
|
|
|
290
294
|
// Create close button with base classes
|
|
@@ -309,9 +313,16 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
309
313
|
closeButton.style.color =
|
|
310
314
|
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
311
315
|
|
|
312
|
-
// Try to render Lucide icon, fallback to text if not provided or fails
|
|
313
|
-
|
|
316
|
+
// Try to render Lucide icon, fallback to text if not provided or fails.
|
|
317
|
+
// The X glyph's paths occupy only the middle 50% of its 24x24 viewBox
|
|
318
|
+
// (from 6,6 to 18,18), while other header icons (e.g. refresh-cw) span
|
|
319
|
+
// ~75% of the viewBox. Rendering X at a larger intrinsic size brings
|
|
320
|
+
// its visible extent into parity with sibling icons in the header.
|
|
321
|
+
// display:block eliminates inline-baseline spacing that can push the
|
|
322
|
+
// icon a fractional pixel off-center inside the button.
|
|
323
|
+
const closeIconSvg = renderLucideIcon(closeButtonIconName, "28px", "currentColor", 1);
|
|
314
324
|
if (closeIconSvg) {
|
|
325
|
+
closeIconSvg.style.display = "block";
|
|
315
326
|
closeButton.appendChild(closeIconSvg);
|
|
316
327
|
} else {
|
|
317
328
|
closeButton.textContent = closeButtonIconText;
|
|
@@ -215,7 +215,9 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
215
215
|
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
216
216
|
|
|
217
217
|
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
218
|
-
|
|
218
|
+
// Larger intrinsic size compensates for the X glyph's sparse viewBox
|
|
219
|
+
// (paths only occupy the middle 50%). Matches header-builder.ts.
|
|
220
|
+
const closeIconSvg = renderLucideIcon(closeButtonIconName, "28px", "currentColor", 1);
|
|
219
221
|
if (closeIconSvg) {
|
|
220
222
|
closeButton.appendChild(closeIconSvg);
|
|
221
223
|
} else {
|
package/src/defaults.ts
CHANGED
|
@@ -43,6 +43,12 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
43
43
|
agentIconSize: "40px",
|
|
44
44
|
headerIconSize: "40px",
|
|
45
45
|
closeButtonSize: "32px",
|
|
46
|
+
// Zero out browser-default <button> padding so the icon gets the full
|
|
47
|
+
// 32x32 content box, matching clearChat.paddingX/Y below. Without this,
|
|
48
|
+
// UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
|
|
49
|
+
// eats into the border-box width and shrinks the rendered icon.
|
|
50
|
+
closeButtonPaddingX: "0px",
|
|
51
|
+
closeButtonPaddingY: "0px",
|
|
46
52
|
callToActionIconName: "arrow-up-right",
|
|
47
53
|
callToActionIconText: "",
|
|
48
54
|
callToActionIconSize: "32px",
|
package/src/types.ts
CHANGED
|
@@ -194,6 +194,17 @@ export type AgentMessageMetadata = {
|
|
|
194
194
|
iteration?: number;
|
|
195
195
|
turnId?: string;
|
|
196
196
|
agentName?: string;
|
|
197
|
+
/**
|
|
198
|
+
* When this message was produced by a step inside a nested flow executed
|
|
199
|
+
* as a tool, identifies the parent tool call id. Enables renderers to
|
|
200
|
+
* visually group or indent nested-flow output under its parent tool.
|
|
201
|
+
*/
|
|
202
|
+
parentToolId?: string;
|
|
203
|
+
/**
|
|
204
|
+
* Nested flow step id that produced this message (e.g. a `send-stream`
|
|
205
|
+
* or `prompt` step inside the nested flow). Stable key for that step.
|
|
206
|
+
*/
|
|
207
|
+
parentStepId?: string;
|
|
197
208
|
};
|
|
198
209
|
|
|
199
210
|
export type AgentWidgetRequestMiddlewareContext = {
|
package/src/ui.ts
CHANGED
|
@@ -4617,9 +4617,11 @@ export const createAgentExperience = (
|
|
|
4617
4617
|
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
4618
4618
|
const closeButtonIconText = launcher.closeButtonIconText ?? "×";
|
|
4619
4619
|
|
|
4620
|
-
// Clear existing content and render new icon
|
|
4620
|
+
// Clear existing content and render new icon.
|
|
4621
|
+
// Larger intrinsic size compensates for the X glyph's sparse
|
|
4622
|
+
// viewBox so the close button visually matches sibling icons.
|
|
4621
4623
|
closeButton.innerHTML = "";
|
|
4622
|
-
const iconSvg = renderLucideIcon(closeButtonIconName, "
|
|
4624
|
+
const iconSvg = renderLucideIcon(closeButtonIconName, "28px", "currentColor", 1);
|
|
4623
4625
|
if (iconSvg) {
|
|
4624
4626
|
closeButton.appendChild(iconSvg);
|
|
4625
4627
|
} else {
|