@mariozechner/pi-web-ui 0.5.44 → 0.5.46

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 (346) hide show
  1. package/README.md +178 -99
  2. package/dist/ChatPanel.d.ts +15 -10
  3. package/dist/ChatPanel.d.ts.map +1 -1
  4. package/dist/ChatPanel.js +68 -100
  5. package/dist/ChatPanel.js.map +1 -1
  6. package/dist/{state/agent-session.d.ts → agent/agent.d.ts} +23 -19
  7. package/dist/agent/agent.d.ts.map +1 -0
  8. package/dist/{state/agent-session.js → agent/agent.js} +50 -32
  9. package/dist/agent/agent.js.map +1 -0
  10. package/dist/{state → agent}/transports/AppTransport.d.ts +1 -3
  11. package/dist/agent/transports/AppTransport.d.ts.map +1 -0
  12. package/dist/{state → agent}/transports/AppTransport.js +5 -4
  13. package/dist/{state → agent}/transports/AppTransport.js.map +1 -1
  14. package/dist/{state → agent}/transports/ProviderTransport.d.ts +1 -3
  15. package/dist/agent/transports/ProviderTransport.d.ts.map +1 -0
  16. package/dist/{state → agent}/transports/ProviderTransport.js +6 -7
  17. package/dist/agent/transports/ProviderTransport.js.map +1 -0
  18. package/dist/{state → agent}/transports/index.d.ts.map +1 -1
  19. package/dist/agent/transports/index.js.map +1 -0
  20. package/dist/{state → agent}/transports/proxy-types.d.ts.map +1 -1
  21. package/dist/agent/transports/proxy-types.js.map +1 -0
  22. package/dist/agent/transports/types.d.ts +12 -0
  23. package/dist/agent/transports/types.d.ts.map +1 -0
  24. package/dist/{state → agent}/transports/types.js.map +1 -1
  25. package/dist/{state → agent}/types.d.ts.map +1 -1
  26. package/dist/{state → agent}/types.js.map +1 -1
  27. package/dist/app.css +1 -1
  28. package/dist/components/AgentInterface.d.ts +7 -4
  29. package/dist/components/AgentInterface.d.ts.map +1 -1
  30. package/dist/components/AgentInterface.js +29 -17
  31. package/dist/components/AgentInterface.js.map +1 -1
  32. package/dist/components/ConsoleBlock.d.ts +1 -0
  33. package/dist/components/ConsoleBlock.d.ts.map +1 -1
  34. package/dist/components/ConsoleBlock.js +7 -1
  35. package/dist/components/ConsoleBlock.js.map +1 -1
  36. package/dist/components/ExpandableSection.d.ts +15 -0
  37. package/dist/components/ExpandableSection.d.ts.map +1 -0
  38. package/dist/components/ExpandableSection.js +63 -0
  39. package/dist/components/ExpandableSection.js.map +1 -0
  40. package/dist/components/MessageEditor.d.ts +8 -1
  41. package/dist/components/MessageEditor.d.ts.map +1 -1
  42. package/dist/components/MessageEditor.js +149 -6
  43. package/dist/components/MessageEditor.js.map +1 -1
  44. package/dist/components/MessageList.d.ts +3 -2
  45. package/dist/components/MessageList.d.ts.map +1 -1
  46. package/dist/components/MessageList.js +14 -1
  47. package/dist/components/MessageList.js.map +1 -1
  48. package/dist/components/Messages.d.ts +15 -6
  49. package/dist/components/Messages.d.ts.map +1 -1
  50. package/dist/components/Messages.js +17 -83
  51. package/dist/components/Messages.js.map +1 -1
  52. package/dist/components/ProviderKeyInput.d.ts.map +1 -1
  53. package/dist/components/ProviderKeyInput.js +6 -5
  54. package/dist/components/ProviderKeyInput.js.map +1 -1
  55. package/dist/components/SandboxedIframe.d.ts +29 -7
  56. package/dist/components/SandboxedIframe.d.ts.map +1 -1
  57. package/dist/components/SandboxedIframe.js +350 -282
  58. package/dist/components/SandboxedIframe.js.map +1 -1
  59. package/dist/components/message-renderer-registry.d.ts +12 -0
  60. package/dist/components/message-renderer-registry.d.ts.map +1 -0
  61. package/dist/components/message-renderer-registry.js +12 -0
  62. package/dist/components/message-renderer-registry.js.map +1 -0
  63. package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts +35 -0
  64. package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts.map +1 -0
  65. package/dist/components/sandbox/ArtifactsRuntimeProvider.js +189 -0
  66. package/dist/components/sandbox/ArtifactsRuntimeProvider.js.map +1 -0
  67. package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts +17 -0
  68. package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts.map +1 -0
  69. package/dist/components/sandbox/AttachmentsRuntimeProvider.js +64 -0
  70. package/dist/components/sandbox/AttachmentsRuntimeProvider.js.map +1 -0
  71. package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts +42 -0
  72. package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts.map +1 -0
  73. package/dist/components/sandbox/ConsoleRuntimeProvider.js +161 -0
  74. package/dist/components/sandbox/ConsoleRuntimeProvider.js.map +1 -0
  75. package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts +30 -0
  76. package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts.map +1 -0
  77. package/dist/components/sandbox/FileDownloadRuntimeProvider.js +97 -0
  78. package/dist/components/sandbox/FileDownloadRuntimeProvider.js.map +1 -0
  79. package/dist/components/sandbox/RuntimeMessageBridge.d.ts +19 -0
  80. package/dist/components/sandbox/RuntimeMessageBridge.d.ts.map +1 -0
  81. package/dist/components/sandbox/RuntimeMessageBridge.js +74 -0
  82. package/dist/components/sandbox/RuntimeMessageBridge.js.map +1 -0
  83. package/dist/components/sandbox/RuntimeMessageRouter.d.ts +65 -0
  84. package/dist/components/sandbox/RuntimeMessageRouter.d.ts.map +1 -0
  85. package/dist/components/sandbox/RuntimeMessageRouter.js +168 -0
  86. package/dist/components/sandbox/RuntimeMessageRouter.js.map +1 -0
  87. package/dist/components/sandbox/SandboxRuntimeProvider.d.ts +33 -0
  88. package/dist/components/sandbox/SandboxRuntimeProvider.d.ts.map +1 -0
  89. package/dist/components/sandbox/SandboxRuntimeProvider.js +2 -0
  90. package/dist/components/sandbox/SandboxRuntimeProvider.js.map +1 -0
  91. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -1
  92. package/dist/dialogs/ApiKeyPromptDialog.js +2 -5
  93. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -1
  94. package/dist/dialogs/ModelSelector.js.map +1 -1
  95. package/dist/dialogs/PersistentStorageDialog.d.ts +17 -0
  96. package/dist/dialogs/PersistentStorageDialog.d.ts.map +1 -0
  97. package/dist/dialogs/PersistentStorageDialog.js +144 -0
  98. package/dist/dialogs/PersistentStorageDialog.js.map +1 -0
  99. package/dist/dialogs/SessionListDialog.d.ts +19 -0
  100. package/dist/dialogs/SessionListDialog.d.ts.map +1 -0
  101. package/dist/dialogs/SessionListDialog.js +152 -0
  102. package/dist/dialogs/SessionListDialog.js.map +1 -0
  103. package/dist/dialogs/SettingsDialog.d.ts.map +1 -1
  104. package/dist/dialogs/SettingsDialog.js +1 -0
  105. package/dist/dialogs/SettingsDialog.js.map +1 -1
  106. package/dist/index.d.ts +34 -16
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +32 -14
  109. package/dist/index.js.map +1 -1
  110. package/dist/prompts/prompts.d.ts +11 -0
  111. package/dist/prompts/prompts.d.ts.map +1 -0
  112. package/dist/prompts/prompts.js +272 -0
  113. package/dist/prompts/prompts.js.map +1 -0
  114. package/dist/storage/app-storage.d.ts +17 -12
  115. package/dist/storage/app-storage.d.ts.map +1 -1
  116. package/dist/storage/app-storage.js +13 -20
  117. package/dist/storage/app-storage.js.map +1 -1
  118. package/dist/storage/backends/indexeddb-storage-backend.d.ts +27 -0
  119. package/dist/storage/backends/indexeddb-storage-backend.d.ts.map +1 -0
  120. package/dist/storage/backends/indexeddb-storage-backend.js +166 -0
  121. package/dist/storage/backends/indexeddb-storage-backend.js.map +1 -0
  122. package/dist/storage/store.d.ts +23 -0
  123. package/dist/storage/store.d.ts.map +1 -0
  124. package/dist/storage/store.js +26 -0
  125. package/dist/storage/store.js.map +1 -0
  126. package/dist/storage/stores/provider-keys-store.d.ts +14 -0
  127. package/dist/storage/stores/provider-keys-store.d.ts.map +1 -0
  128. package/dist/storage/stores/provider-keys-store.js +27 -0
  129. package/dist/storage/stores/provider-keys-store.js.map +1 -0
  130. package/dist/storage/stores/sessions-store.d.ts +31 -0
  131. package/dist/storage/stores/sessions-store.d.ts.map +1 -0
  132. package/dist/storage/stores/sessions-store.js +113 -0
  133. package/dist/storage/stores/sessions-store.js.map +1 -0
  134. package/dist/storage/stores/settings-store.d.ts +14 -0
  135. package/dist/storage/stores/settings-store.d.ts.map +1 -0
  136. package/dist/storage/stores/settings-store.js +28 -0
  137. package/dist/storage/stores/settings-store.js.map +1 -0
  138. package/dist/storage/types.d.ts +156 -22
  139. package/dist/storage/types.d.ts.map +1 -1
  140. package/dist/tools/artifacts/ArtifactElement.d.ts +0 -1
  141. package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -1
  142. package/dist/tools/artifacts/ArtifactElement.js +0 -1
  143. package/dist/tools/artifacts/ArtifactElement.js.map +1 -1
  144. package/dist/tools/artifacts/ArtifactPill.d.ts +4 -0
  145. package/dist/tools/artifacts/ArtifactPill.d.ts.map +1 -0
  146. package/dist/tools/artifacts/ArtifactPill.js +22 -0
  147. package/dist/tools/artifacts/ArtifactPill.js.map +1 -0
  148. package/dist/tools/artifacts/Console.d.ts +18 -0
  149. package/dist/tools/artifacts/Console.d.ts.map +1 -0
  150. package/dist/tools/artifacts/Console.js +95 -0
  151. package/dist/tools/artifacts/Console.js.map +1 -0
  152. package/dist/tools/artifacts/DocxArtifact.d.ts +22 -0
  153. package/dist/tools/artifacts/DocxArtifact.d.ts.map +1 -0
  154. package/dist/tools/artifacts/DocxArtifact.js +208 -0
  155. package/dist/tools/artifacts/DocxArtifact.js.map +1 -0
  156. package/dist/tools/artifacts/ExcelArtifact.d.ts +24 -0
  157. package/dist/tools/artifacts/ExcelArtifact.d.ts.map +1 -0
  158. package/dist/tools/artifacts/ExcelArtifact.js +216 -0
  159. package/dist/tools/artifacts/ExcelArtifact.js.map +1 -0
  160. package/dist/tools/artifacts/GenericArtifact.d.ts +19 -0
  161. package/dist/tools/artifacts/GenericArtifact.d.ts.map +1 -0
  162. package/dist/tools/artifacts/GenericArtifact.js +117 -0
  163. package/dist/tools/artifacts/GenericArtifact.js.map +1 -0
  164. package/dist/tools/artifacts/HtmlArtifact.d.ts +8 -11
  165. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -1
  166. package/dist/tools/artifacts/HtmlArtifact.js +56 -88
  167. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -1
  168. package/dist/tools/artifacts/ImageArtifact.d.ts +20 -0
  169. package/dist/tools/artifacts/ImageArtifact.d.ts.map +1 -0
  170. package/dist/tools/artifacts/ImageArtifact.js +120 -0
  171. package/dist/tools/artifacts/ImageArtifact.js.map +1 -0
  172. package/dist/tools/artifacts/MarkdownArtifact.d.ts +0 -1
  173. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -1
  174. package/dist/tools/artifacts/MarkdownArtifact.js +0 -4
  175. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -1
  176. package/dist/tools/artifacts/PdfArtifact.d.ts +25 -0
  177. package/dist/tools/artifacts/PdfArtifact.d.ts.map +1 -0
  178. package/dist/tools/artifacts/PdfArtifact.js +184 -0
  179. package/dist/tools/artifacts/PdfArtifact.js.map +1 -0
  180. package/dist/tools/artifacts/SvgArtifact.d.ts +0 -1
  181. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -1
  182. package/dist/tools/artifacts/SvgArtifact.js +0 -4
  183. package/dist/tools/artifacts/SvgArtifact.js.map +1 -1
  184. package/dist/tools/artifacts/TextArtifact.d.ts +0 -1
  185. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -1
  186. package/dist/tools/artifacts/TextArtifact.js +0 -4
  187. package/dist/tools/artifacts/TextArtifact.js.map +1 -1
  188. package/dist/tools/artifacts/artifacts-tool-renderer.d.ts +11 -0
  189. package/dist/tools/artifacts/artifacts-tool-renderer.d.ts.map +1 -0
  190. package/dist/tools/artifacts/artifacts-tool-renderer.js +262 -0
  191. package/dist/tools/artifacts/artifacts-tool-renderer.js.map +1 -0
  192. package/dist/tools/artifacts/artifacts.d.ts +10 -13
  193. package/dist/tools/artifacts/artifacts.d.ts.map +1 -1
  194. package/dist/tools/artifacts/artifacts.js +166 -344
  195. package/dist/tools/artifacts/artifacts.js.map +1 -1
  196. package/dist/tools/artifacts/index.d.ts +1 -0
  197. package/dist/tools/artifacts/index.d.ts.map +1 -1
  198. package/dist/tools/artifacts/index.js +1 -0
  199. package/dist/tools/artifacts/index.js.map +1 -1
  200. package/dist/tools/extract-document.d.ts +24 -0
  201. package/dist/tools/extract-document.d.ts.map +1 -0
  202. package/dist/tools/extract-document.js +193 -0
  203. package/dist/tools/extract-document.js.map +1 -0
  204. package/dist/tools/index.d.ts +9 -7
  205. package/dist/tools/index.d.ts.map +1 -1
  206. package/dist/tools/index.js +17 -13
  207. package/dist/tools/index.js.map +1 -1
  208. package/dist/tools/javascript-repl.d.ts +16 -15
  209. package/dist/tools/javascript-repl.d.ts.map +1 -1
  210. package/dist/tools/javascript-repl.js +101 -133
  211. package/dist/tools/javascript-repl.js.map +1 -1
  212. package/dist/tools/renderer-registry.d.ts +12 -0
  213. package/dist/tools/renderer-registry.d.ts.map +1 -1
  214. package/dist/tools/renderer-registry.js +78 -0
  215. package/dist/tools/renderer-registry.js.map +1 -1
  216. package/dist/tools/renderers/BashRenderer.d.ts +2 -4
  217. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -1
  218. package/dist/tools/renderers/BashRenderer.js +30 -26
  219. package/dist/tools/renderers/BashRenderer.js.map +1 -1
  220. package/dist/tools/renderers/CalculateRenderer.d.ts +2 -4
  221. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -1
  222. package/dist/tools/renderers/CalculateRenderer.js +32 -28
  223. package/dist/tools/renderers/CalculateRenderer.js.map +1 -1
  224. package/dist/tools/renderers/DefaultRenderer.d.ts +2 -4
  225. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -1
  226. package/dist/tools/renderers/DefaultRenderer.js +78 -18
  227. package/dist/tools/renderers/DefaultRenderer.js.map +1 -1
  228. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +2 -4
  229. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -1
  230. package/dist/tools/renderers/GetCurrentTimeRenderer.js +57 -21
  231. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -1
  232. package/dist/tools/types.d.ts +5 -2
  233. package/dist/tools/types.d.ts.map +1 -1
  234. package/dist/utils/i18n.d.ts +424 -1
  235. package/dist/utils/i18n.d.ts.map +1 -1
  236. package/dist/utils/i18n.js +131 -7
  237. package/dist/utils/i18n.js.map +1 -1
  238. package/example/package.json +2 -1
  239. package/example/src/custom-messages.ts +112 -0
  240. package/example/src/main.ts +391 -38
  241. package/package.json +48 -43
  242. package/scripts/count-prompt-tokens.ts +88 -0
  243. package/src/ChatPanel.ts +93 -101
  244. package/src/{state/agent-session.ts → agent/agent.ts} +80 -55
  245. package/src/{state → agent}/transports/AppTransport.ts +6 -6
  246. package/src/{state → agent}/transports/ProviderTransport.ts +13 -7
  247. package/src/{state → agent}/transports/types.ts +8 -2
  248. package/src/components/AgentInterface.ts +32 -16
  249. package/src/components/ConsoleBlock.ts +5 -1
  250. package/src/components/ExpandableSection.ts +46 -0
  251. package/src/components/MessageEditor.ts +159 -5
  252. package/src/components/MessageList.ts +18 -3
  253. package/src/components/Messages.ts +48 -89
  254. package/src/components/ProviderKeyInput.ts +6 -5
  255. package/src/components/SandboxedIframe.ts +412 -321
  256. package/src/components/message-renderer-registry.ts +28 -0
  257. package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
  258. package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  259. package/src/components/sandbox/ConsoleRuntimeProvider.ts +187 -0
  260. package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  261. package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  262. package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  263. package/src/components/sandbox/SandboxRuntimeProvider.ts +35 -0
  264. package/src/dialogs/ApiKeyPromptDialog.ts +2 -5
  265. package/src/dialogs/ModelSelector.ts +2 -2
  266. package/src/dialogs/PersistentStorageDialog.ts +141 -0
  267. package/src/dialogs/SessionListDialog.ts +148 -0
  268. package/src/dialogs/SettingsDialog.ts +1 -0
  269. package/src/index.ts +61 -20
  270. package/src/prompts/prompts.ts +282 -0
  271. package/src/storage/app-storage.ts +27 -24
  272. package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  273. package/src/storage/store.ts +33 -0
  274. package/src/storage/stores/provider-keys-store.ts +33 -0
  275. package/src/storage/stores/sessions-store.ts +130 -0
  276. package/src/storage/stores/settings-store.ts +34 -0
  277. package/src/storage/types.ts +182 -22
  278. package/src/tools/artifacts/ArtifactElement.ts +0 -1
  279. package/src/tools/artifacts/ArtifactPill.ts +25 -0
  280. package/src/tools/artifacts/Console.ts +93 -0
  281. package/src/tools/artifacts/DocxArtifact.ts +213 -0
  282. package/src/tools/artifacts/ExcelArtifact.ts +231 -0
  283. package/src/tools/artifacts/GenericArtifact.ts +117 -0
  284. package/src/tools/artifacts/HtmlArtifact.ts +64 -94
  285. package/src/tools/artifacts/ImageArtifact.ts +116 -0
  286. package/src/tools/artifacts/MarkdownArtifact.ts +0 -1
  287. package/src/tools/artifacts/PdfArtifact.ts +201 -0
  288. package/src/tools/artifacts/SvgArtifact.ts +0 -1
  289. package/src/tools/artifacts/TextArtifact.ts +0 -1
  290. package/src/tools/artifacts/artifacts-tool-renderer.ts +298 -0
  291. package/src/tools/artifacts/artifacts.ts +190 -366
  292. package/src/tools/artifacts/index.ts +1 -0
  293. package/src/tools/extract-document.ts +250 -0
  294. package/src/tools/index.ts +25 -14
  295. package/src/tools/javascript-repl.ts +138 -160
  296. package/src/tools/renderer-registry.ts +98 -0
  297. package/src/tools/renderers/BashRenderer.ts +33 -30
  298. package/src/tools/renderers/CalculateRenderer.ts +36 -31
  299. package/src/tools/renderers/DefaultRenderer.ts +84 -21
  300. package/src/tools/renderers/GetCurrentTimeRenderer.ts +68 -23
  301. package/src/tools/types.ts +10 -2
  302. package/src/utils/i18n.ts +203 -8
  303. package/dist/state/agent-session.d.ts.map +0 -1
  304. package/dist/state/agent-session.js.map +0 -1
  305. package/dist/state/transports/AppTransport.d.ts.map +0 -1
  306. package/dist/state/transports/ProviderTransport.d.ts.map +0 -1
  307. package/dist/state/transports/ProviderTransport.js.map +0 -1
  308. package/dist/state/transports/index.js.map +0 -1
  309. package/dist/state/transports/proxy-types.js.map +0 -1
  310. package/dist/state/transports/types.d.ts +0 -11
  311. package/dist/state/transports/types.d.ts.map +0 -1
  312. package/dist/storage/backends/chrome-storage-backend.d.ts +0 -18
  313. package/dist/storage/backends/chrome-storage-backend.d.ts.map +0 -1
  314. package/dist/storage/backends/chrome-storage-backend.js +0 -67
  315. package/dist/storage/backends/chrome-storage-backend.js.map +0 -1
  316. package/dist/storage/backends/indexeddb-backend.d.ts +0 -20
  317. package/dist/storage/backends/indexeddb-backend.d.ts.map +0 -1
  318. package/dist/storage/backends/indexeddb-backend.js +0 -89
  319. package/dist/storage/backends/indexeddb-backend.js.map +0 -1
  320. package/dist/storage/backends/local-storage-backend.d.ts +0 -18
  321. package/dist/storage/backends/local-storage-backend.d.ts.map +0 -1
  322. package/dist/storage/backends/local-storage-backend.js +0 -69
  323. package/dist/storage/backends/local-storage-backend.js.map +0 -1
  324. package/dist/storage/repositories/provider-keys-repository.d.ts +0 -34
  325. package/dist/storage/repositories/provider-keys-repository.d.ts.map +0 -1
  326. package/dist/storage/repositories/provider-keys-repository.js +0 -50
  327. package/dist/storage/repositories/provider-keys-repository.js.map +0 -1
  328. package/dist/storage/repositories/settings-repository.d.ts +0 -34
  329. package/dist/storage/repositories/settings-repository.d.ts.map +0 -1
  330. package/dist/storage/repositories/settings-repository.js +0 -46
  331. package/dist/storage/repositories/settings-repository.js.map +0 -1
  332. package/src/storage/backends/chrome-storage-backend.ts +0 -82
  333. package/src/storage/backends/indexeddb-backend.ts +0 -107
  334. package/src/storage/backends/local-storage-backend.ts +0 -74
  335. package/src/storage/repositories/provider-keys-repository.ts +0 -55
  336. package/src/storage/repositories/settings-repository.ts +0 -51
  337. /package/dist/{state → agent}/transports/index.d.ts +0 -0
  338. /package/dist/{state → agent}/transports/index.js +0 -0
  339. /package/dist/{state → agent}/transports/proxy-types.d.ts +0 -0
  340. /package/dist/{state → agent}/transports/proxy-types.js +0 -0
  341. /package/dist/{state → agent}/transports/types.js +0 -0
  342. /package/dist/{state → agent}/types.d.ts +0 -0
  343. /package/dist/{state → agent}/types.js +0 -0
  344. /package/src/{state → agent}/transports/index.ts +0 -0
  345. /package/src/{state → agent}/transports/proxy-types.ts +0 -0
  346. /package/src/{state → agent}/types.ts +0 -0
