@runtypelabs/persona 3.25.0 → 3.26.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 +25 -0
- package/dist/index.cjs +40 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +51 -51
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +39 -39
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +37 -37
- package/dist/theme-editor.js +35 -35
- package/package.json +1 -1
- package/src/client.test.ts +49 -0
- package/src/client.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.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/client.test.ts
CHANGED
|
@@ -1633,6 +1633,55 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1633
1633
|
expect(seqTool).toBeLessThan(seq1);
|
|
1634
1634
|
});
|
|
1635
1635
|
|
|
1636
|
+
it('should not emit a whitespace-only assistant bubble before a leading tool call', async () => {
|
|
1637
|
+
const events: AgentWidgetEvent[] = [];
|
|
1638
|
+
|
|
1639
|
+
const encoder = new TextEncoder();
|
|
1640
|
+
global.fetch = vi.fn().mockImplementation(async () => {
|
|
1641
|
+
const stream = new ReadableStream({
|
|
1642
|
+
start(controller) {
|
|
1643
|
+
const e = (eventType: string, data: Record<string, unknown>) =>
|
|
1644
|
+
controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${JSON.stringify({ type: eventType, ...data })}\n\n`));
|
|
1645
|
+
|
|
1646
|
+
e('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 });
|
|
1647
|
+
e('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
|
|
1648
|
+
// Tool UI is the first meaningful output. Some providers still emit
|
|
1649
|
+
// newline-only text lifecycle events around the tool boundary; those
|
|
1650
|
+
// must not become an empty assistant message bubble.
|
|
1651
|
+
e('tool_start', { toolId: 'tc_1', name: 'add_to_cart', toolType: 'local', sequenceIndex: 4 });
|
|
1652
|
+
e('text_start', { partId: 'text_0', messageId: 'msg_s1', seq: 1 });
|
|
1653
|
+
e('step_delta', { id: 's1', text: '\n', partId: 'text_0', messageId: 'msg_s1', seq: 2 });
|
|
1654
|
+
e('text_end', { partId: 'text_0', messageId: 'msg_s1', seq: 3 });
|
|
1655
|
+
e('tool_complete', { toolId: 'tc_1', name: 'add_to_cart', success: true, completedAt: new Date().toISOString(), executionTime: 20, sequenceIndex: 5 });
|
|
1656
|
+
e('flow_complete', { success: true });
|
|
1657
|
+
controller.close();
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
return { ok: true, body: stream };
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
1664
|
+
await client.dispatch(
|
|
1665
|
+
{ messages: [{ id: 'usr_1', role: 'user', content: 'Add to cart', createdAt: new Date().toISOString() }] },
|
|
1666
|
+
(event) => events.push(event)
|
|
1667
|
+
);
|
|
1668
|
+
|
|
1669
|
+
const messageEvents = events.filter(e => e.type === 'message');
|
|
1670
|
+
const messagesById = new Map<string, AgentWidgetMessage>();
|
|
1671
|
+
for (const event of messageEvents) {
|
|
1672
|
+
if (event.type === 'message') messagesById.set(event.message.id, event.message);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const allMessages = Array.from(messagesById.values());
|
|
1676
|
+
const assistantTexts = allMessages.filter(m => m.role === 'assistant' && !m.variant);
|
|
1677
|
+
const toolMsgs = allMessages.filter(m => m.variant === 'tool');
|
|
1678
|
+
|
|
1679
|
+
expect(assistantTexts).toHaveLength(0);
|
|
1680
|
+
expect(toolMsgs).toHaveLength(1);
|
|
1681
|
+
expect(toolMsgs[0].toolCall?.name).toBe('add_to_cart');
|
|
1682
|
+
expect(toolMsgs[0].toolCall?.status).toBe('complete');
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1636
1685
|
it('should not split when partId is absent (backward compatible)', async () => {
|
|
1637
1686
|
const events: AgentWidgetEvent[] = [];
|
|
1638
1687
|
|
package/src/client.ts
CHANGED
|
@@ -1297,7 +1297,25 @@ export class AgentWidgetClient {
|
|
|
1297
1297
|
};
|
|
1298
1298
|
};
|
|
1299
1299
|
|
|
1300
|
+
const shouldEmitMessage = (msg: AgentWidgetMessage): boolean => {
|
|
1301
|
+
if (msg.role !== "assistant" || msg.variant) return true;
|
|
1302
|
+
|
|
1303
|
+
const hasContentParts =
|
|
1304
|
+
Array.isArray(msg.contentParts) && msg.contentParts.length > 0;
|
|
1305
|
+
const hasRawContent =
|
|
1306
|
+
typeof msg.rawContent === "string" && msg.rawContent.trim() !== "";
|
|
1307
|
+
const hasVisibleText =
|
|
1308
|
+
typeof msg.content === "string" && msg.content.trim() !== "";
|
|
1309
|
+
|
|
1310
|
+
// Do not surface assistant text bubbles that only contain whitespace.
|
|
1311
|
+
// Some providers emit newline-only text parts around a leading tool call;
|
|
1312
|
+
// rendering those as normal messages creates an empty bubble above the
|
|
1313
|
+
// tool card. Keep media/component/stop-reason messages renderable.
|
|
1314
|
+
return hasVisibleText || hasContentParts || hasRawContent || Boolean(msg.stopReason);
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1300
1317
|
const emitMessage = (msg: AgentWidgetMessage) => {
|
|
1318
|
+
if (!shouldEmitMessage(msg)) return;
|
|
1301
1319
|
onEvent({
|
|
1302
1320
|
type: "message",
|
|
1303
1321
|
message: cloneMessage(msg)
|