@runtypelabs/persona 3.16.0 → 3.18.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.
Files changed (71) hide show
  1. package/README.md +142 -0
  2. package/dist/animations/glyph-cycle.cjs +279 -0
  3. package/dist/animations/glyph-cycle.d.cts +5 -0
  4. package/dist/animations/glyph-cycle.d.ts +5 -0
  5. package/dist/animations/glyph-cycle.js +252 -0
  6. package/dist/animations/types-cwY5HaFD.d.cts +307 -0
  7. package/dist/animations/types-cwY5HaFD.d.ts +307 -0
  8. package/dist/animations/wipe.cjs +107 -0
  9. package/dist/animations/wipe.d.cts +5 -0
  10. package/dist/animations/wipe.d.ts +5 -0
  11. package/dist/animations/wipe.js +80 -0
  12. package/dist/index.cjs +49 -48
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +504 -1
  15. package/dist/index.d.ts +504 -1
  16. package/dist/index.global.js +143 -88
  17. package/dist/index.global.js.map +1 -1
  18. package/dist/index.js +49 -48
  19. package/dist/index.js.map +1 -1
  20. package/dist/testing.cjs +85 -0
  21. package/dist/testing.d.cts +39 -0
  22. package/dist/testing.d.ts +39 -0
  23. package/dist/testing.js +56 -0
  24. package/dist/theme-editor.cjs +2095 -207
  25. package/dist/theme-editor.d.cts +432 -2
  26. package/dist/theme-editor.d.ts +432 -2
  27. package/dist/theme-editor.js +2093 -207
  28. package/dist/theme-reference.cjs +1 -1
  29. package/dist/theme-reference.d.cts +14 -0
  30. package/dist/theme-reference.d.ts +14 -0
  31. package/dist/widget.css +565 -0
  32. package/package.json +20 -3
  33. package/src/animations/glyph-cycle.ts +332 -0
  34. package/src/animations/wipe.ts +66 -0
  35. package/src/client.test.ts +275 -0
  36. package/src/client.ts +99 -0
  37. package/src/components/ask-user-question-bubble.test.ts +583 -0
  38. package/src/components/ask-user-question-bubble.ts +924 -0
  39. package/src/components/composer-builder.ts +61 -10
  40. package/src/components/message-bubble.test.ts +181 -2
  41. package/src/components/message-bubble.ts +209 -14
  42. package/src/components/messages.ts +33 -1
  43. package/src/components/panel.ts +45 -5
  44. package/src/defaults.ts +37 -0
  45. package/src/index-global.ts +31 -0
  46. package/src/index.ts +34 -1
  47. package/src/plugins/types.ts +57 -0
  48. package/src/session.test.ts +276 -1
  49. package/src/session.ts +247 -3
  50. package/src/styles/widget.css +565 -0
  51. package/src/testing/index.ts +11 -0
  52. package/src/testing/mock-stream.test.ts +80 -0
  53. package/src/testing/mock-stream.ts +94 -0
  54. package/src/testing.ts +2 -0
  55. package/src/theme-editor/index.ts +4 -0
  56. package/src/theme-editor/preview-utils.test.ts +60 -0
  57. package/src/theme-editor/preview-utils.ts +129 -0
  58. package/src/theme-editor/sections.test.ts +19 -0
  59. package/src/theme-editor/sections.ts +84 -1
  60. package/src/types/theme.ts +15 -0
  61. package/src/types.ts +360 -0
  62. package/src/ui.ask-user-question-plugin.test.ts +649 -0
  63. package/src/ui.stop-button.test.ts +165 -0
  64. package/src/ui.ts +706 -11
  65. package/src/utils/message-fingerprint.ts +2 -0
  66. package/src/utils/morph.ts +7 -0
  67. package/src/utils/storage.ts +10 -2
  68. package/src/utils/stream-animation.test.ts +417 -0
  69. package/src/utils/stream-animation.ts +449 -0
  70. package/src/utils/theme.test.ts +36 -0
  71. package/src/utils/tokens.ts +23 -0
