@mariozechner/pi-web-ui 0.5.44

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 (265) hide show
  1. package/README.md +252 -0
  2. package/dist/ChatPanel.d.ts +23 -0
  3. package/dist/ChatPanel.d.ts.map +1 -0
  4. package/dist/ChatPanel.js +224 -0
  5. package/dist/ChatPanel.js.map +1 -0
  6. package/dist/app.css +2 -0
  7. package/dist/components/AgentInterface.d.ts +35 -0
  8. package/dist/components/AgentInterface.d.ts.map +1 -0
  9. package/dist/components/AgentInterface.js +308 -0
  10. package/dist/components/AgentInterface.js.map +1 -0
  11. package/dist/components/AttachmentTile.d.ts +12 -0
  12. package/dist/components/AttachmentTile.d.ts.map +1 -0
  13. package/dist/components/AttachmentTile.js +114 -0
  14. package/dist/components/AttachmentTile.js.map +1 -0
  15. package/dist/components/ConsoleBlock.d.ts +11 -0
  16. package/dist/components/ConsoleBlock.d.ts.map +1 -0
  17. package/dist/components/ConsoleBlock.js +77 -0
  18. package/dist/components/ConsoleBlock.js.map +1 -0
  19. package/dist/components/Input.d.ts +26 -0
  20. package/dist/components/Input.d.ts.map +1 -0
  21. package/dist/components/Input.js +56 -0
  22. package/dist/components/Input.js.map +1 -0
  23. package/dist/components/MessageEditor.d.ts +38 -0
  24. package/dist/components/MessageEditor.d.ts.map +1 -0
  25. package/dist/components/MessageEditor.js +296 -0
  26. package/dist/components/MessageEditor.js.map +1 -0
  27. package/dist/components/MessageList.d.ts +13 -0
  28. package/dist/components/MessageList.d.ts.map +1 -0
  29. package/dist/components/MessageList.js +88 -0
  30. package/dist/components/MessageList.js.map +1 -0
  31. package/dist/components/Messages.d.ts +53 -0
  32. package/dist/components/Messages.d.ts.map +1 -0
  33. package/dist/components/Messages.js +323 -0
  34. package/dist/components/Messages.js.map +1 -0
  35. package/dist/components/ProviderKeyInput.d.ts +16 -0
  36. package/dist/components/ProviderKeyInput.d.ts.map +1 -0
  37. package/dist/components/ProviderKeyInput.js +183 -0
  38. package/dist/components/ProviderKeyInput.js.map +1 -0
  39. package/dist/components/SandboxedIframe.d.ts +63 -0
  40. package/dist/components/SandboxedIframe.d.ts.map +1 -0
  41. package/dist/components/SandboxedIframe.js +435 -0
  42. package/dist/components/SandboxedIframe.js.map +1 -0
  43. package/dist/components/StreamingMessageContainer.d.ts +17 -0
  44. package/dist/components/StreamingMessageContainer.d.ts.map +1 -0
  45. package/dist/components/StreamingMessageContainer.js +114 -0
  46. package/dist/components/StreamingMessageContainer.js.map +1 -0
  47. package/dist/dialogs/ApiKeyPromptDialog.d.ts +15 -0
  48. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -0
  49. package/dist/dialogs/ApiKeyPromptDialog.js +80 -0
  50. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -0
  51. package/dist/dialogs/AttachmentOverlay.d.ts +32 -0
  52. package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -0
  53. package/dist/dialogs/AttachmentOverlay.js +575 -0
  54. package/dist/dialogs/AttachmentOverlay.js.map +1 -0
  55. package/dist/dialogs/ModelSelector.d.ts +27 -0
  56. package/dist/dialogs/ModelSelector.d.ts.map +1 -0
  57. package/dist/dialogs/ModelSelector.js +334 -0
  58. package/dist/dialogs/ModelSelector.js.map +1 -0
  59. package/dist/dialogs/SettingsDialog.d.ts +31 -0
  60. package/dist/dialogs/SettingsDialog.d.ts.map +1 -0
  61. package/dist/dialogs/SettingsDialog.js +228 -0
  62. package/dist/dialogs/SettingsDialog.js.map +1 -0
  63. package/dist/index.d.ts +46 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +51 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/state/agent-session.d.ts +58 -0
  68. package/dist/state/agent-session.d.ts.map +1 -0
  69. package/dist/state/agent-session.js +252 -0
  70. package/dist/state/agent-session.js.map +1 -0
  71. package/dist/state/transports/AppTransport.d.ts +13 -0
  72. package/dist/state/transports/AppTransport.d.ts.map +1 -0
  73. package/dist/state/transports/AppTransport.js +316 -0
  74. package/dist/state/transports/AppTransport.js.map +1 -0
  75. package/dist/state/transports/ProviderTransport.d.ts +12 -0
  76. package/dist/state/transports/ProviderTransport.d.ts.map +1 -0
  77. package/dist/state/transports/ProviderTransport.js +44 -0
  78. package/dist/state/transports/ProviderTransport.js.map +1 -0
  79. package/dist/state/transports/index.d.ts +4 -0
  80. package/dist/state/transports/index.d.ts.map +1 -0
  81. package/dist/state/transports/index.js +4 -0
  82. package/dist/state/transports/index.js.map +1 -0
  83. package/dist/state/transports/proxy-types.d.ts +48 -0
  84. package/dist/state/transports/proxy-types.d.ts.map +1 -0
  85. package/dist/state/transports/proxy-types.js +2 -0
  86. package/dist/state/transports/proxy-types.js.map +1 -0
  87. package/dist/state/transports/types.d.ts +11 -0
  88. package/dist/state/transports/types.d.ts.map +1 -0
  89. package/dist/state/transports/types.js +2 -0
  90. package/dist/state/transports/types.js.map +1 -0
  91. package/dist/state/types.d.ts +15 -0
  92. package/dist/state/types.d.ts.map +1 -0
  93. package/dist/state/types.js +2 -0
  94. package/dist/state/types.js.map +1 -0
  95. package/dist/storage/app-storage.d.ts +26 -0
  96. package/dist/storage/app-storage.d.ts.map +1 -0
  97. package/dist/storage/app-storage.js +44 -0
  98. package/dist/storage/app-storage.js.map +1 -0
  99. package/dist/storage/backends/chrome-storage-backend.d.ts +18 -0
  100. package/dist/storage/backends/chrome-storage-backend.d.ts.map +1 -0
  101. package/dist/storage/backends/chrome-storage-backend.js +67 -0
  102. package/dist/storage/backends/chrome-storage-backend.js.map +1 -0
  103. package/dist/storage/backends/indexeddb-backend.d.ts +20 -0
  104. package/dist/storage/backends/indexeddb-backend.d.ts.map +1 -0
  105. package/dist/storage/backends/indexeddb-backend.js +89 -0
  106. package/dist/storage/backends/indexeddb-backend.js.map +1 -0
  107. package/dist/storage/backends/local-storage-backend.d.ts +18 -0
  108. package/dist/storage/backends/local-storage-backend.d.ts.map +1 -0
  109. package/dist/storage/backends/local-storage-backend.js +69 -0
  110. package/dist/storage/backends/local-storage-backend.js.map +1 -0
  111. package/dist/storage/repositories/provider-keys-repository.d.ts +34 -0
  112. package/dist/storage/repositories/provider-keys-repository.d.ts.map +1 -0
  113. package/dist/storage/repositories/provider-keys-repository.js +50 -0
  114. package/dist/storage/repositories/provider-keys-repository.js.map +1 -0
  115. package/dist/storage/repositories/settings-repository.d.ts +34 -0
  116. package/dist/storage/repositories/settings-repository.d.ts.map +1 -0
  117. package/dist/storage/repositories/settings-repository.js +46 -0
  118. package/dist/storage/repositories/settings-repository.js.map +1 -0
  119. package/dist/storage/types.d.ts +43 -0
  120. package/dist/storage/types.d.ts.map +1 -0
  121. package/dist/storage/types.js +2 -0
  122. package/dist/storage/types.js.map +1 -0
  123. package/dist/tools/artifacts/ArtifactElement.d.ts +10 -0
  124. package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -0
  125. package/dist/tools/artifacts/ArtifactElement.js +12 -0
  126. package/dist/tools/artifacts/ArtifactElement.js.map +1 -0
  127. package/dist/tools/artifacts/HtmlArtifact.d.ts +30 -0
  128. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -0
  129. package/dist/tools/artifacts/HtmlArtifact.js +217 -0
  130. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -0
  131. package/dist/tools/artifacts/MarkdownArtifact.d.ts +20 -0
  132. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -0
  133. package/dist/tools/artifacts/MarkdownArtifact.js +84 -0
  134. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -0
  135. package/dist/tools/artifacts/SvgArtifact.d.ts +19 -0
  136. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -0
  137. package/dist/tools/artifacts/SvgArtifact.js +80 -0
  138. package/dist/tools/artifacts/SvgArtifact.js.map +1 -0
  139. package/dist/tools/artifacts/TextArtifact.d.ts +20 -0
  140. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -0
  141. package/dist/tools/artifacts/TextArtifact.js +147 -0
  142. package/dist/tools/artifacts/TextArtifact.js.map +1 -0
  143. package/dist/tools/artifacts/artifacts.d.ts +67 -0
  144. package/dist/tools/artifacts/artifacts.d.ts.map +1 -0
  145. package/dist/tools/artifacts/artifacts.js +836 -0
  146. package/dist/tools/artifacts/artifacts.js.map +1 -0
  147. package/dist/tools/artifacts/index.d.ts +7 -0
  148. package/dist/tools/artifacts/index.d.ts.map +1 -0
  149. package/dist/tools/artifacts/index.js +7 -0
  150. package/dist/tools/artifacts/index.js.map +1 -0
  151. package/dist/tools/index.d.ts +14 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +29 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/javascript-repl.d.ts +43 -0
  156. package/dist/tools/javascript-repl.d.ts.map +1 -0
  157. package/dist/tools/javascript-repl.js +252 -0
  158. package/dist/tools/javascript-repl.js.map +1 -0
  159. package/dist/tools/renderer-registry.d.ts +11 -0
  160. package/dist/tools/renderer-registry.d.ts.map +1 -0
  161. package/dist/tools/renderer-registry.js +15 -0
  162. package/dist/tools/renderer-registry.js.map +1 -0
  163. package/dist/tools/renderers/BashRenderer.d.ts +12 -0
  164. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -0
  165. package/dist/tools/renderers/BashRenderer.js +35 -0
  166. package/dist/tools/renderers/BashRenderer.js.map +1 -0
  167. package/dist/tools/renderers/CalculateRenderer.d.ts +12 -0
  168. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -0
  169. package/dist/tools/renderers/CalculateRenderer.js +38 -0
  170. package/dist/tools/renderers/CalculateRenderer.js.map +1 -0
  171. package/dist/tools/renderers/DefaultRenderer.d.ts +8 -0
  172. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -0
  173. package/dist/tools/renderers/DefaultRenderer.js +31 -0
  174. package/dist/tools/renderers/DefaultRenderer.js.map +1 -0
  175. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +12 -0
  176. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -0
  177. package/dist/tools/renderers/GetCurrentTimeRenderer.js +30 -0
  178. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -0
  179. package/dist/tools/types.d.ts +7 -0
  180. package/dist/tools/types.d.ts.map +1 -0
  181. package/dist/tools/types.js +2 -0
  182. package/dist/tools/types.js.map +1 -0
  183. package/dist/utils/attachment-utils.d.ts +19 -0
  184. package/dist/utils/attachment-utils.d.ts.map +1 -0
  185. package/dist/utils/attachment-utils.js +415 -0
  186. package/dist/utils/attachment-utils.js.map +1 -0
  187. package/dist/utils/auth-token.d.ts +3 -0
  188. package/dist/utils/auth-token.d.ts.map +1 -0
  189. package/dist/utils/auth-token.js +19 -0
  190. package/dist/utils/auth-token.js.map +1 -0
  191. package/dist/utils/format.d.ts +6 -0
  192. package/dist/utils/format.d.ts.map +1 -0
  193. package/dist/utils/format.js +47 -0
  194. package/dist/utils/format.js.map +1 -0
  195. package/dist/utils/i18n.d.ts +111 -0
  196. package/dist/utils/i18n.d.ts.map +1 -0
  197. package/dist/utils/i18n.js +224 -0
  198. package/dist/utils/i18n.js.map +1 -0
  199. package/dist/utils/test-sessions.d.ts +347 -0
  200. package/dist/utils/test-sessions.d.ts.map +1 -0
  201. package/dist/utils/test-sessions.js +2215 -0
  202. package/dist/utils/test-sessions.js.map +1 -0
  203. package/example/README.md +61 -0
  204. package/example/index.html +13 -0
  205. package/example/package-lock.json +1965 -0
  206. package/example/package.json +22 -0
  207. package/example/src/app.css +1 -0
  208. package/example/src/main.ts +57 -0
  209. package/example/src/test-sessions.ts +104 -0
  210. package/example/tsconfig.json +15 -0
  211. package/example/vite.config.ts +6 -0
  212. package/package.json +45 -0
  213. package/src/ChatPanel.ts +214 -0
  214. package/src/app.css +44 -0
  215. package/src/components/AgentInterface.ts +316 -0
  216. package/src/components/AttachmentTile.ts +112 -0
  217. package/src/components/ConsoleBlock.ts +67 -0
  218. package/src/components/Input.ts +112 -0
  219. package/src/components/MessageEditor.ts +272 -0
  220. package/src/components/MessageList.ts +82 -0
  221. package/src/components/Messages.ts +310 -0
  222. package/src/components/ProviderKeyInput.ts +170 -0
  223. package/src/components/SandboxedIframe.ts +525 -0
  224. package/src/components/StreamingMessageContainer.ts +101 -0
  225. package/src/dialogs/ApiKeyPromptDialog.ts +76 -0
  226. package/src/dialogs/AttachmentOverlay.ts +635 -0
  227. package/src/dialogs/ModelSelector.ts +324 -0
  228. package/src/dialogs/SettingsDialog.ts +223 -0
  229. package/src/index.ts +63 -0
  230. package/src/state/agent-session.ts +311 -0
  231. package/src/state/transports/AppTransport.ts +363 -0
  232. package/src/state/transports/ProviderTransport.ts +49 -0
  233. package/src/state/transports/index.ts +3 -0
  234. package/src/state/transports/proxy-types.ts +15 -0
  235. package/src/state/transports/types.ts +16 -0
  236. package/src/state/types.ts +11 -0
  237. package/src/storage/app-storage.ts +53 -0
  238. package/src/storage/backends/chrome-storage-backend.ts +82 -0
  239. package/src/storage/backends/indexeddb-backend.ts +107 -0
  240. package/src/storage/backends/local-storage-backend.ts +74 -0
  241. package/src/storage/repositories/provider-keys-repository.ts +55 -0
  242. package/src/storage/repositories/settings-repository.ts +51 -0
  243. package/src/storage/types.ts +48 -0
  244. package/src/tools/artifacts/ArtifactElement.ts +15 -0
  245. package/src/tools/artifacts/HtmlArtifact.ts +221 -0
  246. package/src/tools/artifacts/MarkdownArtifact.ts +81 -0
  247. package/src/tools/artifacts/SvgArtifact.ts +77 -0
  248. package/src/tools/artifacts/TextArtifact.ts +148 -0
  249. package/src/tools/artifacts/artifacts.ts +888 -0
  250. package/src/tools/artifacts/index.ts +6 -0
  251. package/src/tools/index.ts +35 -0
  252. package/src/tools/javascript-repl.ts +309 -0
  253. package/src/tools/renderer-registry.ts +18 -0
  254. package/src/tools/renderers/BashRenderer.ts +45 -0
  255. package/src/tools/renderers/CalculateRenderer.ts +49 -0
  256. package/src/tools/renderers/DefaultRenderer.ts +36 -0
  257. package/src/tools/renderers/GetCurrentTimeRenderer.ts +39 -0
  258. package/src/tools/types.ts +7 -0
  259. package/src/utils/attachment-utils.ts +472 -0
  260. package/src/utils/auth-token.ts +22 -0
  261. package/src/utils/format.ts +42 -0
  262. package/src/utils/i18n.ts +343 -0
  263. package/src/utils/test-sessions.ts +2247 -0
  264. package/tsconfig.build.json +20 -0
  265. package/tsconfig.json +7 -0