@@ -1,6 +1,9 @@
1
1
  import { LitElement } from "lit";
2
2
  import { customElement, property } from "lit/decorators.js";
3
- import type { Attachment } from "../utils/attachment-utils.js";
3
+ import { ConsoleRuntimeProvider } from "./sandbox/ConsoleRuntimeProvider.js";
4
+ import { RuntimeMessageBridge } from "./sandbox/RuntimeMessageBridge.js";
5
+ import { type MessageConsumer, RUNTIME_MESSAGE_ROUTER } from "./sandbox/RuntimeMessageRouter.js";
6
+ import type { SandboxRuntimeProvider } from "./sandbox/SandboxRuntimeProvider.js";
4
7
 
5
8
  export interface SandboxFile {
6
9
  fileName: string;
@@ -13,6 +16,7 @@ export interface SandboxResult {
13
16
  console: Array<{ type: string; text: string }>;
14
17
  files?: SandboxFile[];
15
18
  error?: { message: string; stack: string };
19
+ returnValue?: any;
16
20
  }
17
21
 
18
22
  /**
@@ -21,6 +25,25 @@ export interface SandboxResult {
21
25
  */
22
26
  export type SandboxUrlProvider = () => string;
23
27
 
28
+ /**
29
+ * Configuration for prepareHtmlDocument
30
+ */
31
+ export interface PrepareHtmlOptions {
32
+ /** True if this is an HTML artifact (inject into existing HTML), false if REPL (wrap in HTML) */
33
+ isHtmlArtifact: boolean;
34
+ /** True if this is a standalone download (no runtime bridge, no navigation interceptor) */
35
+ isStandalone?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Escape HTML special sequences in code to prevent premature tag closure
40
+ * @param code Code that will be injected into <script> tags
41
+ * @returns Escaped code safe for injection
42
+ */
43
+ function escapeScriptContent(code: string): string {
44
+ return code.replace(/<\/script/gi, "<\\/script");
45
+ }
46
+
24
47
  @customElement("sandbox-iframe")
25
48
  export class SandboxIframe extends LitElement {
26
49
  private iframe?: HTMLIFrameElement;
@@ -42,6 +65,9 @@ export class SandboxIframe extends LitElement {
42
65
 
43
66
  override disconnectedCallback() {
44
67
  super.disconnectedCallback();
68
+ // Note: We don't unregister the sandbox here for loadContent() mode
69
+ // because the caller (HtmlArtifact) owns the sandbox lifecycle.
70
+ // For execute() mode, the sandbox is unregistered in the cleanup function.
45
71
  this.iframe?.remove();
46
72
  }
47
73
 
@@ -49,65 +75,157 @@ export class SandboxIframe extends LitElement {
49
75
  * Load HTML content into sandbox and keep it displayed (for HTML artifacts)
50
76
  * @param sandboxId Unique ID
51
77
  * @param htmlContent Full HTML content
52
- * @param attachments Attachments available
78
+ * @param providers Runtime providers to inject
79
+ * @param consumers Message consumers to register (optional)
53
80
  */
54
- public loadContent(sandboxId: string, htmlContent: string, attachments: Attachment[]): void {
55
- const completeHtml = this.prepareHtmlDocument(sandboxId, htmlContent, attachments);
81
+ public loadContent(
82
+ sandboxId: string,
83
+ htmlContent: string,
84
+ providers: SandboxRuntimeProvider[] = [],
85
+ consumers: MessageConsumer[] = [],
86
+ ): void {
87
+ // Unregister previous sandbox if exists
88
+ try {
89
+ RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
90
+ } catch {
91
+ // Sandbox might not exist, that's ok
92
+ }
93
+
94
+ providers = [new ConsoleRuntimeProvider(), ...providers];
95
+
96
+ RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
97
+
98
+ // loadContent is always used for HTML artifacts (not standalone)
99
+ const completeHtml = this.prepareHtmlDocument(sandboxId, htmlContent, providers, {
100
+ isHtmlArtifact: true,
101
+ isStandalone: false,
102
+ });
103
+
104
+ // Validate HTML before loading
105
+ const validationError = this.validateHtml(completeHtml);
106
+ if (validationError) {
107
+ console.error("HTML validation failed:", validationError);
108
+ // Show error in iframe instead of crashing
109
+ this.iframe?.remove();
110
+ this.iframe = document.createElement("iframe");
111
+ this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
112
+ this.iframe.srcdoc = `
113
+ <html>
114
+ <body style="font-family: monospace; padding: 20px; background: #fff; color: #000;">
115
+ <h3 style="color: #c00;">HTML Validation Error</h3>
116
+ <pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap;">${validationError}</pre>
117
+ </body>
118
+ </html>
119
+ `;
120
+ this.appendChild(this.iframe);
121
+ return;
122
+ }
123
+
124
+ // Remove previous iframe if exists
125
+ this.iframe?.remove();
56
126
 
57
127
  if (this.sandboxUrlProvider) {
58
128
  // Browser extension mode: use sandbox.html with postMessage
59
- this.loadViaSandboxUrl(sandboxId, completeHtml, attachments);
129
+ this.loadViaSandboxUrl(sandboxId, completeHtml);
60
130
  } else {
61
131
  // Web mode: use srcdoc
62
- this.loadViaSrcdoc(completeHtml);
132
+ this.loadViaSrcdoc(sandboxId, completeHtml);
63
133
  }
64
134
  }
65
135
 
66
- private loadViaSandboxUrl(sandboxId: string, completeHtml: string, attachments: Attachment[]): void {
67
- // Wait for sandbox-ready and send content
136
+ private loadViaSandboxUrl(sandboxId: string, completeHtml: string): void {
137
+ // Create iframe pointing to sandbox URL
138
+ this.iframe = document.createElement("iframe");
139
+ this.iframe.sandbox.add("allow-scripts");
140
+ this.iframe.sandbox.add("allow-modals");
141
+ this.iframe.style.width = "100%";
142
+ this.iframe.style.height = "100%";
143
+ this.iframe.style.border = "none";
144
+ this.iframe.src = this.sandboxUrlProvider!();
145
+
146
+ // Update router with iframe reference BEFORE appending to DOM
147
+ RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
148
+
149
+ // Listen for open-external-url messages from iframe
150
+ const externalUrlHandler = (e: MessageEvent) => {
151
+ if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
152
+ // Use chrome.tabs API to open in new tab
153
+ const chromeAPI = (globalThis as any).chrome;
154
+ if (chromeAPI?.tabs) {
155
+ chromeAPI.tabs.create({ url: e.data.url });
156
+ } else {
157
+ // Fallback for non-extension context
158
+ window.open(e.data.url, "_blank");
159
+ }
160
+ }
161
+ };
162
+ window.addEventListener("message", externalUrlHandler);
163
+
164
+ // Listen for sandbox-ready and sandbox-error messages directly
68
165
  const readyHandler = (e: MessageEvent) => {
69
166
  if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
70
167
  window.removeEventListener("message", readyHandler);
168
+ window.removeEventListener("message", errorHandler);
169
+
170
+ // Send content to sandbox
71
171
  this.iframe?.contentWindow?.postMessage(
72
172
  {
73
173
  type: "sandbox-load",
74
174
  sandboxId,
75
175
  code: completeHtml,
76
- attachments,
77
176
  },
78
177
  "*",
79
178
  );
80
179
  }
81
180
  };
82
- window.addEventListener("message", readyHandler);
83
181
 
84
- // Always recreate iframe to ensure fresh sandbox and sandbox-ready message
85
- this.iframe?.remove();
86
- this.iframe = document.createElement("iframe");
87
- this.iframe.sandbox.add("allow-scripts");
88
- this.iframe.sandbox.add("allow-modals");
89
- this.iframe.style.width = "100%";
90
- this.iframe.style.height = "100%";
91
- this.iframe.style.border = "none";
182
+ const errorHandler = (e: MessageEvent) => {
183
+ if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
184
+ window.removeEventListener("message", readyHandler);
185
+ window.removeEventListener("message", errorHandler);
92
186
 
93
- this.iframe.src = this.sandboxUrlProvider!();
187
+ // The sandbox.js already sent us the error via postMessage.
188
+ // We need to convert it to an execution-error message that the execute() consumer will handle.
189
+ // Simulate receiving an execution-error from the sandbox
190
+ window.postMessage(
191
+ {
192
+ sandboxId: sandboxId,
193
+ type: "execution-error",
194
+ error: { message: e.data.error, stack: e.data.stack },
195
+ },
196
+ "*",
197
+ );
198
+ }
199
+ };
200
+
201
+ window.addEventListener("message", readyHandler);
202
+ window.addEventListener("message", errorHandler);
94
203
 
95
204
  this.appendChild(this.iframe);
96
205
  }
97
206
 
98
- private loadViaSrcdoc(completeHtml: string): void {
99
- // Always recreate iframe to ensure fresh sandbox
100
- this.iframe?.remove();
207
+ private loadViaSrcdoc(sandboxId: string, completeHtml: string): void {
208
+ // Create iframe with srcdoc
101
209
  this.iframe = document.createElement("iframe");
102
210
  this.iframe.sandbox.add("allow-scripts");
103
211
  this.iframe.sandbox.add("allow-modals");
104
212
  this.iframe.style.width = "100%";
105
213
  this.iframe.style.height = "100%";
106
214
  this.iframe.style.border = "none";
107
-
108
- // Set content directly via srcdoc (no CSP restrictions in web apps)
109
215
  this.iframe.srcdoc = completeHtml;
110
216
 
217
+ // Update router with iframe reference BEFORE appending to DOM
218
+ RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
219
+
220
+ // Listen for open-external-url messages from iframe
221
+ const externalUrlHandler = (e: MessageEvent) => {
222
+ if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
223
+ // Fallback for non-extension context
224
+ window.open(e.data.url, "_blank");
225
+ }
226
+ };
227
+ window.addEventListener("message", externalUrlHandler);
228
+
111
229
  this.appendChild(this.iframe);
112
230
  }
113
231
 
@@ -115,159 +233,215 @@ export class SandboxIframe extends LitElement {
115
233
  * Execute code in sandbox
116
234
  * @param sandboxId Unique ID for this execution
117
235
  * @param code User code (plain JS for REPL, or full HTML for artifacts)
118
- * @param attachments Attachments available to the code
236
+ * @param providers Runtime providers to inject
237
+ * @param consumers Additional message consumers (optional, execute has its own internal consumer)
119
238
  * @param signal Abort signal
120
239
  * @returns Promise resolving to execution result
121
240
  */
122
241
  public async execute(
123
242
  sandboxId: string,
124
243
  code: string,
125
- attachments: Attachment[],
244
+ providers: SandboxRuntimeProvider[] = [],
245
+ consumers: MessageConsumer[] = [],
126
246
  signal?: AbortSignal,
247
+ isHtmlArtifact: boolean = false,
127
248
  ): Promise<SandboxResult> {
128
249
  if (signal?.aborted) {
129
250
  throw new Error("Execution aborted");
130
251
  }
131
252
 
132
- // Prepare the complete HTML document with runtime + user code
133
- const completeHtml = this.prepareHtmlDocument(sandboxId, code, attachments);
253
+ const consoleProvider = new ConsoleRuntimeProvider();
254
+ providers = [consoleProvider, ...providers];
255
+ RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
256
+
257
+ const files: SandboxFile[] = [];
258
+ let completed = false;
134
259
 
135
- // Wait for execution to complete
136
260
  return new Promise((resolve, reject) => {
137
- const logs: Array<{ type: string; text: string }> = [];
138
- const files: SandboxFile[] = [];
139
- let completed = false;
140
-
141
- const messageHandler = (e: MessageEvent) => {
142
- // Ignore messages not for this sandbox
143
- if (e.data.sandboxId !== sandboxId) return;
144
-
145
- if (e.data.type === "console") {
146
- logs.push({
147
- type: e.data.method === "error" ? "error" : "log",
148
- text: e.data.text,
149
- });
150
- } else if (e.data.type === "file-returned") {
151
- files.push({
152
- fileName: e.data.fileName,
153
- content: e.data.content,
154
- mimeType: e.data.mimeType,
155
- });
156
- } else if (e.data.type === "execution-complete") {
157
- completed = true;
158
- cleanup();
159
- resolve({
160
- success: true,
161
- console: logs,
162
- files: files,
163
- });
164
- } else if (e.data.type === "execution-error") {
165
- completed = true;
166
- cleanup();
167
- resolve({
168
- success: false,
169
- console: logs,
170
- error: e.data.error,
171
- files,
172
- });
173
- }
261
+ // 4. Create execution consumer for lifecycle messages
262
+ const executionConsumer: MessageConsumer = {
263
+ async handleMessage(message: any): Promise<void> {
264
+ if (message.type === "file-returned") {
265
+ files.push({
266
+ fileName: message.fileName,
267
+ content: message.content,
268
+ mimeType: message.mimeType,
269
+ });
270
+ } else if (message.type === "execution-complete") {
271
+ completed = true;
272
+ cleanup();
273
+ resolve({
274
+ success: true,
275
+ console: consoleProvider.getLogs(),
276
+ files,
277
+ returnValue: message.returnValue,
278
+ });
279
+ } else if (message.type === "execution-error") {
280
+ completed = true;
281
+ cleanup();
282
+ resolve({ success: false, console: consoleProvider.getLogs(), error: message.error, files });
283
+ }
284
+ },
174
285
  };
175
286
 
287
+ RUNTIME_MESSAGE_ROUTER.addConsumer(sandboxId, executionConsumer);
288
+
289
+ const cleanup = () => {
290
+ RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
291
+ signal?.removeEventListener("abort", abortHandler);
292
+ clearTimeout(timeoutId);
293
+ this.iframe?.remove();
294
+ this.iframe = undefined;
295
+ };
296
+
297
+ // Abort handler
176
298
  const abortHandler = () => {
177
299
  if (!completed) {
300
+ completed = true;
178
301
  cleanup();
179
302
  reject(new Error("Execution aborted"));
180
303
  }
181
304
  };
182
305
 
183
- let readyHandler: ((e: MessageEvent) => void) | undefined;
184
-
185
- const cleanup = () => {
186
- window.removeEventListener("message", messageHandler);
187
- signal?.removeEventListener("abort", abortHandler);
188
- if (readyHandler) {
189
- window.removeEventListener("message", readyHandler);
190
- }
191
- clearTimeout(timeoutId);
192
- };
193
-
194
- // Set up listeners BEFORE creating iframe
195
- window.addEventListener("message", messageHandler);
196
- signal?.addEventListener("abort", abortHandler);
306
+ if (signal) {
307
+ signal.addEventListener("abort", abortHandler);
308
+ }
197
309
 
198
- // Timeout after 30 seconds
310
+ // Timeout handler (30 seconds)
199
311
  const timeoutId = setTimeout(() => {
200
312
  if (!completed) {
313
+ completed = true;
201
314
  cleanup();
202
315
  resolve({
203
316
  success: false,
204
- error: { message: "Execution timeout (30s)", stack: "" },
205
- console: logs,
317
+ console: consoleProvider.getLogs(),
318
+ error: { message: "Execution timeout (120s)", stack: "" },
206
319
  files,
207
320
  });
208
321
  }
209
- }, 30000);
322
+ }, 120000);
323
+
324
+ // 4. Prepare HTML and create iframe
325
+ const completeHtml = this.prepareHtmlDocument(sandboxId, code, providers, {
326
+ isHtmlArtifact,
327
+ isStandalone: false,
328
+ });
329
+
330
+ // 5. Validate HTML before sending to sandbox
331
+ const validationError = this.validateHtml(completeHtml);
332
+ if (validationError) {
333
+ reject(new Error(`HTML validation failed: ${validationError}`));
334
+ return;
335
+ }
210
336
 
211
337
  if (this.sandboxUrlProvider) {
212
- // Browser extension mode: wait for sandbox-ready and send content
213
- readyHandler = (e: MessageEvent) => {
338
+ // Browser extension mode: wait for sandbox-ready
339
+ this.iframe = document.createElement("iframe");
340
+ this.iframe.sandbox.add("allow-scripts", "allow-modals");
341
+ this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
342
+ this.iframe.src = this.sandboxUrlProvider();
343
+
344
+ // Update router with iframe reference BEFORE appending to DOM
345
+ RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
346
+
347
+ // Listen for sandbox-ready and sandbox-error messages
348
+ const readyHandler = (e: MessageEvent) => {
214
349
  if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
215
- window.removeEventListener("message", readyHandler!);
216
- // Send the complete HTML
350
+ window.removeEventListener("message", readyHandler);
351
+ window.removeEventListener("message", errorHandler);
352
+
353
+ // Send content to sandbox
217
354
  this.iframe?.contentWindow?.postMessage(
218
355
  {
219
356
  type: "sandbox-load",
220
357
  sandboxId,
221
358
  code: completeHtml,
222
- attachments,
223
359
  },
224
360
  "*",
225
361
  );
226
362
  }
227
363
  };
228
- window.addEventListener("message", readyHandler);
229
364
 
230
- // Create iframe AFTER all listeners are set up
231
- this.iframe?.remove();
232
- this.iframe = document.createElement("iframe");
233
- this.iframe.sandbox.add("allow-scripts");
234
- this.iframe.sandbox.add("allow-modals");
235
- this.iframe.style.width = "100%";
236
- this.iframe.style.height = "100%";
237
- this.iframe.style.border = "none";
365
+ const errorHandler = (e: MessageEvent) => {
366
+ if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
367
+ window.removeEventListener("message", readyHandler);
368
+ window.removeEventListener("message", errorHandler);
238
369
 
239
- this.iframe.src = this.sandboxUrlProvider();
370
+ // Convert sandbox-error to execution-error for the execution consumer
371
+ window.postMessage(
372
+ {
373
+ sandboxId: sandboxId,
374
+ type: "execution-error",
375
+ error: { message: e.data.error, stack: e.data.stack },
376
+ },
377
+ "*",
378
+ );
379
+ }
380
+ };
381
+
382
+ window.addEventListener("message", readyHandler);
383
+ window.addEventListener("message", errorHandler);
240
384
 
241
385
  this.appendChild(this.iframe);
242
386
  } else {
243
387
  // Web mode: use srcdoc
244
- this.iframe?.remove();
245
388
  this.iframe = document.createElement("iframe");
246
- this.iframe.sandbox.add("allow-scripts");
247
- this.iframe.sandbox.add("allow-modals");
248
- this.iframe.style.width = "100%";
249
- this.iframe.style.height = "100%";
250
- this.iframe.style.border = "none";
251
-
252
- // Set content via srcdoc BEFORE appending to DOM
389
+ this.iframe.sandbox.add("allow-scripts", "allow-modals");
390
+ this.iframe.style.cssText = "width: 100%; height: 100%; border: none; display: none;";
253
391
  this.iframe.srcdoc = completeHtml;
254
392
 
393
+ // Update router with iframe reference BEFORE appending to DOM
394
+ RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
395
+
255
396
  this.appendChild(this.iframe);
256
397
  }
257
398
  });
258
399
  }
259
400
 
401
+ /**
402
+ * Validate HTML using DOMParser - returns error message if invalid, null if valid
403
+ * Note: JavaScript syntax validation is done in sandbox.js to avoid CSP restrictions
404
+ */
405
+ private validateHtml(html: string): string | null {
406
+ try {
407
+ const parser = new DOMParser();
408
+ const doc = parser.parseFromString(html, "text/html");
409
+
410
+ // Check for parser errors
411
+ const parserError = doc.querySelector("parsererror");
412
+ if (parserError) {
413
+ return parserError.textContent || "Unknown parse error";
414
+ }
415
+
416
+ return null;
417
+ } catch (error: any) {
418
+ return error.message || "Unknown validation error";
419
+ }
420
+ }
421
+
260
422
  /**
261
423
  * Prepare complete HTML document with runtime + user code
424
+ * PUBLIC so HtmlArtifact can use it for download button
262
425
  */
263
- private prepareHtmlDocument(sandboxId: string, userCode: string, attachments: Attachment[]): string {
264
- // Runtime script that will be injected
265
- const runtime = this.getRuntimeScript(sandboxId, attachments);
426
+ public prepareHtmlDocument(
427
+ sandboxId: string,
428
+ userCode: string,
429
+ providers: SandboxRuntimeProvider[] = [],
430
+ options?: PrepareHtmlOptions,
431
+ ): string {
432
+ // Default options
433
+ const opts: PrepareHtmlOptions = {
434
+ isHtmlArtifact: false,
435
+ isStandalone: false,
436
+ ...options,
437
+ };
266
438
 
267
- // Check if user provided full HTML
268
- const hasHtmlTag = /<html[^>]*>/i.test(userCode);
439
+ // Runtime script that will be injected
440
+ const runtime = this.getRuntimeScript(sandboxId, providers, opts.isStandalone || false);
269
441
 
270
- if (hasHtmlTag) {
442
+ // Only check for HTML tags if explicitly marked as HTML artifact
443
+ // For javascript_repl, userCode is JavaScript that may contain HTML in string literals
444
+ if (opts.isHtmlArtifact) {
271
445
  // HTML Artifact - inject runtime into existing HTML
272
446
  const headMatch = userCode.match(/<head[^>]*>/i);
273
447
  if (headMatch) {
@@ -285,6 +459,9 @@ export class SandboxIframe extends LitElement {
285
459
  return runtime + userCode;
286
460
  } else {
287
461
  // REPL - wrap code in HTML with runtime and call complete() when done
462
+ // Escape </script> in user code to prevent premature tag closure
463
+ const escapedUserCode = escapeScriptContent(userCode);
464
+
288
465
  return `<!DOCTYPE html>
289
466
  <html>
290
467
  <head>
@@ -294,11 +471,35 @@ export class SandboxIframe extends LitElement {
294
471
  <script type="module">
295
472
  (async () => {
296
473
  try {
297
- ${userCode}
298
- window.complete();
474
+ // Wrap user code in async function to capture return value
475
+ const userCodeFunc = async () => {
476
+ ${escapedUserCode}
477
+ };
478
+
479
+ const returnValue = await userCodeFunc();
480
+
481
+ // Call completion callbacks before complete()
482
+ if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
483
+ try {
484
+ await Promise.all(window.__completionCallbacks.map(cb => cb(true)));
485
+ } catch (e) {
486
+ console.error('Completion callback error:', e);
487
+ }
488
+ }
489
+
490
+ await window.complete(null, returnValue);
299
491
  } catch (error) {
300
- console.error(error?.stack || error?.message || String(error));
301
- window.complete({
492
+
493
+ // Call completion callbacks before complete() (error path)
494
+ if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
495
+ try {
496
+ await Promise.all(window.__completionCallbacks.map(cb => cb(false)));
497
+ } catch (e) {
498
+ console.error('Completion callback error:', e);
499
+ }
500
+ }
501
+
502
+ await window.complete({
302
503
  message: error?.message || String(error),
303
504
  stack: error?.stack || new Error().stack
304
505
  });
@@ -311,215 +512,105 @@ export class SandboxIframe extends LitElement {
311
512
  }
312
513
 
313
514
  /**
314
- * Get the runtime script that captures console, provides helpers, etc.
515
+ * Generate runtime script from providers
516
+ * @param sandboxId Unique sandbox ID
517
+ * @param providers Runtime providers
518
+ * @param isStandalone If true, skip runtime bridge and navigation interceptor (for standalone downloads)
315
519
  */
316
- private getRuntimeScript(sandboxId: string, attachments: Attachment[]): string {
317
- // Convert attachments to serializable format
318
- const attachmentsData = attachments.map((a) => ({
319
- id: a.id,
320
- fileName: a.fileName,
321
- mimeType: a.mimeType,
322
- size: a.size,
323
- content: a.content,
324
- extractedText: a.extractedText,
325
- }));
326
-
327
- // Runtime function that will run in the sandbox (NO parameters - values injected before function)
328
- const runtimeFunc = () => {
329
- // Helper functions
330
- (window as any).listFiles = () =>
331
- (attachments || []).map((a: any) => ({
332
- id: a.id,
333
- fileName: a.fileName,
334
- mimeType: a.mimeType,
335
- size: a.size,
336
- }));
337
-
338
- (window as any).readTextFile = (attachmentId: string) => {
339
- const a = (attachments || []).find((x: any) => x.id === attachmentId);
340
- if (!a) throw new Error("Attachment not found: " + attachmentId);
341
- if (a.extractedText) return a.extractedText;
342
- try {
343
- return atob(a.content);
344
- } catch {
345
- throw new Error("Failed to decode text content for: " + attachmentId);
346
- }
347
- };
348
-
349
- (window as any).readBinaryFile = (attachmentId: string) => {
350
- const a = (attachments || []).find((x: any) => x.id === attachmentId);
351
- if (!a) throw new Error("Attachment not found: " + attachmentId);
352
- const bin = atob(a.content);
353
- const bytes = new Uint8Array(bin.length);
354
- for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
355
- return bytes;
356
- };
357
-
358
- (window as any).returnFile = async (fileName: string, content: any, mimeType?: string) => {
359
- let finalContent: any, finalMimeType: string;
360
-
361
- if (content instanceof Blob) {
362
- const arrayBuffer = await content.arrayBuffer();
363
- finalContent = new Uint8Array(arrayBuffer);
364
- finalMimeType = mimeType || content.type || "application/octet-stream";
365
- if (!mimeType && !content.type) {
366
- throw new Error(
367
- "returnFile: MIME type is required for Blob content. Please provide a mimeType parameter (e.g., 'image/png').",
368
- );
369
- }
370
- } else if (content instanceof Uint8Array) {
371
- finalContent = content;
372
- if (!mimeType) {
373
- throw new Error(
374
- "returnFile: MIME type is required for Uint8Array content. Please provide a mimeType parameter (e.g., 'image/png').",
375
- );
376
- }
377
- finalMimeType = mimeType;
378
- } else if (typeof content === "string") {
379
- finalContent = content;
380
- finalMimeType = mimeType || "text/plain";
381
- } else {
382
- finalContent = JSON.stringify(content, null, 2);
383
- finalMimeType = mimeType || "application/json";
384
- }
385
-
386
- window.parent.postMessage(
387
- {
388
- type: "file-returned",
389
- sandboxId,
390
- fileName,
391
- content: finalContent,
392
- mimeType: finalMimeType,
393
- },
394
- "*",
395
- );
396
- };
397
-
398
- // Console capture
399
- const originalConsole = {
400
- log: console.log,
401
- error: console.error,
402
- warn: console.warn,
403
- info: console.info,
404
- };
405
-
406
- ["log", "error", "warn", "info"].forEach((method) => {
407
- (console as any)[method] = (...args: any[]) => {
408
- const text = args
409
- .map((arg) => {
410
- try {
411
- return typeof arg === "object" ? JSON.stringify(arg) : String(arg);
412
- } catch {
413
- return String(arg);
414
- }
415
- })
416
- .join(" ");
417
-
418
- window.parent.postMessage(
419
- {
420
- type: "console",
421
- sandboxId,
422
- method,
423
- text,
424
- },
425
- "*",
426
- );
427
-
428
- (originalConsole as any)[method].apply(console, args);
429
- };
430
- });
431
-
432
- // Track errors for HTML artifacts
433
- let lastError: { message: string; stack: string } | null = null;
434
-
435
- // Error handlers
436
- window.addEventListener("error", (e) => {
437
- const text =
438
- (e.error?.stack || e.message || String(e)) + " at line " + (e.lineno || "?") + ":" + (e.colno || "?");
439
-
440
- // Store the error
441
- lastError = {
442
- message: e.error?.message || e.message || String(e),
443
- stack: e.error?.stack || text,
444
- };
445
-
446
- window.parent.postMessage(
447
- {
448
- type: "console",
449
- sandboxId,
450
- method: "error",
451
- text,
452
- },
453
- "*",
454
- );
455
- });
456
-
457
- window.addEventListener("unhandledrejection", (e) => {
458
- const text = "Unhandled promise rejection: " + (e.reason?.message || e.reason || "Unknown error");
459
-
460
- // Store the error
461
- lastError = {
462
- message: e.reason?.message || String(e.reason) || "Unhandled promise rejection",
463
- stack: e.reason?.stack || text,
464
- };
520
+ private getRuntimeScript(
521
+ sandboxId: string,
522
+ providers: SandboxRuntimeProvider[] = [],
523
+ isStandalone: boolean = false,
524
+ ): string {
525
+ // Collect all data from providers
526
+ const allData: Record<string, any> = {};
527
+ for (const provider of providers) {
528
+ Object.assign(allData, provider.getData());
529
+ }
465
530
 
466
- window.parent.postMessage(
467
- {
468
- type: "console",
469
- sandboxId,
470
- method: "error",
471
- text,
472
- },
473
- "*",
474
- );
475
- });
531
+ // Generate bridge code (skip if standalone)
532
+ const bridgeCode = isStandalone
533
+ ? ""
534
+ : RuntimeMessageBridge.generateBridgeCode({
535
+ context: "sandbox-iframe",
536
+ sandboxId,
537
+ });
476
538
 
477
- // Expose complete() method for user code to call
478
- let completionSent = false;
479
- (window as any).complete = (error?: { message: string; stack: string }) => {
480
- if (completionSent) return;
481
- completionSent = true;
482
-
483
- // Use provided error or last caught error
484
- const finalError = error || lastError;
485
-
486
- if (finalError) {
487
- window.parent.postMessage(
488
- {
489
- type: "execution-error",
490
- sandboxId,
491
- error: finalError,
492
- },
493
- "*",
494
- );
495
- } else {
496
- window.parent.postMessage(
497
- {
498
- type: "execution-complete",
499
- sandboxId,
500
- },
501
- "*",
502
- );
503
- }
504
- };
539
+ // Collect all runtime functions - pass sandboxId as string literal
540
+ const runtimeFunctions: string[] = [];
541
+ for (const provider of providers) {
542
+ runtimeFunctions.push(`(${provider.getRuntime().toString()})(${JSON.stringify(sandboxId)});`);
543
+ }
505
544
 
506
- // Fallback timeout for HTML artifacts that don't call complete()
507
- if (document.readyState === "complete" || document.readyState === "interactive") {
508
- setTimeout(() => (window as any).complete(), 2000);
509
- } else {
510
- window.addEventListener("load", () => {
511
- setTimeout(() => (window as any).complete(), 2000);
512
- });
545
+ // Build script with HTML escaping
546
+ // Escape </script> to prevent premature tag closure in HTML parser
547
+ const dataInjection = Object.entries(allData)
548
+ .map(([key, value]) => {
549
+ const jsonStr = JSON.stringify(value).replace(/<\/script/gi, "<\\/script");
550
+ return `window.${key} = ${jsonStr};`;
551
+ })
552
+ .join("\n");
553
+
554
+ // TODO the font-size is needed, as chrome seems to inject a stylesheet into iframes
555
+ // found in an extension context like sidepanel, settin body { font-size: 75% }. It's
556
+ // definitely not our code doing that.
557
+ // See https://stackoverflow.com/questions/71480433/chrome-is-injecting-some-stylesheet-in-popup-ui-which-reduces-the-font-size-to-7
558
+
559
+ // Navigation interceptor (only if NOT standalone)
560
+ const navigationInterceptor = isStandalone
561
+ ? ""
562
+ : `
563
+ // Navigation interceptor: prevent all navigation and open externally
564
+ (function() {
565
+ // Intercept link clicks
566
+ document.addEventListener('click', function(e) {
567
+ const link = e.target.closest('a');
568
+ if (link && link.href) {
569
+ // Check if it's an external link (not javascript: or #hash)
570
+ if (link.href.startsWith('http://') || link.href.startsWith('https://')) {
571
+ e.preventDefault();
572
+ e.stopPropagation();
573
+ window.parent.postMessage({ type: 'open-external-url', url: link.href }, '*');
513
574
  }
514
- };
575
+ }
576
+ }, true);
577
+
578
+ // Intercept form submissions
579
+ document.addEventListener('submit', function(e) {
580
+ const form = e.target;
581
+ if (form && form.action) {
582
+ e.preventDefault();
583
+ e.stopPropagation();
584
+ window.parent.postMessage({ type: 'open-external-url', url: form.action }, '*');
585
+ }
586
+ }, true);
587
+
588
+ // Prevent window.location changes (only if not already redefined)
589
+ try {
590
+ const originalLocation = window.location;
591
+ Object.defineProperty(window, 'location', {
592
+ get: function() { return originalLocation; },
593
+ set: function(url) {
594
+ window.parent.postMessage({ type: 'open-external-url', url: url.toString() }, '*');
595
+ }
596
+ });
597
+ } catch (e) {
598
+ // Already defined, skip
599
+ }
600
+ })();
601
+ `;
515
602
 
516
- // Prepend the const declarations, then the function
517
- return (
518
- `<script>\n` +
519
- `window.sandboxId = ${JSON.stringify(sandboxId)};\n` +
520
- `window.attachments = ${JSON.stringify(attachmentsData)};\n` +
521
- `(${runtimeFunc.toString()})();\n` +
522
- `</script>`
523
- );
603
+ return `<style>
604
+ html, body {
605
+ font-size: initial;
606
+ }
607
+ </style>
608
+ <script>
609
+ window.sandboxId = ${JSON.stringify(sandboxId)};
610
+ ${dataInjection}
611
+ ${bridgeCode}
612
+ ${runtimeFunctions.join("\n")}
613
+ ${navigationInterceptor}
614
+ </script>`;
524
615
  }
525
616
  }