package/src/client.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  AgentWidgetHeadersFunction,
14
14
  AgentWidgetSSEEventResult as _AgentWidgetSSEEventResult,
15
15
  AgentExecutionState,
16
+ StopReasonKind,
16
17
  ClientSession,
17
18
  ClientInitResponse,
18
19
  ClientChatRequest,
@@ -774,6 +775,49 @@ export class AgentWidgetClient {
774
775
  });
775
776
  }
776
777
 
778
+ /**
779
+ * Resume a paused flow execution by supplying outputs for LOCAL
780
+ * (client-executed) tools. Used by the built-in `ask_user_question`
781
+ * answer-pill sheet, but generic enough for any LOCAL tool.
782
+ *
783
+ * Posts to the upstream `/resume` endpoint (the dispatch URL with
784
+ * `/dispatch` replaced by `/resume` — works for both direct-to-Runtype
785
+ * and the persona proxy) and returns the raw Response so the caller can
786
+ * pipe its SSE body through `connectStream()`.
787
+ *
788
+ * @param executionId - The paused execution id carried on `step_await`.
789
+ * @param toolOutputs - Map keyed by tool name → the tool's result value.
790
+ */
791
+ public async resumeFlow(
792
+ executionId: string,
793
+ toolOutputs: Record<string, unknown>,
794
+ options?: { streamResponse?: boolean }
795
+ ): Promise<Response> {
796
+ const trimmed = this.config.apiUrl?.replace(/\/+$/, '') || DEFAULT_CLIENT_API_BASE;
797
+ // Runtype mounts POST /resume as a child route of /v1/dispatch, so the
798
+ // final URL is always `${apiUrl}/resume`. Proxies should follow the
799
+ // same shape (`/api/chat/dispatch/resume`) to keep the widget agnostic.
800
+ const url = `${trimmed}/resume`;
801
+
802
+ let headers: Record<string, string> = {
803
+ 'Content-Type': 'application/json',
804
+ ...this.headers
805
+ };
806
+ if (this.getHeaders) {
807
+ Object.assign(headers, await this.getHeaders());
808
+ }
809
+
810
+ return fetch(url, {
811
+ method: 'POST',
812
+ headers,
813
+ body: JSON.stringify({
814
+ executionId,
815
+ toolOutputs,
816
+ streamResponse: options?.streamResponse ?? true,
817
+ }),
818
+ });
819
+ }
820
+
777
821
  private async buildAgentPayload(
778
822
  messages: AgentWidgetMessage[]
779
823
  ): Promise<AgentWidgetAgentRequestPayload> {
@@ -1721,6 +1765,34 @@ export class AgentWidgetClient {
1721
1765
  if (callKey) {
1722
1766
  toolContext.byCall.delete(callKey);
1723
1767
  }
1768
+ } else if (payloadType === "step_await" && payload.awaitReason === "local_tool_required" && payload.toolName) {
1769
+ // LOCAL tool pause. Runtype's prompt step throws LocalToolRequiredError
1770
+ // when the model calls a tool with `toolType: "local"`. The server
1771
+ // emits step_await with the tool name, params, and execution id; the
1772
+ // execution pauses until the client POSTs /resume with toolOutputs.
1773
+ //
1774
+ // Upsert a fully-populated tool-variant message so the existing
1775
+ // ask_user_question bubble + sheet paths fire. Mark the message with
1776
+ // `awaitingLocalTool: true` so the UI knows to resolve via
1777
+ // resumeFlow rather than the legacy sendMessage fallback.
1778
+ const toolId = (payload.toolId as string) ?? `local-${nextSequence()}`;
1779
+ const toolMessage = ensureToolMessage(toolId);
1780
+ const tool = toolMessage.toolCall ?? { id: toolId, status: "pending" as const };
1781
+ tool.name = payload.toolName as string;
1782
+ tool.args = payload.parameters;
1783
+ tool.status = "complete";
1784
+ tool.chunks = tool.chunks ?? [];
1785
+ tool.startedAt =
1786
+ tool.startedAt ?? resolveTimestamp(payload.startedAt ?? payload.timestamp);
1787
+ tool.completedAt = tool.completedAt ?? tool.startedAt;
1788
+ toolMessage.toolCall = tool;
1789
+ toolMessage.streaming = false;
1790
+ toolMessage.agentMetadata = {
1791
+ ...toolMessage.agentMetadata,
1792
+ executionId: (payload.executionId as string) ?? toolMessage.agentMetadata?.executionId,
1793
+ awaitingLocalTool: true,
1794
+ };
1795
+ emitMessage(toolMessage);
1724
1796
  } else if (payloadType === "text_start") {
1725
1797
  // Lifecycle event: a new text segment is beginning (emitted at tool boundaries).
1726
1798
  // When toolContext is present this fired inside a nested flow — it must not
@@ -2094,11 +2166,20 @@ export class AgentWidgetClient {
2094
2166
  continue;
2095
2167
  }
2096
2168
 
2169
+ // Capture optional per-step stopReason emitted by the runtime
2170
+ // (e.g. `'max_tool_calls'`, `'length'`). This is the dispatch-mode
2171
+ // fallback — `agent_turn_complete` will overwrite it later in
2172
+ // agent-loop streams.
2173
+ const stepStopReason = (payload as any).stopReason as
2174
+ | StopReasonKind
2175
+ | undefined;
2176
+
2097
2177
  if (didSplitByPartId) {
2098
2178
  // Sealed segment(s) — do not create a second bubble from step_complete.
2099
2179
  // Merge authoritative final response into the last sealed segment (fixes async lag).
2100
2180
  if (assistantMessage !== null) {
2101
2181
  const msg: AgentWidgetMessage = assistantMessage;
2182
+ if (stepStopReason) msg.stopReason = stepStopReason;
2102
2183
  streamParsers.delete(msg.id);
2103
2184
  rawContentBuffers.delete(msg.id);
2104
2185
  if (msg.streaming !== false) {
@@ -2109,6 +2190,7 @@ export class AgentWidgetClient {
2109
2190
  const splitFinalContent = payload.result?.response;
2110
2191
  const sealedForReconcile = lastSealedTextSegment;
2111
2192
  if (sealedForReconcile) {
2193
+ if (stepStopReason) sealedForReconcile.stopReason = stepStopReason;
2112
2194
  if (splitFinalContent !== undefined && splitFinalContent !== null) {
2113
2195
  reconcileSealedAssistantWithFinalResponse(sealedForReconcile, splitFinalContent);
2114
2196
  } else {
@@ -2121,6 +2203,7 @@ export class AgentWidgetClient {
2121
2203
  }
2122
2204
  const finalContent = payload.result?.response;
2123
2205
  const assistant = ensureAssistantMessage();
2206
+ if (stepStopReason) assistant.stopReason = stepStopReason;
2124
2207
  if (finalContent !== undefined && finalContent !== null) {
2125
2208
  // Check if we already have extracted text from streaming
2126
2209
  const parser = streamParsers.get(assistant.id);
@@ -2408,6 +2491,22 @@ export class AgentWidgetClient {
2408
2491
  emitMessage(reasoningMessage);
2409
2492
  }
2410
2493
  }
2494
+
2495
+ // Attach the turn-level stopReason to the assistant message
2496
+ // produced by this turn. Only overwrite the current message —
2497
+ // prior turns already sealed their own stopReason via step_complete.
2498
+ const turnStopReason = (payload as any).stopReason as
2499
+ | StopReasonKind
2500
+ | undefined;
2501
+ if (turnStopReason && assistantMessage !== null) {
2502
+ const turnId = payload.turnId;
2503
+ const matchesTurn =
2504
+ !turnId || assistantMessage.agentMetadata?.turnId === turnId;
2505
+ if (matchesTurn) {
2506
+ assistantMessage.stopReason = turnStopReason;
2507
+ emitMessage(assistantMessage);
2508
+ }
2509
+ }
2411
2510
  } else if (payloadType === "agent_tool_start") {
2412
2511
  const toolId = payload.toolCallId ?? `agent-tool-${nextSequence()}`;
2413
2512
  trackToolId(getToolCallKey(payload), toolId);