@runtypelabs/persona 3.9.0 → 3.9.2

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.
@@ -124,7 +124,7 @@ function deepMerge(base, override) {
124
124
  var DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
125
125
  var DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
126
126
  var DEFAULT_WIDGET_CONFIG = {
127
- apiUrl: "http://localhost:43111/api/chat/dispatch",
127
+ apiUrl: "https://api.runtype.com/api/chat/dispatch",
128
128
  // Client token mode defaults (optional, only used when clientToken is set)
129
129
  clientToken: void 0,
130
130
  theme: void 0,
@@ -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) {
@@ -10170,7 +10182,7 @@ var createToolBubble = (message, config) => {
10170
10182
  if (toolCallConfig.codeBlockTextColor) {
10171
10183
  logsPre.style.color = toolCallConfig.codeBlockTextColor;
10172
10184
  }
10173
- logsPre.textContent = tool.chunks.join("\n");
10185
+ logsPre.textContent = tool.chunks.join("");
10174
10186
  logsBlock.append(logsLabel, logsPre);
10175
10187
  content.appendChild(logsBlock);
10176
10188
  }
@@ -19,7 +19,7 @@ function deepMerge(base, override) {
19
19
  var DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
20
20
  var DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
21
21
  var DEFAULT_WIDGET_CONFIG = {
22
- apiUrl: "http://localhost:43111/api/chat/dispatch",
22
+ apiUrl: "https://api.runtype.com/api/chat/dispatch",
23
23
  // Client token mode defaults (optional, only used when clientToken is set)
24
24
  clientToken: void 0,
25
25
  theme: void 0,
@@ -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) {
@@ -10065,7 +10077,7 @@ var createToolBubble = (message, config) => {
10065
10077
  if (toolCallConfig.codeBlockTextColor) {
10066
10078
  logsPre.style.color = toolCallConfig.codeBlockTextColor;
10067
10079
  }
10068
- logsPre.textContent = tool.chunks.join("\n");
10080
+ logsPre.textContent = tool.chunks.join("");
10069
10081
  logsBlock.append(logsLabel, logsPre);
10070
10082
  content.appendChild(logsBlock);
10071
10083
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
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) {
@@ -215,7 +215,7 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
215
215
  if (toolCallConfig.codeBlockTextColor) {
216
216
  logsPre.style.color = toolCallConfig.codeBlockTextColor;
217
217
  }
218
- logsPre.textContent = tool.chunks.join("\n");
218
+ logsPre.textContent = tool.chunks.join("");
219
219
  logsBlock.append(logsLabel, logsPre);
220
220
  content.appendChild(logsBlock);
221
221
  }
package/src/defaults.ts CHANGED
@@ -17,7 +17,7 @@ export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
17
17
  * Single source of truth for all default values
18
18
  */
19
19
  export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
20
- apiUrl: "http://localhost:43111/api/chat/dispatch",
20
+ apiUrl: "https://api.runtype.com/api/chat/dispatch",
21
21
  // Client token mode defaults (optional, only used when clientToken is set)
22
22
  clientToken: undefined,
23
23
  theme: undefined,