@@ -0,0 +1,6 @@
1
+ export { ArtifactElement } from "./ArtifactElement.js";
2
+ export { type Artifact, ArtifactsPanel, type ArtifactsParams } from "./artifacts.js";
3
+ export { HtmlArtifact } from "./HtmlArtifact.js";
4
+ export { MarkdownArtifact } from "./MarkdownArtifact.js";
5
+ export { SvgArtifact } from "./SvgArtifact.js";
6
+ export { TextArtifact } from "./TextArtifact.js";
@@ -0,0 +1,35 @@
1
+ import type { TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { getToolRenderer, registerToolRenderer } from "./renderer-registry.js";
4
+ import { BashRenderer } from "./renderers/BashRenderer.js";
5
+ import { DefaultRenderer } from "./renderers/DefaultRenderer.js";
6
+ import "./javascript-repl.js"; // Auto-registers the renderer
7
+
8
+ // Register all built-in tool renderers
9
+ registerToolRenderer("bash", new BashRenderer());
10
+
11
+ const defaultRenderer = new DefaultRenderer();
12
+
13
+ /**
14
+ * Render tool call parameters
15
+ */
16
+ export function renderToolParams(toolName: string, params: any, isStreaming?: boolean): TemplateResult {
17
+ const renderer = getToolRenderer(toolName);
18
+ if (renderer) {
19
+ return renderer.renderParams(params, isStreaming);
20
+ }
21
+ return defaultRenderer.renderParams(params, isStreaming);
22
+ }
23
+
24
+ /**
25
+ * Render tool result
26
+ */
27
+ export function renderToolResult(toolName: string, params: any, result: ToolResultMessage): TemplateResult {
28
+ const renderer = getToolRenderer(toolName);
29
+ if (renderer) {
30
+ return renderer.renderResult(params, result);
31
+ }
32
+ return defaultRenderer.renderResult(params, result);
33
+ }
34
+
35
+ export { registerToolRenderer, getToolRenderer };
@@ -0,0 +1,309 @@
1
+ import { html, i18n, type TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { AgentTool, ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { type Static, Type } from "@sinclair/typebox";
4
+ import { type SandboxFile, SandboxIframe, type SandboxResult } from "../components/SandboxedIframe.js";
5
+ import type { Attachment } from "../utils/attachment-utils.js";
6
+
7
+ import { registerToolRenderer } from "./renderer-registry.js";
8
+ import type { ToolRenderer } from "./types.js";
9
+
10
+ // Execute JavaScript code with attachments using SandboxedIframe
11
+ export async function executeJavaScript(
12
+ code: string,
13
+ attachments: Attachment[] = [],
14
+ signal?: AbortSignal,
15
+ sandboxUrlProvider?: () => string,
16
+ ): Promise<{ output: string; files?: SandboxFile[] }> {
17
+ if (!code) {
18
+ throw new Error("Code parameter is required");
19
+ }
20
+
21
+ // Check for abort before starting
22
+ if (signal?.aborted) {
23
+ throw new Error("Execution aborted");
24
+ }
25
+
26
+ // Create a SandboxedIframe instance for execution
27
+ const sandbox = new SandboxIframe();
28
+ if (sandboxUrlProvider) {
29
+ sandbox.sandboxUrlProvider = sandboxUrlProvider;
30
+ }
31
+ sandbox.style.display = "none";
32
+ document.body.appendChild(sandbox);
33
+
34
+ try {
35
+ const sandboxId = `repl-${Date.now()}`;
36
+ const result: SandboxResult = await sandbox.execute(sandboxId, code, attachments, signal);
37
+
38
+ // Remove the sandbox iframe after execution
39
+ sandbox.remove();
40
+
41
+ // Return plain text output
42
+ if (!result.success) {
43
+ // Return error as plain text
44
+ return {
45
+ output: `Error: ${result.error?.message || "Unknown error"}\n${result.error?.stack || ""}`,
46
+ };
47
+ }
48
+
49
+ // Build plain text response
50
+ let output = "";
51
+
52
+ // Add console output - result.console contains { type: string, text: string } from sandbox.js
53
+ if (result.console && result.console.length > 0) {
54
+ for (const entry of result.console) {
55
+ const prefix = entry.type === "error" ? "[ERROR]" : "";
56
+ output += (prefix ? `${prefix} ` : "") + entry.text + "\n";
57
+ }
58
+ }
59
+
60
+ // Add file notifications
61
+ if (result.files && result.files.length > 0) {
62
+ output += `\n[Files returned: ${result.files.length}]\n`;
63
+ for (const file of result.files) {
64
+ output += ` - ${file.fileName} (${file.mimeType})\n`;
65
+ }
66
+ } else {
67
+ // Explicitly note when no files were returned (helpful for debugging)
68
+ if (code.includes("returnFile")) {
69
+ output += "\n[No files returned - check async operations]";
70
+ }
71
+ }
72
+
73
+ return {
74
+ output: output.trim() || "Code executed successfully (no output)",
75
+ files: result.files,
76
+ };
77
+ } catch (error: unknown) {
78
+ // Clean up on error
79
+ sandbox.remove();
80
+ throw new Error((error as Error).message || "Execution failed");
81
+ }
82
+ }
83
+
84
+ export type JavaScriptReplToolResult = {
85
+ files?:
86
+ | {
87
+ fileName: string;
88
+ contentBase64: string;
89
+ mimeType: string;
90
+ }[]
91
+ | undefined;
92
+ };
93
+
94
+ const javascriptReplSchema = Type.Object({
95
+ code: Type.String({ description: "JavaScript code to execute" }),
96
+ });
97
+
98
+ export function createJavaScriptReplTool(): AgentTool<typeof javascriptReplSchema, JavaScriptReplToolResult> & {
99
+ attachmentsProvider?: () => Attachment[];
100
+ sandboxUrlProvider?: () => string;
101
+ } {
102
+ return {
103
+ label: "JavaScript REPL",
104
+ name: "javascript_repl",
105
+ attachmentsProvider: () => [], // default to empty array
106
+ sandboxUrlProvider: undefined, // optional, for browser extensions
107
+ description: `Execute JavaScript code in a sandboxed browser environment with full modern browser capabilities.
108
+
109
+ Environment: Modern browser with ALL Web APIs available:
110
+ - ES2023+ JavaScript (async/await, optional chaining, nullish coalescing, etc.)
111
+ - DOM APIs (document, window, Canvas, WebGL, etc.)
112
+ - Fetch API for HTTP requests
113
+
114
+ Loading external libraries via dynamic imports (use esm.run):
115
+ - XLSX (Excel files): const XLSX = await import('https://esm.run/xlsx');
116
+ - Papa Parse (CSV): const Papa = (await import('https://esm.run/papaparse')).default;
117
+ - Lodash: const _ = await import('https://esm.run/lodash-es');
118
+ - D3.js: const d3 = await import('https://esm.run/d3');
119
+ - Chart.js: const Chart = (await import('https://esm.run/chart.js/auto')).default;
120
+ - Three.js: const THREE = await import('https://esm.run/three');
121
+ - Any npm package: await import('https://esm.run/package-name')
122
+
123
+ IMPORTANT for graphics/canvas:
124
+ - Use fixed dimensions like 400x400 or 800x600, NOT window.innerWidth/Height
125
+ - For Three.js: renderer.setSize(400, 400) and camera aspect ratio of 1
126
+ - For Chart.js: Set options: { responsive: false, animation: false } to ensure immediate rendering
127
+ - Web Storage (localStorage, sessionStorage, IndexedDB)
128
+ - Web Workers, WebAssembly, WebSockets
129
+ - Media APIs (Audio, Video, WebRTC)
130
+ - File APIs (Blob, FileReader, etc.)
131
+ - Crypto API for cryptography
132
+ - And much more - anything a modern browser supports!
133
+
134
+ Output:
135
+ - console.log() - All output is captured as text
136
+ - await returnFile(filename, content, mimeType?) - Create downloadable files (async function!)
137
+ * Always use await with returnFile
138
+ * REQUIRED: For Blob/Uint8Array binary content, you MUST supply a proper MIME type (e.g., "image/png").
139
+ If omitted, the REPL throws an Error with stack trace pointing to the offending line.
140
+ * Strings without a MIME default to text/plain.
141
+ * Objects are auto-JSON stringified and default to application/json unless a MIME is provided.
142
+ * Canvas images: Use toBlob() with await Promise wrapper
143
+ * Examples:
144
+ - await returnFile('data.txt', 'Hello World', 'text/plain')
145
+ - await returnFile('data.json', {key: 'value'}, 'application/json')
146
+ - await returnFile('data.csv', 'name,age\\nJohn,30', 'text/csv')
147
+ - Chart.js example:
148
+ const Chart = (await import('https://esm.run/chart.js/auto')).default;
149
+ const canvas = document.createElement('canvas');
150
+ canvas.width = 400; canvas.height = 300;
151
+ document.body.appendChild(canvas);
152
+ new Chart(canvas, {
153
+ type: 'line',
154
+ data: {
155
+ labels: ['Jan', 'Feb', 'Mar', 'Apr'],
156
+ datasets: [{ label: 'Sales', data: [10, 20, 15, 25], borderColor: 'blue' }]
157
+ },
158
+ options: { responsive: false, animation: false }
159
+ });
160
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
161
+ await returnFile('chart.png', blob, 'image/png');
162
+
163
+ Global variables:
164
+ - attachments[] - Array of attachment objects from user messages
165
+ * Properties:
166
+ - id: string (unique identifier)
167
+ - fileName: string (e.g., "data.xlsx")
168
+ - mimeType: string (e.g., "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
169
+ - size: number (bytes)
170
+ * Helper functions:
171
+ - listFiles() - Returns array of {id, fileName, mimeType, size} for all attachments
172
+ - readTextFile(attachmentId) - Returns text content of attachment (for CSV, JSON, text files)
173
+ - readBinaryFile(attachmentId) - Returns Uint8Array of binary data (for images, Excel, etc.)
174
+ * Examples:
175
+ - const files = listFiles();
176
+ - const csvContent = readTextFile(files[0].id); // Read CSV as text
177
+ - const xlsxBytes = readBinaryFile(files[0].id); // Read Excel as binary
178
+ - All standard browser globals (window, document, fetch, etc.)`,
179
+ parameters: javascriptReplSchema,
180
+ execute: async function (_toolCallId: string, args: Static<typeof javascriptReplSchema>, signal?: AbortSignal) {
181
+ const attachments = this.attachmentsProvider?.() || [];
182
+ const result = await executeJavaScript(args.code, attachments, signal, this.sandboxUrlProvider);
183
+ // Convert files to JSON-serializable with base64 payloads
184
+ const files = (result.files || []).map((f) => {
185
+ const toBase64 = (input: string | Uint8Array): { base64: string; size: number } => {
186
+ if (input instanceof Uint8Array) {
187
+ let binary = "";
188
+ const chunk = 0x8000;
189
+ for (let i = 0; i < input.length; i += chunk) {
190
+ binary += String.fromCharCode(...input.subarray(i, i + chunk));
191
+ }
192
+ return { base64: btoa(binary), size: input.length };
193
+ } else if (typeof input === "string") {
194
+ const enc = new TextEncoder();
195
+ const bytes = enc.encode(input);
196
+ let binary = "";
197
+ const chunk = 0x8000;
198
+ for (let i = 0; i < bytes.length; i += chunk) {
199
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
200
+ }
201
+ return { base64: btoa(binary), size: bytes.length };
202
+ } else {
203
+ const s = String(input);
204
+ const enc = new TextEncoder();
205
+ const bytes = enc.encode(s);
206
+ let binary = "";
207
+ const chunk = 0x8000;
208
+ for (let i = 0; i < bytes.length; i += chunk) {
209
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
210
+ }
211
+ return { base64: btoa(binary), size: bytes.length };
212
+ }
213
+ };
214
+
215
+ const { base64, size } = toBase64(f.content);
216
+ return {
217
+ fileName: f.fileName || "file",
218
+ mimeType: f.mimeType || "application/octet-stream",
219
+ size,
220
+ contentBase64: base64,
221
+ };
222
+ });
223
+ return { output: result.output, details: { files } };
224
+ },
225
+ };
226
+ }
227
+
228
+ // Export a default instance for backward compatibility
229
+ export const javascriptReplTool = createJavaScriptReplTool();
230
+
231
+ // JavaScript REPL renderer with streaming support
232
+
233
+ interface JavaScriptReplParams {
234
+ code: string;
235
+ }
236
+
237
+ interface JavaScriptReplResult {
238
+ output?: string;
239
+ files?: Array<{
240
+ fileName: string;
241
+ mimeType: string;
242
+ size: number;
243
+ contentBase64: string;
244
+ }>;
245
+ }
246
+
247
+ export const javascriptReplRenderer: ToolRenderer<JavaScriptReplParams, JavaScriptReplResult> = {
248
+ renderParams(params: JavaScriptReplParams, isStreaming?: boolean): TemplateResult {
249
+ if (isStreaming && (!params.code || params.code.length === 0)) {
250
+ return html`<div class="text-sm text-muted-foreground">Writing JavaScript code...</div>`;
251
+ }
252
+
253
+ return html`
254
+ <div class="text-sm text-muted-foreground mb-2">${i18n("Executing JavaScript")}</div>
255
+ <code-block .code=${params.code || ""} language="javascript"></code-block>
256
+ `;
257
+ },
258
+
259
+ renderResult(_params: JavaScriptReplParams, result: ToolResultMessage<JavaScriptReplResult>): TemplateResult {
260
+ // Console output is in the main output field, files are in details
261
+ const output = result.output || "";
262
+ const files = result.details?.files || [];
263
+
264
+ const attachments: Attachment[] = files.map((f, i) => {
265
+ // Decode base64 content for text files to show in overlay
266
+ let extractedText: string | undefined;
267
+ const isTextBased =
268
+ f.mimeType?.startsWith("text/") ||
269
+ f.mimeType === "application/json" ||
270
+ f.mimeType === "application/javascript" ||
271
+ f.mimeType?.includes("xml");
272
+
273
+ if (isTextBased && f.contentBase64) {
274
+ try {
275
+ extractedText = atob(f.contentBase64);
276
+ } catch (e) {
277
+ console.warn("Failed to decode base64 content for", f.fileName);
278
+ }
279
+ }
280
+
281
+ return {
282
+ id: `repl-${Date.now()}-${i}`,
283
+ type: f.mimeType?.startsWith("image/") ? "image" : "document",
284
+ fileName: f.fileName || `file-${i}`,
285
+ mimeType: f.mimeType || "application/octet-stream",
286
+ size: f.size ?? 0,
287
+ content: f.contentBase64,
288
+ preview: f.mimeType?.startsWith("image/") ? f.contentBase64 : undefined,
289
+ extractedText,
290
+ };
291
+ });
292
+
293
+ return html`
294
+ <div class="flex flex-col gap-3">
295
+ ${output ? html`<console-block .content=${output}></console-block>` : ""}
296
+ ${
297
+ attachments.length
298
+ ? html`<div class="flex flex-wrap gap-2">
299
+ ${attachments.map((att) => html`<attachment-tile .attachment=${att}></attachment-tile>`)}
300
+ </div>`
301
+ : ""
302
+ }
303
+ </div>
304
+ `;
305
+ },
306
+ };
307
+
308
+ // Auto-register the renderer
309
+ registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);
@@ -0,0 +1,18 @@
1
+ import type { ToolRenderer } from "./types.js";
2
+
3
+ // Registry of tool renderers
4
+ export const toolRenderers = new Map<string, ToolRenderer>();
5
+
6
+ /**
7
+ * Register a custom tool renderer
8
+ */
9
+ export function registerToolRenderer(toolName: string, renderer: ToolRenderer): void {
10
+ toolRenderers.set(toolName, renderer);
11
+ }
12
+
13
+ /**
14
+ * Get a tool renderer by name
15
+ */
16
+ export function getToolRenderer(toolName: string): ToolRenderer | undefined {
17
+ return toolRenderers.get(toolName);
18
+ }
@@ -0,0 +1,45 @@
1
+ import { html, type TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { i18n } from "../../utils/i18n.js";
4
+ import type { ToolRenderer } from "../types.js";
5
+
6
+ interface BashParams {
7
+ command: string;
8
+ }
9
+
10
+ // Bash tool has undefined details (only uses output)
11
+ export class BashRenderer implements ToolRenderer<BashParams, undefined> {
12
+ renderParams(params: BashParams, isStreaming?: boolean): TemplateResult {
13
+ if (isStreaming && (!params.command || params.command.length === 0)) {
14
+ return html`<div class="text-sm text-muted-foreground">${i18n("Writing command...")}</div>`;
15
+ }
16
+
17
+ return html`
18
+ <div class="text-sm text-muted-foreground">
19
+ <span>${i18n("Running command:")}</span>
20
+ <code class="ml-1 px-1.5 py-0.5 bg-muted rounded text-xs font-mono">${params.command}</code>
21
+ </div>
22
+ `;
23
+ }
24
+
25
+ renderResult(_params: BashParams, result: ToolResultMessage<undefined>): TemplateResult {
26
+ const output = result.output || "";
27
+ const isError = result.isError === true;
28
+
29
+ if (isError) {
30
+ return html`
31
+ <div class="text-sm">
32
+ <div class="text-destructive font-medium mb-1">${i18n("Command failed:")}</div>
33
+ <pre class="text-xs font-mono text-destructive bg-destructive/10 p-2 rounded overflow-x-auto">${output}</pre>
34
+ </div>
35
+ `;
36
+ }
37
+
38
+ // Display the command output
39
+ return html`
40
+ <div class="text-sm">
41
+ <pre class="text-xs font-mono text-foreground bg-muted/50 p-2 rounded overflow-x-auto">${output}</pre>
42
+ </div>
43
+ `;
44
+ }
45
+ }
@@ -0,0 +1,49 @@
1
+ import { html, type TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { i18n } from "../../utils/i18n.js";
4
+ import type { ToolRenderer } from "../types.js";
5
+
6
+ interface CalculateParams {
7
+ expression: string;
8
+ }
9
+
10
+ // Calculate tool has undefined details (only uses output)
11
+ export class CalculateRenderer implements ToolRenderer<CalculateParams, undefined> {
12
+ renderParams(params: CalculateParams, isStreaming?: boolean): TemplateResult {
13
+ if (isStreaming && !params.expression) {
14
+ return html`<div class="text-sm text-muted-foreground">${i18n("Writing expression...")}</div>`;
15
+ }
16
+
17
+ return html`
18
+ <div class="text-sm text-muted-foreground">
19
+ <span>${i18n("Calculating")}</span>
20
+ <code class="mx-1 px-1.5 py-0.5 bg-muted rounded text-xs font-mono">${params.expression}</code>
21
+ </div>
22
+ `;
23
+ }
24
+
25
+ renderResult(_params: CalculateParams, result: ToolResultMessage<undefined>): TemplateResult {
26
+ // Parse the output to make it look nicer
27
+ const output = result.output || "";
28
+ const isError = result.isError === true;
29
+
30
+ if (isError) {
31
+ return html`<div class="text-sm text-destructive">${output}</div>`;
32
+ }
33
+
34
+ // Try to split on = to show expression and result separately
35
+ const parts = output.split(" = ");
36
+ if (parts.length === 2) {
37
+ return html`
38
+ <div class="text-sm font-mono">
39
+ <span class="text-muted-foreground">${parts[0]}</span>
40
+ <span class="text-muted-foreground mx-1">=</span>
41
+ <span class="text-foreground font-semibold">${parts[1]}</span>
42
+ </div>
43
+ `;
44
+ }
45
+
46
+ // Fallback to showing the whole output
47
+ return html`<div class="text-sm font-mono text-foreground">${output}</div>`;
48
+ }
49
+ }
@@ -0,0 +1,36 @@
1
+ import { html, type TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { i18n } from "../../utils/i18n.js";
4
+ import type { ToolRenderer } from "../types.js";
5
+
6
+ export class DefaultRenderer implements ToolRenderer {
7
+ renderParams(params: any, isStreaming?: boolean): TemplateResult {
8
+ let text: string;
9
+ let isJson = false;
10
+
11
+ try {
12
+ text = JSON.stringify(JSON.parse(params), null, 2);
13
+ isJson = true;
14
+ } catch {
15
+ try {
16
+ text = JSON.stringify(params, null, 2);
17
+ isJson = true;
18
+ } catch {
19
+ text = String(params);
20
+ }
21
+ }
22
+
23
+ if (isStreaming && (!text || text === "{}" || text === "null")) {
24
+ return html`<div class="text-sm text-muted-foreground">${i18n("Preparing tool parameters...")}</div>`;
25
+ }
26
+
27
+ return html`<console-block .content=${text}></console-block>`;
28
+ }
29
+
30
+ renderResult(_params: any, result: ToolResultMessage): TemplateResult {
31
+ // Just show the output field - that's what was sent to the LLM
32
+ const text = result.output || i18n("(no output)");
33
+
34
+ return html`<div class="text-sm text-muted-foreground whitespace-pre-wrap font-mono">${text}</div>`;
35
+ }
36
+ }
@@ -0,0 +1,39 @@
1
+ import { html, type TemplateResult } from "@mariozechner/mini-lit";
2
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import { i18n } from "../../utils/i18n.js";
4
+ import type { ToolRenderer } from "../types.js";
5
+
6
+ interface GetCurrentTimeParams {
7
+ timezone?: string;
8
+ }
9
+
10
+ // GetCurrentTime tool has undefined details (only uses output)
11
+ export class GetCurrentTimeRenderer implements ToolRenderer<GetCurrentTimeParams, undefined> {
12
+ renderParams(params: GetCurrentTimeParams, isStreaming?: boolean): TemplateResult {
13
+ if (params.timezone) {
14
+ return html`
15
+ <div class="text-sm text-muted-foreground">
16
+ <span>${i18n("Getting current time in")}</span>
17
+ <code class="mx-1 px-1.5 py-0.5 bg-muted rounded text-xs font-mono">${params.timezone}</code>
18
+ </div>
19
+ `;
20
+ }
21
+ return html`
22
+ <div class="text-sm text-muted-foreground">
23
+ <span>${i18n("Getting current date and time")}${isStreaming ? "..." : ""}</span>
24
+ </div>
25
+ `;
26
+ }
27
+
28
+ renderResult(_params: GetCurrentTimeParams, result: ToolResultMessage<undefined>): TemplateResult {
29
+ const output = result.output || "";
30
+ const isError = result.isError === true;
31
+
32
+ if (isError) {
33
+ return html`<div class="text-sm text-destructive">${output}</div>`;
34
+ }
35
+
36
+ // Display the date/time result
37
+ return html`<div class="text-sm font-mono text-foreground">${output}</div>`;
38
+ }
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { ToolResultMessage } from "@mariozechner/pi-ai";
2
+ import type { TemplateResult } from "lit";
3
+
4
+ export interface ToolRenderer<TParams = any, TDetails = any> {
5
+ renderParams(params: TParams, isStreaming?: boolean): TemplateResult;
6
+ renderResult(params: TParams, result: ToolResultMessage<TDetails>): TemplateResult;
7
+ }