@runtypelabs/persona 3.9.0 → 3.9.1

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.
@@ -4842,6 +4842,18 @@ var AgentWidgetClient = class {
4842
4842
  if (stepType === "tool" || executionType === "context") {
4843
4843
  continue;
4844
4844
  }
4845
+ if (didSplitByPartId) {
4846
+ if (assistantMessage !== null) {
4847
+ const msg = assistantMessage;
4848
+ streamParsers.delete(msg.id);
4849
+ rawContentBuffers.delete(msg.id);
4850
+ if (msg.streaming !== false) {
4851
+ msg.streaming = false;
4852
+ emitMessage(msg);
4853
+ }
4854
+ }
4855
+ continue;
4856
+ }
4845
4857
  const finalContent = (_ja = payload.result) == null ? void 0 : _ja.response;
4846
4858
  const assistant = ensureAssistantMessage();
4847
4859
  if (finalContent !== void 0 && finalContent !== null) {
@@ -4737,6 +4737,18 @@ var AgentWidgetClient = class {
4737
4737
  if (stepType === "tool" || executionType === "context") {
4738
4738
  continue;
4739
4739
  }
4740
+ if (didSplitByPartId) {
4741
+ if (assistantMessage !== null) {
4742
+ const msg = assistantMessage;
4743
+ streamParsers.delete(msg.id);
4744
+ rawContentBuffers.delete(msg.id);
4745
+ if (msg.streaming !== false) {
4746
+ msg.streaming = false;
4747
+ emitMessage(msg);
4748
+ }
4749
+ }
4750
+ continue;
4751
+ }
4740
4752
  const finalContent = (_ja = payload.result) == null ? void 0 : _ja.response;
4741
4753
  const assistant = ensureAssistantMessage();
4742
4754
  if (finalContent !== void 0 && finalContent !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
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",
@@ -1784,5 +1784,59 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1784
1784
  expect(assistantTexts[0].streaming).toBe(false);
1785
1785
  expect(assistantTexts[1].streaming).toBe(false);
1786
1786
  });
1787
+
1788
+ it('should not duplicate text when step_complete follows text_end', async () => {
1789
+ const events: AgentWidgetEvent[] = [];
1790
+
1791
+ const encoder = new TextEncoder();
1792
+ global.fetch = vi.fn().mockImplementation(async () => {
1793
+ const stream = new ReadableStream({
1794
+ start(controller) {
1795
+ const e = (eventType: string, data: Record<string, unknown>) =>
1796
+ controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${JSON.stringify({ type: eventType, ...data })}\n\n`));
1797
+
1798
+ e('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 });
1799
+ // Tools fire first (no text before them)
1800
+ e('tool_start', { toolId: 'tc_1', name: 'test_tool', toolType: 'custom', startedAt: new Date().toISOString() });
1801
+ e('tool_complete', { toolId: 'tc_1', name: 'test_tool', success: true, completedAt: new Date().toISOString(), executionTime: 0 });
1802
+ // Then text segment
1803
+ e('text_start', { partId: 'text_1', messageId: 'msg_s1', seq: 1 });
1804
+ e('step_delta', { id: 's1', text: 'Tool returned a result.', partId: 'text_1', messageId: 'msg_s1', seq: 2 });
1805
+ e('text_end', { partId: 'text_1', messageId: 'msg_s1', seq: 3 });
1806
+ // step_complete with full response (should NOT create a duplicate)
1807
+ e('step_complete', { id: 's1', name: 'Response', stepType: 'prompt', success: true, result: { response: 'Tool returned a result.' }, executionTime: 500 });
1808
+ e('flow_complete', { success: true });
1809
+ controller.close();
1810
+ }
1811
+ });
1812
+ return { ok: true, body: stream };
1813
+ });
1814
+
1815
+ const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
1816
+ await client.dispatch(
1817
+ { messages: [{ id: 'usr_1', role: 'user', content: 'Call tool', createdAt: new Date().toISOString() }] },
1818
+ (event) => events.push(event)
1819
+ );
1820
+
1821
+ const messageEvents = events.filter(e => e.type === 'message');
1822
+ const messagesById = new Map<string, AgentWidgetMessage>();
1823
+ for (const event of messageEvents) {
1824
+ if (event.type === 'message') messagesById.set(event.message.id, event.message);
1825
+ }
1826
+
1827
+ const allMessages = Array.from(messagesById.values());
1828
+ const assistantTexts = allMessages
1829
+ .filter(m => m.role === 'assistant' && !m.variant)
1830
+ .sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
1831
+ const toolMsgs = allMessages.filter(m => m.variant === 'tool');
1832
+
1833
+ // Exactly ONE text message (not duplicated by step_complete)
1834
+ expect(assistantTexts.length).toBe(1);
1835
+ expect(assistantTexts[0].content).toBe('Tool returned a result.');
1836
+ expect(assistantTexts[0].streaming).toBe(false);
1837
+
1838
+ // Tool message exists
1839
+ expect(toolMsgs.length).toBe(1);
1840
+ });
1787
1841
  });
1788
1842
 
package/src/client.ts CHANGED
@@ -1757,6 +1757,20 @@ export class AgentWidgetClient {
1757
1757
  // Skip tool-related completions - they're handled by tool_complete
1758
1758
  continue;
1759
1759
  }
1760
+ if (didSplitByPartId) {
1761
+ // text_end already sealed the assistant message(s) — don't recreate
1762
+ // one from step_complete's full response (would cause duplication)
1763
+ if (assistantMessage !== null) {
1764
+ const msg: AgentWidgetMessage = assistantMessage;
1765
+ streamParsers.delete(msg.id);
1766
+ rawContentBuffers.delete(msg.id);
1767
+ if (msg.streaming !== false) {
1768
+ msg.streaming = false;
1769
+ emitMessage(msg);
1770
+ }
1771
+ }
1772
+ continue;
1773
+ }
1760
1774
  const finalContent = payload.result?.response;
1761
1775
  const assistant = ensureAssistantMessage();
1762
1776
  if (finalContent !== undefined && finalContent !== null) {