@theoremjs/studio 0.0.0-stage → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -2
  3. package/agreement.d.ts +14 -0
  4. package/agreement.js +80 -0
  5. package/asks.d.ts +47 -0
  6. package/asks.js +54 -0
  7. package/browser-connection.d.ts +49 -0
  8. package/browser-connection.js +215 -0
  9. package/browser-engine.d.ts +11 -0
  10. package/browser-engine.js +11 -0
  11. package/browser-execution.d.ts +6 -0
  12. package/browser-execution.js +18 -0
  13. package/browser-live.d.ts +4 -0
  14. package/browser-live.js +132 -0
  15. package/browser-transport.d.ts +33 -0
  16. package/browser-transport.js +229 -0
  17. package/browser.d.ts +4 -0
  18. package/browser.js +4 -0
  19. package/canonical.d.ts +10 -0
  20. package/canonical.js +25 -0
  21. package/compile-workspace.d.ts +28 -0
  22. package/compile-workspace.js +295 -0
  23. package/compile.d.ts +65 -0
  24. package/compile.js +1605 -0
  25. package/concierge-demo.d.ts +17 -0
  26. package/concierge-demo.js +1230 -0
  27. package/conversation-key.d.ts +5 -0
  28. package/conversation-key.js +10 -0
  29. package/decision-example-images.d.ts +30 -0
  30. package/decision-example-images.js +30 -0
  31. package/deno.json +3 -0
  32. package/draft.d.ts +407 -0
  33. package/draft.js +505 -0
  34. package/editors.d.ts +29 -0
  35. package/editors.js +51 -0
  36. package/example-embedding-files.d.ts +11 -0
  37. package/example-embedding-files.js +20 -0
  38. package/example.d.ts +36 -0
  39. package/example.js +357 -0
  40. package/guardrail-probe.d.ts +100 -0
  41. package/guardrail-probe.js +722 -0
  42. package/held-credentials.d.ts +28 -0
  43. package/held-credentials.js +87 -0
  44. package/installed.mjs +40 -0
  45. package/lent-keys.d.ts +122 -0
  46. package/lent-keys.js +47 -0
  47. package/line-diff.d.ts +15 -0
  48. package/line-diff.js +41 -0
  49. package/live-connection.d.ts +22 -0
  50. package/live-connection.js +27 -0
  51. package/live-session-bridge.d.ts +15 -0
  52. package/live-session-bridge.js +202 -0
  53. package/map-line.d.ts +19 -0
  54. package/map-line.js +42 -0
  55. package/map.d.ts +154 -0
  56. package/map.js +525 -0
  57. package/media-accept.d.ts +19 -0
  58. package/media-accept.js +65 -0
  59. package/mod.d.ts +39 -0
  60. package/mod.js +33 -0
  61. package/origins.d.ts +52 -0
  62. package/origins.js +165 -0
  63. package/package.json +85 -4
  64. package/page/assets/Banner-BISz3nXA.js +2 -0
  65. package/page/assets/BottomSheet-BwfBDtje.js +1 -0
  66. package/page/assets/Card-Lp-0P5eL.js +1 -0
  67. package/page/assets/CardWall-CBRLdDZW.js +1 -0
  68. package/page/assets/Center-nHS1JSux.js +1 -0
  69. package/page/assets/CheckboxInput-Cj5pk-KI.js +2 -0
  70. package/page/assets/CodeBlock-C7XJ4ioE.js +51 -0
  71. package/page/assets/CollapsibleGroup-N7_SOjbm.js +2 -0
  72. package/page/assets/ContextMenu-CBu9H-9H.js +2 -0
  73. package/page/assets/DateInput-BGrQ5tPC.js +1 -0
  74. package/page/assets/Dialog-DELWQo-v.js +1 -0
  75. package/page/assets/EmptyState-C8W4JKBZ.js +1 -0
  76. package/page/assets/HoverCard-D1Y1ltWQ.js +1 -0
  77. package/page/assets/Icon-JrysT8aU.js +3 -0
  78. package/page/assets/IconAlertTriangle-BqAEqqdU.js +1 -0
  79. package/page/assets/IconButton-DlOGzJIX.js +1 -0
  80. package/page/assets/IconEye-BB5aY-wQ.js +1 -0
  81. package/page/assets/IconFileText-6zOFKKrF.js +1 -0
  82. package/page/assets/IconSearch-BTx66a40.js +1 -0
  83. package/page/assets/IconShield-DDOh7Gr5.js +1 -0
  84. package/page/assets/ItemSwipeLayer-Ckn4X6h9.js +1 -0
  85. package/page/assets/PaneState-BvA2tLQg.js +1 -0
  86. package/page/assets/PaneState-eUzUkNE8.css +1 -0
  87. package/page/assets/ScrollableArea-Ckugdj1N.js +1 -0
  88. package/page/assets/Section-DJ97F7QE.js +1 -0
  89. package/page/assets/SegmentedControlItem-h-aaXzyS.js +1 -0
  90. package/page/assets/SidePanel-BL32abk8.js +1 -0
  91. package/page/assets/StackItem-CQviqAfM.js +1 -0
  92. package/page/assets/Text-bum6zWWx.js +2 -0
  93. package/page/assets/TimestampHoverCard-DQIG0RZF.js +1 -0
  94. package/page/assets/ToolResultCharts-CgshSv9Q.js +1 -0
  95. package/page/assets/Tooltip-BH9xiRMl.js +1 -0
  96. package/page/assets/TraceInspector-_yZiz05W.js +1 -0
  97. package/page/assets/TraceOverview-BPO-HIYK.js +57 -0
  98. package/page/assets/VStack-CkVMcNiK.js +1 -0
  99. package/page/assets/_virtual_studio-type-sources-ByxqQaOG.js +12936 -0
  100. package/page/assets/arrive-DvbqpeRS.js +1 -0
  101. package/page/assets/browser-engine-CYq9DQAM.js +9 -0
  102. package/page/assets/browser-transport-D-2jDYe7.js +47 -0
  103. package/page/assets/codicon-DF1abBS2.ttf +0 -0
  104. package/page/assets/compile-egress-BSxOdX4K.js +23 -0
  105. package/page/assets/container.stylex-BmOi-8DQ.js +1 -0
  106. package/page/assets/createReactComponent-CZwsv6DD.js +1 -0
  107. package/page/assets/decision-example-images-CZGr7YIT.js +1 -0
  108. package/page/assets/devWarning-DxuplmRl.js +1 -0
  109. package/page/assets/editor-DjpsE8to.css +1 -0
  110. package/page/assets/editor.api-Cd3ViPQa.js +908 -0
  111. package/page/assets/editor.worker-6GY8RhDc.js +1 -0
  112. package/page/assets/editor.worker-BmdWdrHZ.js +33 -0
  113. package/page/assets/editors-BJX_S59A.js +1 -0
  114. package/page/assets/egress-automata-B5ZGE1mT.js +1 -0
  115. package/page/assets/embedding-space-view-CfQBPDxD.js +140 -0
  116. package/page/assets/encode-files-Bc3HJyAh.js +1 -0
  117. package/page/assets/export-agent-C3yyzXH8.js +43 -0
  118. package/page/assets/figtree-latin-400-normal-BD4aNku5.woff +0 -0
  119. package/page/assets/figtree-latin-400-normal-g7Dtegnw.woff2 +0 -0
  120. package/page/assets/figtree-latin-500-normal-BWnGEVsr.woff2 +0 -0
  121. package/page/assets/figtree-latin-500-normal-B_yy1phn.woff +0 -0
  122. package/page/assets/figtree-latin-600-normal-BM_oTo4n.woff +0 -0
  123. package/page/assets/figtree-latin-600-normal-Cv_xCTDl.woff2 +0 -0
  124. package/page/assets/figtree-latin-700-normal-BCCt2T1q.woff +0 -0
  125. package/page/assets/figtree-latin-700-normal-th6qEP7c.woff2 +0 -0
  126. package/page/assets/focusOutline.stylex-BT0kfa4t.js +1 -0
  127. package/page/assets/focusableSelector-DXZeMU85.js +1 -0
  128. package/page/assets/groupStyles-C7Ter7zC.js +1 -0
  129. package/page/assets/guardrail-tester-Gut5xK4a.js +9 -0
  130. package/page/assets/host-patterns-C9RMP16Q.js +1 -0
  131. package/page/assets/index-CvvmFKgS.js +105 -0
  132. package/page/assets/index-DOaHlqBo.css +1 -0
  133. package/page/assets/isRtlElement-BG33-k_H.js +1 -0
  134. package/page/assets/labels-provider-4TIWDvsE.js +3 -0
  135. package/page/assets/lent-keys-C32ems-I.js +1 -0
  136. package/page/assets/live-ingress-lDUGPwth.js +1 -0
  137. package/page/assets/live-messages-CnkL4GWi.js +1 -0
  138. package/page/assets/mergeProps-ptS-gIMz.js +1 -0
  139. package/page/assets/padding.stylex-B76907Zf.js +1 -0
  140. package/page/assets/preload-helper-DpV6A1kQ.js +1 -0
  141. package/page/assets/reader-BOCCYaRp.js +7 -0
  142. package/page/assets/register-CDgivxvX.js +2 -0
  143. package/page/assets/rolldown-runtime-hePW80VL.js +1 -0
  144. package/page/assets/schemas-BQ9Jydzb.js +56 -0
  145. package/page/assets/shaped-data-BcarWqd5.js +16 -0
  146. package/page/assets/sharedResizeObserver-4U9Xq5Oq.js +1 -0
  147. package/page/assets/stub-DhdcU_Ew.js +471 -0
  148. package/page/assets/studio-code-pane-C_SAbcOa.js +8 -0
  149. package/page/assets/studio-console-DZwlEsvr.js +3 -0
  150. package/page/assets/studio-decision-B0FnCAXA.js +1 -0
  151. package/page/assets/studio-embedding-CmA51HVo.js +2 -0
  152. package/page/assets/studio-host-console-By7hqu2l.js +2 -0
  153. package/page/assets/studio-live-BrcVQcyV.js +1 -0
  154. package/page/assets/studio-map-B_xSia-z.js +1 -0
  155. package/page/assets/studio-map-Bb06Tw1F.css +1 -0
  156. package/page/assets/studio-monaco-CbKf90i6.css +1 -0
  157. package/page/assets/studio-monaco-xfyfezQS.js +34 -0
  158. package/page/assets/studio-ts.worker-B-EXG4Sv.js +67741 -0
  159. package/page/assets/studio-ts.worker-BXtW3z-O.js +1 -0
  160. package/page/assets/studio-type-checker-BOvxudki.js +23 -0
  161. package/page/assets/themeProps-3IqahpCr.js +1 -0
  162. package/page/assets/tokens.stylex-DkHf0P9C.js +1 -0
  163. package/page/assets/tool-schema-idaOe2nI.js +18 -0
  164. package/page/assets/trace-patterns-DnYJa0nI.js +1 -0
  165. package/page/assets/trace-record-C3zNksA_.js +1 -0
  166. package/page/assets/trace-schema-9CdEyG6Q.js +1 -0
  167. package/page/assets/tree-DqyAMsbI.js +3 -0
  168. package/page/assets/turn-client-Du3Um3Mg.js +2 -0
  169. package/page/assets/types-BhfXQCLx.js +1 -0
  170. package/page/assets/typescript-20GgZ8PJ.js +1 -0
  171. package/page/assets/use-one-call-Hcs8YBp1.js +1 -0
  172. package/page/assets/useAdaptivePresentation-BzmPCddb.js +1 -0
  173. package/page/assets/useClipboard-BgGRxRMJ.js +1 -0
  174. package/page/assets/useContainerReveal-BFy8N_QU.js +1 -0
  175. package/page/assets/useDevWarning-Z9SoixOP.js +1 -0
  176. package/page/assets/useLayerDismissal-B_G7IWpC.js +1 -0
  177. package/page/assets/useMediaQuery-BCT0NUeZ.js +1 -0
  178. package/page/assets/useScrollLock-Dyf6PHJt.js +1 -0
  179. package/page/assets/useScrollableArea-0NzdiBCq.js +1 -0
  180. package/page/assets/useTooltip-CJM_xhLB.js +1 -0
  181. package/page/assets/workspace-DZALqEDX.js +1 -0
  182. package/page/assets/zip-DTcEfps6.js +1 -0
  183. package/page/index.html +90 -0
  184. package/page.mjs +49 -0
  185. package/people.d.ts +87 -0
  186. package/people.js +110 -0
  187. package/policy.d.ts +43 -0
  188. package/policy.js +102 -0
  189. package/ports.d.mts +32 -0
  190. package/ports.mjs +85 -0
  191. package/probe-battery.d.ts +13 -0
  192. package/probe-battery.js +404 -0
  193. package/providers.d.ts +149 -0
  194. package/providers.js +306 -0
  195. package/read-source.d.ts +48 -0
  196. package/read-source.js +1301 -0
  197. package/read-steps.d.ts +23 -0
  198. package/read-steps.js +2 -0
  199. package/reader.d.ts +11 -0
  200. package/reader.js +11 -0
  201. package/registrations.d.ts +83 -0
  202. package/registrations.js +1 -0
  203. package/requirements.d.ts +22 -0
  204. package/requirements.js +38 -0
  205. package/run-payload-core.d.ts +28 -0
  206. package/run-payload-core.js +123 -0
  207. package/run-payload.d.ts +32 -0
  208. package/run-payload.js +26 -0
  209. package/runtime-scope.d.ts +84 -0
  210. package/runtime-scope.js +308 -0
  211. package/runtime.d.ts +84 -0
  212. package/runtime.js +387 -0
  213. package/section-notes.d.ts +33 -0
  214. package/section-notes.js +38 -0
  215. package/serve-page.mjs +96 -0
  216. package/server/draft-decide.d.ts +72 -0
  217. package/server/draft-decide.js +111 -0
  218. package/server/draft-embedding.d.ts +3 -0
  219. package/server/draft-embedding.js +35 -0
  220. package/server/draft-handler.d.ts +82 -0
  221. package/server/draft-handler.js +213 -0
  222. package/server/draft-probe.d.ts +14 -0
  223. package/server/draft-probe.js +61 -0
  224. package/server/draft-replies.d.ts +22 -0
  225. package/server/draft-replies.js +46 -0
  226. package/server/edited-session.d.ts +28 -0
  227. package/server/edited-session.js +83 -0
  228. package/server/edits.d.ts +38 -0
  229. package/server/edits.js +195 -0
  230. package/server/handler.d.ts +133 -0
  231. package/server/handler.js +598 -0
  232. package/server/lent-draft.d.ts +19 -0
  233. package/server/lent-draft.js +23 -0
  234. package/server/live.d.ts +15 -0
  235. package/server/live.js +96 -0
  236. package/server/open-editor.d.ts +63 -0
  237. package/server/open-editor.js +115 -0
  238. package/server/origins.d.ts +18 -0
  239. package/server/origins.js +330 -0
  240. package/server/project-source.d.ts +208 -0
  241. package/server/project-source.js +1042 -0
  242. package/server/project.d.ts +22 -0
  243. package/server/project.js +56 -0
  244. package/server/run-traces.d.ts +20 -0
  245. package/server/run-traces.js +96 -0
  246. package/server/save-new.d.ts +29 -0
  247. package/server/save-new.js +514 -0
  248. package/server/save-plan.d.ts +46 -0
  249. package/server/save-plan.js +1134 -0
  250. package/server/save-remove.d.ts +21 -0
  251. package/server/save-remove.js +528 -0
  252. package/server/save-session.d.ts +96 -0
  253. package/server/save-session.js +418 -0
  254. package/server/save-wire.d.ts +235 -0
  255. package/server/save-wire.js +8 -0
  256. package/server/save.d.ts +52 -0
  257. package/server/save.js +209 -0
  258. package/server/serve.d.ts +23 -0
  259. package/server/serve.js +413 -0
  260. package/server/test-connection.d.ts +17 -0
  261. package/server/test-connection.js +362 -0
  262. package/server/test-key.d.ts +2 -0
  263. package/server/test-key.js +83 -0
  264. package/server/watch.d.ts +28 -0
  265. package/server/watch.js +93 -0
  266. package/server/zod-edit.d.ts +74 -0
  267. package/server/zod-edit.js +558 -0
  268. package/server.d.ts +8 -0
  269. package/server.js +8 -0
  270. package/setups.mjs +170 -0
  271. package/shared-carry.d.ts +114 -0
  272. package/shared-carry.js +523 -0
  273. package/shared-settings.d.ts +34 -0
  274. package/shared-settings.js +70 -0
  275. package/source.d.ts +75 -0
  276. package/source.js +414 -0
  277. package/start.mjs +169 -0
  278. package/stream-lines.d.ts +9 -0
  279. package/stream-lines.js +27 -0
  280. package/stub.d.ts +6 -0
  281. package/stub.js +33 -0
  282. package/surface.d.ts +53 -0
  283. package/surface.js +631 -0
  284. package/system-markup.d.ts +20 -0
  285. package/system-markup.js +51 -0
  286. package/tool-schema.d.ts +23 -0
  287. package/tool-schema.js +69 -0
  288. package/tools.d.ts +16 -0
  289. package/tools.js +91 -0
  290. package/traces.d.ts +24 -0
  291. package/traces.js +29 -0
  292. package/transport.d.ts +57 -0
  293. package/transport.js +187 -0
  294. package/tree.d.ts +32 -0
  295. package/tree.js +83 -0
  296. package/types.d.ts +62 -0
  297. package/types.js +6 -0
  298. package/ui/acting-as-button.d.ts +2 -0
  299. package/ui/acting-as-button.js +16 -0
  300. package/ui/add-provider.d.ts +13 -0
  301. package/ui/add-provider.js +20 -0
  302. package/ui/boot-mark.css +53 -0
  303. package/ui/boot-mark.d.ts +19 -0
  304. package/ui/boot-mark.js +128 -0
  305. package/ui/boot-paint.d.ts +8 -0
  306. package/ui/boot-paint.js +8 -0
  307. package/ui/brand-icons.d.ts +57 -0
  308. package/ui/brand-icons.js +148 -0
  309. package/ui/built/theme.css +830 -0
  310. package/ui/built/theme.css.d.ts +9 -0
  311. package/ui/built/theorem-studio.d.ts +13 -0
  312. package/ui/built/theorem-studio.js +705 -0
  313. package/ui/built/theorem-studio.variants.d.ts +28 -0
  314. package/ui/code/monaco-contributions.d.ts +45 -0
  315. package/ui/code/studio-code.d.ts +7 -0
  316. package/ui/code/studio-code.js +253 -0
  317. package/ui/code/studio-monaco.d.ts +44 -0
  318. package/ui/code/studio-monaco.js +48 -0
  319. package/ui/code/studio-ts.worker.d.ts +1 -0
  320. package/ui/code/studio-ts.worker.js +16 -0
  321. package/ui/code/studio-type-checker.d.ts +12 -0
  322. package/ui/code/studio-type-checker.js +308 -0
  323. package/ui/code/studio-type-sources.d.ts +26 -0
  324. package/ui/confirm-write.d.ts +12 -0
  325. package/ui/confirm-write.js +45 -0
  326. package/ui/diff-block.d.ts +21 -0
  327. package/ui/diff-block.js +17 -0
  328. package/ui/figtree.css +59 -0
  329. package/ui/file-place.d.ts +4 -0
  330. package/ui/file-place.js +26 -0
  331. package/ui/guardrail-tester.d.ts +18 -0
  332. package/ui/guardrail-tester.js +248 -0
  333. package/ui/inspector-context.d.ts +119 -0
  334. package/ui/inspector-context.js +84 -0
  335. package/ui/inspector.d.ts +247 -0
  336. package/ui/inspector.js +284 -0
  337. package/ui/lib/acting-as.d.ts +25 -0
  338. package/ui/lib/acting-as.js +90 -0
  339. package/ui/lib/boot-sequence.d.ts +32 -0
  340. package/ui/lib/boot-sequence.js +57 -0
  341. package/ui/lib/export-agent.d.ts +11 -0
  342. package/ui/lib/export-agent.js +335 -0
  343. package/ui/lib/facet-icons.d.ts +20 -0
  344. package/ui/lib/facet-icons.js +21 -0
  345. package/ui/lib/panel-motion.d.ts +8 -0
  346. package/ui/lib/panel-motion.js +74 -0
  347. package/ui/lib/project-address.d.ts +14 -0
  348. package/ui/lib/project-address.js +24 -0
  349. package/ui/lib/project-starting.d.ts +13 -0
  350. package/ui/lib/project-starting.js +30 -0
  351. package/ui/lib/project-traces.d.ts +17 -0
  352. package/ui/lib/project-traces.js +54 -0
  353. package/ui/lib/read-map.d.ts +30 -0
  354. package/ui/lib/read-map.js +173 -0
  355. package/ui/lib/read-progress.d.ts +14 -0
  356. package/ui/lib/read-progress.js +12 -0
  357. package/ui/lib/refusal-lines.d.ts +3 -0
  358. package/ui/lib/refusal-lines.js +8 -0
  359. package/ui/lib/studio-activity.d.ts +5 -0
  360. package/ui/lib/studio-activity.js +17 -0
  361. package/ui/lib/studio-console.d.ts +27 -0
  362. package/ui/lib/studio-console.js +9 -0
  363. package/ui/lib/studio-conversation.d.ts +20 -0
  364. package/ui/lib/studio-conversation.js +130 -0
  365. package/ui/lib/studio-examples.d.ts +31 -0
  366. package/ui/lib/studio-examples.js +25 -0
  367. package/ui/lib/studio-labels.d.ts +6 -0
  368. package/ui/lib/studio-labels.js +13 -0
  369. package/ui/lib/studio-map-kept.d.ts +17 -0
  370. package/ui/lib/studio-map-kept.js +48 -0
  371. package/ui/lib/studio-open.d.ts +22 -0
  372. package/ui/lib/studio-open.js +54 -0
  373. package/ui/lib/studio-page.d.ts +33 -0
  374. package/ui/lib/studio-page.js +121 -0
  375. package/ui/lib/studio-prefs.d.ts +26 -0
  376. package/ui/lib/studio-prefs.js +87 -0
  377. package/ui/lib/studio-project.d.ts +106 -0
  378. package/ui/lib/studio-project.js +177 -0
  379. package/ui/lib/studio-restore.d.ts +49 -0
  380. package/ui/lib/studio-restore.js +251 -0
  381. package/ui/lib/studio-run-open.d.ts +15 -0
  382. package/ui/lib/studio-run-open.js +16 -0
  383. package/ui/lib/studio-session.d.ts +34 -0
  384. package/ui/lib/studio-session.js +17 -0
  385. package/ui/lib/studio-settings.d.ts +15 -0
  386. package/ui/lib/studio-settings.js +48 -0
  387. package/ui/lib/studio-store.d.ts +64 -0
  388. package/ui/lib/studio-store.js +391 -0
  389. package/ui/lib/studio-watch.d.ts +12 -0
  390. package/ui/lib/studio-watch.js +27 -0
  391. package/ui/lib/tab-fills.d.ts +6 -0
  392. package/ui/lib/tab-fills.js +14 -0
  393. package/ui/lib/tool-credentials.d.ts +3 -0
  394. package/ui/lib/tool-credentials.js +23 -0
  395. package/ui/lib/tool-probe.d.ts +19 -0
  396. package/ui/lib/tool-probe.js +104 -0
  397. package/ui/lib/type-icons.d.ts +35 -0
  398. package/ui/lib/type-icons.js +38 -0
  399. package/ui/lib/when-idle.d.ts +5 -0
  400. package/ui/lib/when-idle.js +14 -0
  401. package/ui/lib/zip.d.ts +5 -0
  402. package/ui/lib/zip.js +8 -0
  403. package/ui/map-token.css +11 -0
  404. package/ui/map-token.d.ts +17 -0
  405. package/ui/map-token.js +29 -0
  406. package/ui/mcp-icon.d.ts +6 -0
  407. package/ui/mcp-icon.js +8 -0
  408. package/ui/motion.css +113 -0
  409. package/ui/pan.d.ts +32 -0
  410. package/ui/pan.js +90 -0
  411. package/ui/profile-editor.d.ts +33 -0
  412. package/ui/profile-editor.js +2638 -0
  413. package/ui/provider-editor.d.ts +15 -0
  414. package/ui/provider-editor.js +92 -0
  415. package/ui/read-map.css +48 -0
  416. package/ui/read-map.d.ts +7 -0
  417. package/ui/read-map.js +163 -0
  418. package/ui/refusal-detail.d.ts +4 -0
  419. package/ui/refusal-detail.js +21 -0
  420. package/ui/shared-change.d.ts +9 -0
  421. package/ui/shared-change.js +28 -0
  422. package/ui/shell-motion.css +121 -0
  423. package/ui/shell-motion.d.ts +73 -0
  424. package/ui/shell-motion.js +194 -0
  425. package/ui/studio-code-pane.d.ts +19 -0
  426. package/ui/studio-code-pane.js +44 -0
  427. package/ui/studio-compare.d.ts +80 -0
  428. package/ui/studio-compare.js +326 -0
  429. package/ui/studio-conflicts.d.ts +17 -0
  430. package/ui/studio-conflicts.js +87 -0
  431. package/ui/studio-connection.d.ts +64 -0
  432. package/ui/studio-connection.js +286 -0
  433. package/ui/studio-decision.d.ts +6 -0
  434. package/ui/studio-decision.js +49 -0
  435. package/ui/studio-embedding.d.ts +6 -0
  436. package/ui/studio-embedding.js +30 -0
  437. package/ui/studio-host-console.d.ts +6 -0
  438. package/ui/studio-host-console.js +17 -0
  439. package/ui/studio-host.d.ts +73 -0
  440. package/ui/studio-host.js +12 -0
  441. package/ui/studio-live.d.ts +17 -0
  442. package/ui/studio-live.js +26 -0
  443. package/ui/studio-map.css +273 -0
  444. package/ui/studio-map.d.ts +17 -0
  445. package/ui/studio-map.js +397 -0
  446. package/ui/studio-nav.d.ts +24 -0
  447. package/ui/studio-nav.js +17 -0
  448. package/ui/studio-no-project.d.ts +5 -0
  449. package/ui/studio-no-project.js +9 -0
  450. package/ui/studio-run.css +76 -0
  451. package/ui/studio-run.d.ts +14 -0
  452. package/ui/studio-run.js +119 -0
  453. package/ui/studio-runner.d.ts +45 -0
  454. package/ui/studio-runner.js +187 -0
  455. package/ui/studio-save.d.ts +49 -0
  456. package/ui/studio-save.js +307 -0
  457. package/ui/studio-screen.d.ts +61 -0
  458. package/ui/studio-screen.js +2078 -0
  459. package/ui/studio-settings.d.ts +31 -0
  460. package/ui/studio-settings.js +288 -0
  461. package/ui/studio-shell.css +27 -0
  462. package/ui/studio-shell.d.ts +43 -0
  463. package/ui/studio-shell.js +87 -0
  464. package/ui/studio-theme.d.ts +7 -0
  465. package/ui/studio-theme.js +94 -0
  466. package/ui/studio.css +427 -0
  467. package/ui/theorem-mark.css +84 -0
  468. package/ui/theorem-mark.d.ts +22 -0
  469. package/ui/theorem-mark.js +21 -0
  470. package/vite.d.mts +30 -0
  471. package/vite.mjs +277 -0
  472. package/workspace.d.ts +230 -0
  473. package/workspace.js +1054 -0
package/workspace.js ADDED
@@ -0,0 +1,1054 @@
1
+ /**
2
+ * A workspace holds several agents and one library of tools they share. Each
3
+ * agent is a draft without the library: it names the tools it allows by key.
4
+ * Every draft helper still works on one agent through `agentDraft`, which puts
5
+ * its allowed tools back, and `withAgentDraft`, which takes them out again.
6
+ */
7
+ import { canonical } from "./canonical.js";
8
+ import { createBlankDraft, draftKey, freeName, } from "./draft.js";
9
+ import { providerIdOf, providerNodeId, studioNodeRef, studioTree, toolSpecKeyOf, toolSpecNodeId, toolSpecNodes, } from "./tree.js";
10
+ export const STUDIO_WORKSPACE_VERSION = 5;
11
+ const AGENT_PREFIX = 'agent:';
12
+ /** An agent's node id: its root, or a facet or binding under it (`identity` is the root). */
13
+ export function agentNodeId(agentKey, inner = 'identity') {
14
+ return inner === 'identity'
15
+ ? `${AGENT_PREFIX}${agentKey}`
16
+ : `${AGENT_PREFIX}${agentKey}/${inner}`;
17
+ }
18
+ /** The inverse of `agentNodeId`; `undefined` for an id that isn't an agent's. */
19
+ function parseAgentNodeId(id) {
20
+ if (!id.startsWith(AGENT_PREFIX))
21
+ return undefined;
22
+ const rest = id.slice(AGENT_PREFIX.length);
23
+ const slash = rest.indexOf('/');
24
+ if (slash < 0)
25
+ return { key: rest, inner: 'identity' };
26
+ return { key: rest.slice(0, slash), inner: rest.slice(slash + 1) };
27
+ }
28
+ /** A single draft's node id, under `agentKey`. Tools carry no agent: every agent shares them. */
29
+ export function scopedNodeId(agentKey, id) {
30
+ return toolSpecKeyOf(id) === undefined ? agentNodeId(agentKey, id) : id;
31
+ }
32
+ /** What a workspace node id names; `undefined` when it names nothing there. */
33
+ export function workspaceNodeRef(workspace, id) {
34
+ const tool = toolSpecKeyOf(id);
35
+ if (tool !== undefined) {
36
+ return workspace.toolSpecs.some((spec) => spec.key === tool) ? { tool } : undefined;
37
+ }
38
+ const provider = providerIdOf(id);
39
+ if (provider !== undefined) {
40
+ return workspace.providers.some((each) => each.id === provider) ? { provider } : undefined;
41
+ }
42
+ const parsed = parseAgentNodeId(id);
43
+ const agent = parsed && workspace.agents.find((candidate) => candidate.key === parsed.key);
44
+ if (!parsed || !agent || toolSpecKeyOf(parsed.inner) !== undefined)
45
+ return undefined;
46
+ const ref = studioNodeRef(draftOf(agent, [], workspace.providers), parsed.inner);
47
+ return ref ? { agent: agent.key, ref } : undefined;
48
+ }
49
+ /** `agent` as a single draft whose tools are the ones it allows from `library`, in library order. */
50
+ function draftOf(agent, library, providers) {
51
+ const { key: _key, tools: { allow, ...tools }, ...rest } = agent;
52
+ const held = new Map(library.map((tool) => [tool.key, tool]));
53
+ // In the order the agent allows them: that is the order its model is offered them in.
54
+ return { ...rest, providers, tools, toolSpecs: allow.flatMap((key) => held.get(key) ?? []) };
55
+ }
56
+ /** One agent as a single draft: its tools are the library's it allows, in the order it allows them. */
57
+ export function agentDraft(workspace, key) {
58
+ const agent = workspace.agents.find((candidate) => candidate.key === key);
59
+ return agent && draftOf(agent, workspace.toolSpecs, workspace.providers);
60
+ }
61
+ /** A single draft as an agent allowing every tool it holds. */
62
+ function agentFromDraft(draft, key = draftKey('agent')) {
63
+ const { toolSpecs, tools, providers: _providers, ...rest } = draft;
64
+ return { ...rest, key, tools: { ...tools, allow: toolSpecs.map((tool) => tool.key) } };
65
+ }
66
+ /** `toolSpecs` written into `library`: same keys replace, new keys append. Unchanged keeps the array. */
67
+ function mergeLibrary(library, toolSpecs) {
68
+ const byKey = new Map(toolSpecs.map((tool) => [tool.key, tool]));
69
+ const known = new Set(library.map((tool) => tool.key));
70
+ const added = toolSpecs.filter((tool) => !known.has(tool.key));
71
+ const changed = library.some((tool) => (byKey.get(tool.key) ?? tool) !== tool);
72
+ if (!changed && added.length === 0)
73
+ return library;
74
+ return [...library.map((tool) => byKey.get(tool.key) ?? tool), ...added];
75
+ }
76
+ function mapAgent(workspace, key, change) {
77
+ return workspace.agents.map((agent) => (agent.key === key ? change(agent) : agent));
78
+ }
79
+ /** What an agent or a tool is held by, and what a provider is. */
80
+ const byKey = (each) => each.key;
81
+ const byId = (provider) => provider.id;
82
+ /**
83
+ * Gives a start to each agent, tool, and provider that has none, and forgets the starts nothing can
84
+ * reach: a removed agent's, a tool's that is in no library and no agent's start, and a provider's
85
+ * that is not held and that no agent's start has a model on.
86
+ */
87
+ function remembered(workspace) {
88
+ const agents = Object.fromEntries(workspace.agents.map((agent) => [agent.key, workspace.starts.agents[agent.key] ?? agent]));
89
+ const held = new Map(workspace.toolSpecs.map((tool) => [tool.key, tool]));
90
+ const reachable = new Set([
91
+ ...held.keys(),
92
+ ...Object.values(agents).flatMap((agent) => agent.tools.allow),
93
+ ]);
94
+ const tools = {};
95
+ for (const key of reachable) {
96
+ const start = workspace.starts.tools[key] ?? held.get(key);
97
+ if (start)
98
+ tools[key] = start;
99
+ }
100
+ const providers = Object.fromEntries(workspace.providers.map((provider) => [
101
+ provider.id,
102
+ workspace.starts.providers[provider.id] ?? provider,
103
+ ]));
104
+ for (const id of Object.values(agents).flatMap(providersOf)) {
105
+ const start = workspace.starts.providers[id];
106
+ if (start && !(id in providers))
107
+ providers[id] = start;
108
+ }
109
+ return { ...workspace, starts: { agents, tools, providers } };
110
+ }
111
+ /** The providers an agent's models run on. */
112
+ const providersOf = (agent) => agent.modelBindings.map((binding) => binding.provider);
113
+ /**
114
+ * `providers` with each one `agents` started on that is no longer held, as it started. A model
115
+ * has no provider to fall to, so an agent put back to its start brings its providers with it.
116
+ */
117
+ function withStartProviders(workspace, agents) {
118
+ const held = new Set(workspace.providers.map(byId));
119
+ const back = [...new Set(agents.flatMap(providersOf))].flatMap((id) => {
120
+ const start = workspace.starts.providers[id];
121
+ return start && !held.has(id) ? [start] : [];
122
+ });
123
+ return back.length ? [...workspace.providers, ...back] : workspace.providers;
124
+ }
125
+ /** Makes what an agent holds now its start: for an example that links its agents after adding them. */
126
+ export function markAgentStart(workspace, key) {
127
+ const agent = workspace.agents.find((candidate) => candidate.key === key);
128
+ if (!agent)
129
+ return workspace;
130
+ return remembered({
131
+ ...workspace,
132
+ starts: { ...workspace.starts, agents: { ...workspace.starts.agents, [key]: agent } },
133
+ });
134
+ }
135
+ /**
136
+ * Writes one agent's draft back. Its tools go to the library: a changed tool
137
+ * changes there for every agent, a new one joins the library and this agent's
138
+ * allow list, and one the draft dropped leaves only this agent's allow list.
139
+ * `registered` updates the library for a tool the file defines and does not allow.
140
+ */
141
+ export function withAgentDraft(workspace, key, draft, registered) {
142
+ if (!workspace.agents.some((agent) => agent.key === key))
143
+ return workspace;
144
+ const defined = registered
145
+ ? mergeLibrary(workspace.toolSpecs, [...registered])
146
+ : workspace.toolSpecs;
147
+ return remembered({
148
+ ...workspace,
149
+ toolSpecs: mergeLibrary(defined, draft.toolSpecs),
150
+ agents: mapAgent(workspace, key, () => agentFromDraft(draft, key)),
151
+ });
152
+ }
153
+ /**
154
+ * A v1 single draft as a workspace: it becomes the one agent, its tools become
155
+ * the library and it allows them all, and the open node moves under it.
156
+ */
157
+ export function workspaceFromDraft(draft, selectedId = 'identity') {
158
+ const agent = agentFromDraft(draft);
159
+ return remembered({
160
+ v: STUDIO_WORKSPACE_VERSION,
161
+ agents: [agent],
162
+ toolSpecs: draft.toolSpecs,
163
+ providers: draft.providers,
164
+ selected: scopedNodeId(agent.key, selectedId),
165
+ chatWith: agent.key,
166
+ starts: { agents: {}, tools: {}, providers: {} },
167
+ });
168
+ }
169
+ /** A workspace with one blank agent. */
170
+ export function createBlankWorkspace() {
171
+ return workspaceFromDraft(createBlankDraft());
172
+ }
173
+ /**
174
+ * One agent as the editor shows it: its own settings, with the whole library
175
+ * as its tools. Which of them it allows stays on the agent (`setToolAllowed`).
176
+ */
177
+ export function libraryDraft(workspace, key) {
178
+ const agent = workspace.agents.find((candidate) => candidate.key === key);
179
+ return agent && { ...draftOf(agent, [], workspace.providers), toolSpecs: workspace.toolSpecs };
180
+ }
181
+ /**
182
+ * Writes back a `libraryDraft`. Its tools become the library: a tool it
183
+ * dropped leaves every agent, and a tool it added is allowed on this agent.
184
+ */
185
+ export function withLibraryDraft(workspace, key, draft) {
186
+ const agent = workspace.agents.find((candidate) => candidate.key === key);
187
+ if (!agent)
188
+ return workspace;
189
+ const kept = new Set(draft.toolSpecs.map((tool) => tool.key));
190
+ const known = new Set(workspace.toolSpecs.map((tool) => tool.key));
191
+ const added = draft.toolSpecs.filter((tool) => !known.has(tool.key)).map((tool) => tool.key);
192
+ const keep = (allow) => allow.filter((toolKey) => kept.has(toolKey));
193
+ const { toolSpecs: _library, providers: _providers, tools, ...rest } = draft;
194
+ return remembered({
195
+ ...workspace,
196
+ toolSpecs: draft.toolSpecs,
197
+ agents: workspace.agents.map((each) => each.key === key
198
+ ? { ...rest, key, tools: { ...tools, allow: [...keep(each.tools.allow), ...added] } }
199
+ : each.tools.allow.every((toolKey) => kept.has(toolKey))
200
+ ? each
201
+ : { ...each, tools: { ...each.tools, allow: keep(each.tools.allow) } }),
202
+ });
203
+ }
204
+ /**
205
+ * Appends `draft` as a new agent, with an id no other agent has. A tool whose
206
+ * name the library already has is shared, not copied; the rest join the library.
207
+ */
208
+ export function addAgent(workspace, draft = createBlankDraft()) {
209
+ const shared = new Map(workspace.toolSpecs.map((tool) => [tool.toolName, tool.key]));
210
+ const base = draft.identity.agentId.trim();
211
+ const taken = workspace.agents.map((agent) => agent.identity.agentId);
212
+ const agentId = base && freeName(base, taken, (n) => `${base}_${n}`);
213
+ const agent = agentFromDraft({ ...draft, identity: { ...draft.identity, agentId } });
214
+ agent.tools.allow = draft.toolSpecs.map((tool) => shared.get(tool.toolName) ?? tool.key);
215
+ return remembered({
216
+ ...workspace,
217
+ agents: [...workspace.agents, agent],
218
+ toolSpecs: [
219
+ ...workspace.toolSpecs,
220
+ ...draft.toolSpecs.filter((tool) => !shared.has(tool.toolName)),
221
+ ],
222
+ selected: agentNodeId(agent.key),
223
+ });
224
+ }
225
+ /** A copy of an agent right after it, allowing the same tools, with an id it doesn't share. */
226
+ export function duplicateAgent(workspace, key) {
227
+ const index = workspace.agents.findIndex((agent) => agent.key === key);
228
+ const source = workspace.agents[index];
229
+ if (!source)
230
+ return workspace;
231
+ const base = source.identity.agentId.trim() || 'agent';
232
+ const taken = workspace.agents.map((agent) => agent.identity.agentId);
233
+ const copy = { ...structuredClone(source), key: draftKey('agent') };
234
+ copy.identity.agentId = freeName(`${base}_copy`, taken, (n) => `${base}_copy${n}`);
235
+ const agents = [...workspace.agents];
236
+ agents.splice(index + 1, 0, copy);
237
+ return remembered({ ...workspace, agents, selected: agentNodeId(copy.key) });
238
+ }
239
+ /**
240
+ * Removes an agent; the last one stays. Nothing is left pointing at it: the
241
+ * agent tools that ran it leave the library and every agent, and a model it
242
+ * summarised for goes back to summarising itself. The open node and the chat
243
+ * move to a neighbour when they were on it.
244
+ */
245
+ export function removeAgent(workspace, key) {
246
+ const index = workspace.agents.findIndex((agent) => agent.key === key);
247
+ if (index < 0 || workspace.agents.length === 1)
248
+ return workspace;
249
+ const neighbour = workspace.agents[index + 1] ?? workspace.agents[index - 1];
250
+ if (!neighbour)
251
+ return workspace;
252
+ const ranIt = new Set(workspace.toolSpecs
253
+ .filter((tool) => tool.toolType === 'agent' && tool.agentKey === key)
254
+ .map((tool) => tool.key));
255
+ const agents = workspace.agents
256
+ .filter((agent) => agent.key !== key)
257
+ .map((agent) => ({
258
+ ...agent,
259
+ tools: { ...agent.tools, allow: agent.tools.allow.filter((toolKey) => !ranIt.has(toolKey)) },
260
+ modelBindings: agent.modelBindings.map((binding) => binding.compactWith === key ? { ...binding, compactWith: undefined } : binding),
261
+ }));
262
+ const gone = parseAgentNodeId(workspace.selected)?.key === key ||
263
+ [...ranIt].some((toolKey) => workspace.selected === toolSpecNodeId(toolKey));
264
+ return remembered({
265
+ ...workspace,
266
+ agents,
267
+ toolSpecs: workspace.toolSpecs.filter((tool) => !ranIt.has(tool.key)),
268
+ selected: gone ? agentNodeId(neighbour.key) : workspace.selected,
269
+ chatWith: workspace.chatWith === key ? neighbour.key : workspace.chatWith,
270
+ });
271
+ }
272
+ /** Allows or stops allowing a library tool on one agent. */
273
+ export function setToolAllowed(workspace, agentKey, toolKey, allowed) {
274
+ const agent = workspace.agents.find((candidate) => candidate.key === agentKey);
275
+ if (!agent || !workspace.toolSpecs.some((tool) => tool.key === toolKey))
276
+ return workspace;
277
+ if (agent.tools.allow.includes(toolKey) === allowed)
278
+ return workspace;
279
+ const allow = allowed
280
+ ? [...agent.tools.allow, toolKey]
281
+ : agent.tools.allow.filter((key) => key !== toolKey);
282
+ return {
283
+ ...workspace,
284
+ agents: mapAgent(workspace, agentKey, () => ({ ...agent, tools: { ...agent.tools, allow } })),
285
+ };
286
+ }
287
+ /** Removes a tool from the library and from every agent that allowed it. */
288
+ export function removeLibraryTool(workspace, toolKey) {
289
+ if (!workspace.toolSpecs.some((tool) => tool.key === toolKey))
290
+ return workspace;
291
+ const agents = workspace.agents.map((agent) => agent.tools.allow.includes(toolKey)
292
+ ? {
293
+ ...agent,
294
+ tools: { ...agent.tools, allow: agent.tools.allow.filter((key) => key !== toolKey) },
295
+ }
296
+ : agent);
297
+ return remembered({
298
+ ...workspace,
299
+ agents,
300
+ toolSpecs: workspace.toolSpecs.filter((tool) => tool.key !== toolKey),
301
+ selected: workspace.selected === toolSpecNodeId(toolKey)
302
+ ? agentNodeId(workspace.agents[0]?.key ?? '')
303
+ : workspace.selected,
304
+ });
305
+ }
306
+ /** The same settings with the same values. The order they are held in is not a difference. */
307
+ const same = (a, b) => canonical(a) === canonical(b);
308
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
309
+ /** Whether every agent, tool, and provider is as it started, and no tool was added or removed. */
310
+ export function atStart(workspace) {
311
+ const { agents, tools, providers } = workspace.starts;
312
+ return (workspace.agents.every((agent) => same(agent, agents[agent.key])) &&
313
+ workspace.toolSpecs.every((tool) => same(tool, tools[tool.key])) &&
314
+ workspace.providers.every((provider) => same(provider, providers[provider.id])) &&
315
+ Object.keys(tools).length === workspace.toolSpecs.length);
316
+ }
317
+ /** Makes what the workspace holds now its start: after a Save, the project's files hold it. */
318
+ export function startedHere(workspace) {
319
+ return {
320
+ ...workspace,
321
+ starts: {
322
+ agents: Object.fromEntries(workspace.agents.map((agent) => [agent.key, agent])),
323
+ tools: Object.fromEntries(workspace.toolSpecs.map((tool) => [tool.key, tool])),
324
+ providers: Object.fromEntries(workspace.providers.map((provider) => [provider.id, provider])),
325
+ },
326
+ };
327
+ }
328
+ /** `start` under `agentId`, allowing the tools that are held and compacting with an agent that is here. */
329
+ function fitted(start, agentId, held, here) {
330
+ return {
331
+ ...start,
332
+ identity: { ...start.identity, agentId },
333
+ tools: { ...start.tools, allow: start.tools.allow.filter((toolKey) => held.has(toolKey)) },
334
+ modelBindings: start.modelBindings.map((binding) => binding.compactWith && !here.has(binding.compactWith)
335
+ ? { ...binding, compactWith: undefined }
336
+ : binding),
337
+ };
338
+ }
339
+ /**
340
+ * Puts one agent back to its start: its own settings and the tools it allowed. The tools keep
341
+ * their edits, since other agents share them; one that left the library comes back as it started.
342
+ * Nothing else changes. An agent already at its start returns the same workspace.
343
+ */
344
+ export function resetAgent(workspace, key) {
345
+ const start = workspace.starts.agents[key];
346
+ const now = workspace.agents.find((agent) => agent.key === key);
347
+ if (!start || !now)
348
+ return workspace;
349
+ const here = new Set(workspace.agents.map((agent) => agent.key));
350
+ const library = new Set(workspace.toolSpecs.map((tool) => tool.key));
351
+ const names = workspace.toolSpecs.map((tool) => tool.toolName);
352
+ const back = [];
353
+ for (const toolKey of start.tools.allow) {
354
+ const tool = workspace.starts.tools[toolKey];
355
+ if (library.has(toolKey) || !tool)
356
+ continue;
357
+ // An agent tool whose agent was removed has nothing to run.
358
+ if (tool.toolType === 'agent' && !here.has(tool.agentKey ?? ''))
359
+ continue;
360
+ const toolName = freeName(tool.toolName, names, (n) => `${tool.toolName}_${n}`);
361
+ names.push(toolName);
362
+ back.push({ ...tool, toolName });
363
+ }
364
+ const toolSpecs = back.length ? [...workspace.toolSpecs, ...back] : workspace.toolSpecs;
365
+ const held = new Set(toolSpecs.map((tool) => tool.key));
366
+ const id = start.identity.agentId;
367
+ const others = workspace.agents
368
+ .filter((agent) => agent.key !== key)
369
+ .map((agent) => agent.identity.agentId);
370
+ const agent = fitted(start, id && freeName(id, others, (n) => `${id}_${n}`), held, here);
371
+ if (back.length === 0 && same(agent, now))
372
+ return workspace;
373
+ const providers = withStartProviders(workspace, [start]);
374
+ const next = {
375
+ ...workspace,
376
+ agents: mapAgent(workspace, key, () => agent),
377
+ toolSpecs,
378
+ providers,
379
+ };
380
+ return workspaceNodeRef(next, next.selected) ? next : { ...next, selected: agentNodeId(key) };
381
+ }
382
+ /**
383
+ * Puts every agent, every library tool, and every provider back to its start, in one step. A tool that left the
384
+ * library comes back when an agent started out allowing it. An agent or a tool added here stays: it
385
+ * started as it is. A workspace already at its start is returned as it is.
386
+ */
387
+ export function resetAll(workspace) {
388
+ const { starts } = workspace;
389
+ const here = new Set(workspace.agents.map((agent) => agent.key));
390
+ const library = new Map(workspace.toolSpecs.map((tool) => [tool.key, starts.tools[tool.key] ?? tool]));
391
+ for (const agent of workspace.agents) {
392
+ for (const toolKey of starts.agents[agent.key]?.tools.allow ?? []) {
393
+ const tool = starts.tools[toolKey];
394
+ if (!tool || library.has(toolKey))
395
+ continue;
396
+ // An agent tool whose agent was removed has nothing to run.
397
+ if (tool.toolType === 'agent' && !here.has(tool.agentKey ?? ''))
398
+ continue;
399
+ library.set(toolKey, tool);
400
+ }
401
+ }
402
+ // Two that started under one name cannot both hold it: the later one takes the next free name.
403
+ const names = [];
404
+ const toolSpecs = [...library.values()].map((tool) => {
405
+ const toolName = freeName(tool.toolName, names, (n) => `${tool.toolName}_${n}`);
406
+ names.push(toolName);
407
+ return toolName === tool.toolName ? tool : { ...tool, toolName };
408
+ });
409
+ const ids = [];
410
+ const agents = workspace.agents.map((now) => {
411
+ const start = starts.agents[now.key] ?? now;
412
+ const id = start.identity.agentId;
413
+ const agentId = id && freeName(id, ids, (n) => `${id}_${n}`);
414
+ ids.push(agentId);
415
+ return fitted(start, agentId, library, here);
416
+ });
417
+ const providers = withStartProviders(workspace, agents).map((provider) => starts.providers[provider.id] ?? provider);
418
+ if (same(agents, workspace.agents) &&
419
+ same(toolSpecs, workspace.toolSpecs) &&
420
+ same(providers, workspace.providers)) {
421
+ return workspace;
422
+ }
423
+ const next = { ...workspace, agents, toolSpecs, providers };
424
+ const first = agents[0];
425
+ return workspaceNodeRef(next, next.selected) || !first
426
+ ? next
427
+ : { ...next, selected: agentNodeId(first.key) };
428
+ }
429
+ /**
430
+ * The files' workspace, opened again over the one the studio holds. An agent or a tool the studio
431
+ * already opened from the files keeps its key, so what is open, each conversation, and the
432
+ * settings agents share carry on. One the builder removed comes back under a new key.
433
+ */
434
+ export function reopened(files, before) {
435
+ const agents = new Map(Object.values(before.starts.agents).map((agent) => [agent.identity.agentId, agent.key]));
436
+ const tools = new Map(Object.values(before.starts.tools).map((tool) => [tool.toolName, tool.key]));
437
+ let text = JSON.stringify(files);
438
+ const rekey = (from, to) => {
439
+ if (to !== undefined && to !== from)
440
+ text = text.split(from).join(to);
441
+ };
442
+ for (const agent of files.agents)
443
+ rekey(agent.key, agents.get(agent.identity.agentId));
444
+ for (const tool of files.toolSpecs)
445
+ rekey(tool.key, tools.get(tool.toolName));
446
+ const next = JSON.parse(text);
447
+ return {
448
+ ...next,
449
+ selected: workspaceNodeRef(next, before.selected) ? before.selected : next.selected,
450
+ chatWith: next.agents.some((agent) => agent.key === before.chatWith)
451
+ ? before.chatWith
452
+ : next.chatWith,
453
+ };
454
+ }
455
+ /**
456
+ * The builder's edits brought back over files that changed since they were made. What the builder
457
+ * changed or added stays as they left it, and what they did not touch is as the files hold it
458
+ * now. Every start is the files', so an edit made on the old files is one the new files do not
459
+ * hold. A tool the builder removed stays removed; an agent they removed comes back.
460
+ */
461
+ export function rebased(edits, files) {
462
+ const now = reopened(files, edits);
463
+ const laid = (keyOf, held, starts, inFiles, fileStarts) => {
464
+ const file = new Map(inFiles.map((each) => [keyOf(each), each]));
465
+ // What stands in the studio, or stood there and was removed, is not the files' to add.
466
+ const known = new Set([...held.map(keyOf), ...Object.keys(starts)]);
467
+ const kept = held.map((each) => same(each, starts[keyOf(each)]) ? (file.get(keyOf(each)) ?? each) : each);
468
+ // One the files do not hold was added here, and starts where it did.
469
+ const added = kept
470
+ .filter((each) => !(keyOf(each) in fileStarts))
471
+ .map((each) => [keyOf(each), starts[keyOf(each)] ?? each]);
472
+ return {
473
+ held: [...kept, ...inFiles.filter((each) => !known.has(keyOf(each)))],
474
+ starts: { ...Object.fromEntries(added), ...fileStarts },
475
+ };
476
+ };
477
+ const agents = laid(byKey, edits.agents, edits.starts.agents, now.agents, now.starts.agents);
478
+ const tools = laid(byKey, edits.toolSpecs, edits.starts.tools, now.toolSpecs, now.starts.tools);
479
+ const providers = laid(byId, edits.providers, edits.starts.providers, now.providers, now.starts.providers);
480
+ return {
481
+ ...edits,
482
+ agents: agents.held,
483
+ toolSpecs: tools.held,
484
+ providers: providers.held,
485
+ starts: { agents: agents.starts, tools: tools.starts, providers: providers.starts },
486
+ };
487
+ }
488
+ /**
489
+ * A project's files as the studio opened them, as a short text that stays the same until they
490
+ * change. An agent's or a tool's key is new on every open, so each is named by where it first
491
+ * stands. What is open and who the preview talks to are not part of it.
492
+ */
493
+ export function filesPrint(workspace) {
494
+ const names = new Map();
495
+ const collect = (value) => {
496
+ if (Array.isArray(value)) {
497
+ for (const each of value)
498
+ collect(each);
499
+ return;
500
+ }
501
+ if (typeof value !== 'object' || value === null)
502
+ return;
503
+ for (const [field, inner] of Object.entries(value)) {
504
+ if (field !== 'key' || typeof inner !== 'string')
505
+ collect(inner);
506
+ else if (!names.has(inner))
507
+ names.set(inner, `#${names.size}`);
508
+ }
509
+ };
510
+ const named = (value) => {
511
+ if (typeof value === 'string')
512
+ return names.get(value) ?? value;
513
+ if (Array.isArray(value))
514
+ return value.map(named);
515
+ if (typeof value !== 'object' || value === null)
516
+ return value;
517
+ return Object.fromEntries(Object.entries(value).map(([field, inner]) => [names.get(field) ?? field, named(inner)]));
518
+ };
519
+ const held = {
520
+ agents: workspace.agents,
521
+ toolSpecs: workspace.toolSpecs,
522
+ providers: workspace.providers,
523
+ };
524
+ collect(held);
525
+ const text = JSON.stringify(named(held));
526
+ // Two 32-bit FNV-1a passes: enough to tell one reading of the files from another.
527
+ let low = 0x811c9dc5;
528
+ let high = 0x01000193;
529
+ for (let index = 0; index < text.length; index += 1) {
530
+ const code = text.charCodeAt(index);
531
+ low = Math.imul(low ^ code, 0x01000193);
532
+ high = Math.imul(high ^ code, 0x85ebca6b);
533
+ }
534
+ return `${text.length.toString(36)}-${(low >>> 0).toString(36)}${(high >>> 0).toString(36)}`;
535
+ }
536
+ /**
537
+ * Whether the workspace holds edits the files it stands on do not: a change, or an agent or a
538
+ * tool added or removed. `files` is the print of those files.
539
+ */
540
+ export function editedSince(workspace, files) {
541
+ return filesPrint(workspace) !== files;
542
+ }
543
+ const DRAFT_KEY = /^(agent|model|tool|question|criterion|reference)-[0-9a-f]{8}$/;
544
+ /** A value as text with each key named by where it first stands, so two opens of one thing read the same. */
545
+ function keyless(value) {
546
+ const names = new Map();
547
+ return JSON.stringify(value).replace(/\b(agent|model|tool|question|criterion|reference)-[0-9a-f]{8}\b/g, (key) => {
548
+ if (!names.has(key))
549
+ names.set(key, `#${names.size}`);
550
+ return names.get(key) ?? key;
551
+ });
552
+ }
553
+ /**
554
+ * Each thing in a start's list with the one in the files' list it is: the one that reads the
555
+ * same, and otherwise the next one left. So a row the files add or take out moves no other row.
556
+ */
557
+ function paired(start, file) {
558
+ const texts = file.map(keyless);
559
+ const taken = new Set();
560
+ const pairs = [];
561
+ const unmatched = [];
562
+ for (const each of start) {
563
+ const text = keyless(each);
564
+ const at = texts.findIndex((other, index) => other === text && !taken.has(index));
565
+ if (at < 0)
566
+ unmatched.push(each);
567
+ else {
568
+ taken.add(at);
569
+ pairs.push([each, file[at]]);
570
+ }
571
+ }
572
+ const left = file.filter((_, index) => !taken.has(index));
573
+ for (const [index, each] of unmatched.slice(0, left.length).entries())
574
+ pairs.push([each, left[index]]);
575
+ return pairs;
576
+ }
577
+ /**
578
+ * The files with an agent or a tool they renamed under the key the studio holds it by. It is a
579
+ * rename when one the studio opened is gone, one it never held is there, and the two read the
580
+ * same but for the name.
581
+ */
582
+ function renamedAsHeld(now, edits) {
583
+ let text = JSON.stringify(now);
584
+ const pair = (held, starts, inFiles, unnamed) => {
585
+ const file = new Set(inFiles.map((each) => each.key));
586
+ const here = new Set(held.map((each) => each.key));
587
+ const gone = held.flatMap((each) => (file.has(each.key) ? [] : (starts[each.key] ?? [])));
588
+ const fresh = inFiles.filter((each) => !here.has(each.key) && !starts[each.key]);
589
+ const [was, is] = [gone[0], fresh[0]];
590
+ if (gone.length !== 1 || fresh.length !== 1 || !was || !is)
591
+ return;
592
+ if (keyless(unnamed(was)) === keyless(unnamed(is)))
593
+ text = text.split(is.key).join(was.key);
594
+ };
595
+ pair(edits.agents, edits.starts.agents, now.agents, (agent) => ({
596
+ ...agent,
597
+ identity: { ...agent.identity, agentId: '' },
598
+ }));
599
+ pair(edits.toolSpecs, edits.starts.tools, now.toolSpecs, (tool) => ({ ...tool, toolName: '' }));
600
+ return JSON.parse(text);
601
+ }
602
+ /**
603
+ * `now` with the keys inside each agent and tool named as its start names them. A key is new on
604
+ * every open, so what stands in the same place in the start gives its key: a model, a question.
605
+ */
606
+ function alignedToStarts(now, starts) {
607
+ let text = JSON.stringify(now);
608
+ // A key can name a field, whose inner keys line up only once it does: so more than one pass.
609
+ for (let pass = 0; pass < 4; pass += 1) {
610
+ const held = JSON.parse(text);
611
+ const renames = new Map();
612
+ const walk = (start, file) => {
613
+ if (Array.isArray(start) && Array.isArray(file)) {
614
+ for (const [each, other] of paired(start, file))
615
+ walk(each, other);
616
+ }
617
+ else if (isRecord(start) && isRecord(file)) {
618
+ for (const [field, inner] of Object.entries(start)) {
619
+ const other = file[field];
620
+ if (field !== 'key')
621
+ walk(inner, other);
622
+ else if (typeof inner === 'string' &&
623
+ typeof other === 'string' &&
624
+ inner !== other &&
625
+ DRAFT_KEY.test(inner) &&
626
+ DRAFT_KEY.test(other))
627
+ renames.set(other, inner);
628
+ }
629
+ }
630
+ };
631
+ for (const agent of held.agents)
632
+ walk(starts.agents[agent.key], agent);
633
+ for (const tool of held.toolSpecs)
634
+ walk(starts.tools[tool.key], tool);
635
+ if (renames.size === 0)
636
+ return held;
637
+ for (const [from, to] of renames)
638
+ text = text.split(from).join(to);
639
+ }
640
+ return JSON.parse(text);
641
+ }
642
+ const isNames = (value) => Array.isArray(value) &&
643
+ value.every((each) => typeof each === 'string') &&
644
+ new Set(value).size === value.length;
645
+ /** Whether a list holds keyed rows: models, questions. */
646
+ const isRows = (value) => Array.isArray(value) && value.every((row) => isRecord(row) && typeof row.key === 'string');
647
+ /**
648
+ * One value three ways: what the files held, what the builder holds, and what the files hold now.
649
+ * A record merges field by field, a list of keyed rows row by row, and a list of names as a set.
650
+ * A row either side added stays, and one either side took out goes unless the other changed it.
651
+ * Anything else is one setting: whoever changed it has it, and both changing it is a conflict the
652
+ * builder's value stands in for.
653
+ */
654
+ function mergedValue(base, mine, theirs, path, tell) {
655
+ if (same(base, theirs) || same(mine, theirs))
656
+ return mine;
657
+ if (isRecord(base) && isRecord(mine) && isRecord(theirs)) {
658
+ // In the files' order, so a setting the files added sits where they hold it and what comes of
659
+ // the merge is the same as its start when the builder changed nothing.
660
+ const next = {};
661
+ for (const field of new Set([
662
+ ...Object.keys(theirs),
663
+ ...Object.keys(mine),
664
+ ...Object.keys(base),
665
+ ])) {
666
+ const value = mergedValue(base[field], mine[field], theirs[field], [...path, field], tell);
667
+ if (value !== undefined)
668
+ next[field] = value;
669
+ }
670
+ return next;
671
+ }
672
+ if (isRows(base) && isRows(mine) && isRows(theirs)) {
673
+ const was = new Map(base.map((row) => [row.key, row]));
674
+ const now = new Map(theirs.map((row) => [row.key, row]));
675
+ const kept = [];
676
+ for (const row of mine) {
677
+ const [start, file] = [was.get(row.key), now.get(row.key)];
678
+ const at = [...path, kept.length];
679
+ if (!start)
680
+ kept.push(row);
681
+ else if (file)
682
+ kept.push(mergedValue(start, row, file, at, tell));
683
+ else if (same(row, start))
684
+ tell.updated(at);
685
+ else {
686
+ tell.conflict(at, row, undefined);
687
+ kept.push(row);
688
+ }
689
+ }
690
+ const here = new Set(mine.map((row) => row.key));
691
+ // A row the builder took out and the files changed: it would come back after the rows kept.
692
+ let back = kept.length;
693
+ for (const row of theirs) {
694
+ if (here.has(row.key))
695
+ continue;
696
+ const start = was.get(row.key);
697
+ if (!start) {
698
+ tell.updated([...path, kept.length]);
699
+ kept.push(row);
700
+ back += 1;
701
+ }
702
+ else if (!same(row, start))
703
+ tell.conflict([...path, back++], undefined, row);
704
+ }
705
+ return kept;
706
+ }
707
+ if (same(base, mine)) {
708
+ tell.updated(path);
709
+ return theirs;
710
+ }
711
+ if (isNames(base) && isNames(mine) && isNames(theirs)) {
712
+ const taken = base.filter((name) => !theirs.includes(name));
713
+ const added = theirs.filter((name) => !base.includes(name) && !mine.includes(name));
714
+ tell.updated(path);
715
+ return [...mine.filter((name) => !taken.includes(name)), ...added];
716
+ }
717
+ tell.conflict(path, mine, theirs);
718
+ return mine;
719
+ }
720
+ /** Leaves nothing pointing at an agent or a tool that is not there, and keeps something open. */
721
+ function sound(workspace) {
722
+ const agentKeys = new Set(workspace.agents.map((agent) => agent.key));
723
+ // An agent tool whose agent is gone has nothing to run.
724
+ const toolSpecs = workspace.toolSpecs.filter((tool) => tool.toolType !== 'agent' || agentKeys.has(tool.agentKey ?? ''));
725
+ const library = new Set(toolSpecs.map((tool) => tool.key));
726
+ const agents = workspace.agents.map((agent) => {
727
+ const allow = agent.tools.allow.filter((toolKey) => library.has(toolKey));
728
+ const modelBindings = agent.modelBindings.map((binding) => binding.compactWith && !agentKeys.has(binding.compactWith)
729
+ ? { ...binding, compactWith: undefined }
730
+ : binding);
731
+ const next = { ...agent, tools: { ...agent.tools, allow }, modelBindings };
732
+ return same(next, agent) ? agent : next;
733
+ });
734
+ const next = remembered({ ...workspace, agents, toolSpecs });
735
+ const first = agents[0];
736
+ return {
737
+ ...next,
738
+ selected: workspaceNodeRef(next, next.selected) || !first ? next.selected : agentNodeId(first.key),
739
+ chatWith: agentKeys.has(next.chatWith) || !first ? next.chatWith : first.key,
740
+ };
741
+ }
742
+ /**
743
+ * The builder's edits merged with files that changed under them, setting by setting. What only
744
+ * the files changed is as the files hold it; what only the builder changed stays; what both
745
+ * changed to different values is a conflict, and the builder's value stands until they choose
746
+ * (`withFilesChosen`). Every start is the files', so what is left to save is the builder's edits.
747
+ *
748
+ * `before` is the files as the studio last read them. It tells an agent the files took away from
749
+ * one the builder added, and names the agents the builder removed, which keep no start. Without
750
+ * it nothing the studio holds is taken away, and an agent the builder removed comes back.
751
+ */
752
+ export function mergedWithFiles(edits, files, before) {
753
+ const now = alignedToStarts(renamedAsHeld(reopened(files, edits), edits), edits.starts);
754
+ const updated = [];
755
+ const conflicts = [];
756
+ const laid = (kind, keyOf, nameOf, held, starts, inFiles, fileStarts, read) => {
757
+ const file = new Map(inFiles.map((each) => [keyOf(each), each]));
758
+ const here = new Set(held.map((each) => keyOf(each)));
759
+ const names = new Set(held.map((each) => nameOf(starts[keyOf(each)] ?? each)));
760
+ const wasRead = new Set(read?.map(nameOf));
761
+ const kept = [];
762
+ const nextStarts = { ...fileStarts };
763
+ for (const mine of held) {
764
+ const [base, theirs] = [starts[keyOf(mine)], file.get(keyOf(mine))];
765
+ const of = { kind, key: keyOf(mine), name: nameOf(theirs ?? base ?? mine) };
766
+ if (!base || (!theirs && !wasRead.has(nameOf(base)))) {
767
+ // Added here, since the files never held it: it starts as it did.
768
+ kept.push(mine);
769
+ nextStarts[keyOf(mine)] = base ?? mine;
770
+ }
771
+ else if (!theirs) {
772
+ // The files took it away: it goes unless the builder changed it.
773
+ if (same(mine, base))
774
+ updated.push({ ...of, path: [] });
775
+ else {
776
+ conflicts.push({ ...of, path: [], mine, theirs: undefined });
777
+ kept.push(mine);
778
+ nextStarts[keyOf(mine)] = base;
779
+ }
780
+ }
781
+ else {
782
+ kept.push(mergedValue(base, mine, theirs, [], {
783
+ updated: (path) => updated.push({ ...of, path }),
784
+ conflict: (path, yours, files) => conflicts.push({ ...of, path, mine: yours, theirs: files }),
785
+ }));
786
+ }
787
+ }
788
+ // Whether the builder removed what the files hold, and whether the files changed it since.
789
+ const removedHere = (theirs) => {
790
+ // A start outlives its removal for a tool an agent started with; the last reading names the rest.
791
+ const was = starts[keyOf(theirs)] ??
792
+ read?.find((each) => nameOf(each) === nameOf(theirs) && !names.has(nameOf(each)));
793
+ if (!was)
794
+ return undefined;
795
+ // What it points at may have a new key: an agent tool's agent, removed with it.
796
+ return keyless(was) === keyless(theirs) ? 'unchanged' : 'changed';
797
+ };
798
+ for (const theirs of inFiles) {
799
+ if (here.has(keyOf(theirs)))
800
+ continue;
801
+ const of = { kind, key: keyOf(theirs), name: nameOf(theirs), path: [] };
802
+ const removed = removedHere(theirs);
803
+ if (removed === 'changed')
804
+ conflicts.push({ ...of, mine: undefined, theirs });
805
+ else if (removed === undefined) {
806
+ kept.push(theirs);
807
+ updated.push(of);
808
+ }
809
+ }
810
+ return { held: kept, starts: nextStarts };
811
+ };
812
+ const agents = laid('agent', byKey, (agent) => agent.identity.agentId, edits.agents, edits.starts.agents, now.agents, now.starts.agents, before?.agents);
813
+ const tools = laid('tool', byKey, (tool) => tool.toolName, edits.toolSpecs, edits.starts.tools, now.toolSpecs, now.starts.tools, before?.toolSpecs);
814
+ const providers = laid('provider', byId, byId, edits.providers, edits.starts.providers, now.providers, now.starts.providers, before?.providers);
815
+ const workspace = sound({
816
+ ...edits,
817
+ agents: agents.held,
818
+ toolSpecs: tools.held,
819
+ providers: providers.held,
820
+ starts: { agents: agents.starts, tools: tools.starts, providers: providers.starts },
821
+ });
822
+ return { workspace, updated, conflicts, files: now };
823
+ }
824
+ /** Stands where a row was taken out, so the rows after it keep their places until every choice is in. */
825
+ const GONE = Object.freeze({ gone: true });
826
+ function withoutGone(value) {
827
+ if (Array.isArray(value))
828
+ return value.filter((each) => each !== GONE).map(withoutGone);
829
+ if (!isRecord(value))
830
+ return value;
831
+ const fields = Object.entries(value).map(([field, inner]) => [field, withoutGone(inner)]);
832
+ return Object.fromEntries(fields);
833
+ }
834
+ function putAt(value, path, to) {
835
+ const [field, ...rest] = path;
836
+ if (field === undefined)
837
+ return to;
838
+ if (Array.isArray(value)) {
839
+ // A row past the end comes back after the rows there.
840
+ if (typeof field === 'number' && field >= value.length)
841
+ return to === undefined ? value : [...value, to];
842
+ return value.map((each, index) => {
843
+ if (index !== field)
844
+ return each;
845
+ return rest.length === 0 && to === undefined ? GONE : putAt(each, rest, to);
846
+ });
847
+ }
848
+ const next = { ...(isRecord(value) ? value : {}) };
849
+ const inner = putAt(next[field], rest, to);
850
+ if (inner === undefined)
851
+ delete next[field];
852
+ else
853
+ next[field] = inner;
854
+ return next;
855
+ }
856
+ /**
857
+ * A merge's workspace with the files' value at each conflict named in `theirs`, by its place in
858
+ * `merge.conflicts`. Every other conflict keeps the builder's value.
859
+ */
860
+ export function withFilesChosen(merge, theirs) {
861
+ let { agents, toolSpecs, providers } = merge.workspace;
862
+ const starts = {
863
+ agents: { ...merge.workspace.starts.agents },
864
+ tools: { ...merge.workspace.starts.tools },
865
+ providers: { ...merge.workspace.starts.providers },
866
+ };
867
+ const settle = (keyOf, held, kept, conflict) => {
868
+ if (conflict.path.length > 0) {
869
+ return held.map((each) => keyOf(each) === conflict.key ? putAt(each, conflict.path, conflict.theirs) : each);
870
+ }
871
+ // The whole agent, tool, or provider: the files took it away, or hold one the builder removed.
872
+ if (conflict.theirs === undefined) {
873
+ delete kept[conflict.key];
874
+ return held.filter((each) => keyOf(each) !== conflict.key);
875
+ }
876
+ kept[conflict.key] = conflict.theirs;
877
+ return [...held, conflict.theirs];
878
+ };
879
+ for (const index of new Set(theirs)) {
880
+ const conflict = merge.conflicts[index];
881
+ if (!conflict)
882
+ continue;
883
+ if (conflict.kind === 'tool')
884
+ toolSpecs = settle(byKey, toolSpecs, starts.tools, conflict);
885
+ else if (conflict.kind === 'provider')
886
+ providers = settle(byId, providers, starts.providers, conflict);
887
+ else {
888
+ agents = settle(byKey, agents, starts.agents, conflict);
889
+ if (conflict.path.length > 0 || conflict.theirs === undefined)
890
+ continue;
891
+ // An agent that comes back brings the tools that run it, for the agents the files let use them.
892
+ const held = new Set(toolSpecs.map((tool) => tool.key));
893
+ const runIt = merge.files.toolSpecs.filter((tool) => tool.toolType === 'agent' && tool.agentKey === conflict.key && !held.has(tool.key));
894
+ for (const tool of runIt)
895
+ starts.tools[tool.key] = tool;
896
+ toolSpecs = [...toolSpecs, ...runIt];
897
+ agents = agents.map((agent) => {
898
+ const allowed = merge.files.agents.find((each) => each.key === agent.key)?.tools.allow ?? [];
899
+ const more = runIt
900
+ .map((tool) => tool.key)
901
+ .filter((key) => allowed.includes(key) && !agent.tools.allow.includes(key));
902
+ return more.length
903
+ ? { ...agent, tools: { ...agent.tools, allow: [...agent.tools.allow, ...more] } }
904
+ : agent;
905
+ });
906
+ }
907
+ }
908
+ [agents, toolSpecs] = [withoutGone(agents), withoutGone(toolSpecs)];
909
+ return sound({ ...merge.workspace, agents, toolSpecs, providers, starts });
910
+ }
911
+ /**
912
+ * Puts one library tool back to its start, for every agent that allows it. Which agents allow it
913
+ * stays as it is. A tool already at its start returns the same workspace.
914
+ */
915
+ export function resetLibraryTool(workspace, toolKey) {
916
+ const start = workspace.starts.tools[toolKey];
917
+ const now = workspace.toolSpecs.find((tool) => tool.key === toolKey);
918
+ if (!start || !now)
919
+ return workspace;
920
+ const others = workspace.toolSpecs
921
+ .filter((tool) => tool.key !== toolKey)
922
+ .map((tool) => tool.toolName);
923
+ const tool = {
924
+ ...start,
925
+ toolName: freeName(start.toolName, others, (n) => `${start.toolName}_${n}`),
926
+ };
927
+ if (same(tool, now))
928
+ return workspace;
929
+ return {
930
+ ...workspace,
931
+ toolSpecs: workspace.toolSpecs.map((each) => (each.key === toolKey ? tool : each)),
932
+ };
933
+ }
934
+ /** The workspace with one provider as `provider` holds it, by its id. Every agent's models read it. */
935
+ export function withProvider(workspace, provider) {
936
+ const now = workspace.providers.find((each) => each.id === provider.id);
937
+ if (!now || same(now, provider))
938
+ return workspace;
939
+ return {
940
+ ...workspace,
941
+ providers: workspace.providers.map((each) => (each.id === provider.id ? provider : each)),
942
+ };
943
+ }
944
+ /** How many models run on the provider `id`, and across how many agents. */
945
+ export function providerUse(workspace, id) {
946
+ const counts = workspace.agents.map((agent) => providersOf(agent).filter((held) => held === id).length);
947
+ return {
948
+ models: counts.reduce((sum, count) => sum + count, 0),
949
+ agents: counts.filter((count) => count > 0).length,
950
+ };
951
+ }
952
+ /**
953
+ * Who else calls the model `apiId` on the provider `id`, besides the model `bindingKey` of the
954
+ * agent `agentKey`: that agent's other models, by id, and the other agents, by name.
955
+ */
956
+ export function modelSharers(workspace, agentKey, bindingKey, id, apiId) {
957
+ const calls = (binding) => binding.provider === id && binding.apiId.trim() === apiId.trim();
958
+ const own = workspace.agents.find((agent) => agent.key === agentKey)?.modelBindings ?? [];
959
+ return {
960
+ models: own
961
+ .filter((binding) => binding.key !== bindingKey && calls(binding))
962
+ .map((binding) => binding.modelId.trim() || 'new'),
963
+ agents: workspace.agents
964
+ .filter((agent) => agent.key !== agentKey && agent.modelBindings.some(calls))
965
+ .map((agent) => agent.identity.agentId.trim() || 'New agent'),
966
+ };
967
+ }
968
+ /** A provider from the kernel definition `definition` under the first free id: what the + beside a provider picker adds. */
969
+ export function newProvider(taken, definition) {
970
+ return { id: freeName('provider', taken, (n) => `provider_${n}`), definition, connection: {} };
971
+ }
972
+ /** Adds a provider for every agent's models to pick, and opens it. One whose id is taken changes nothing. */
973
+ export function addProvider(workspace, provider) {
974
+ if (workspace.providers.some((each) => each.id === provider.id))
975
+ return workspace;
976
+ return remembered({
977
+ ...workspace,
978
+ providers: [...workspace.providers, provider],
979
+ selected: providerNodeId(provider.id),
980
+ });
981
+ }
982
+ /**
983
+ * Removes a provider no model runs on. One a model runs on stays: a model has no provider to fall
984
+ * to, so its models move first.
985
+ */
986
+ export function removeProvider(workspace, id) {
987
+ const held = workspace.providers.some((each) => each.id === id);
988
+ if (!held || providerUse(workspace, id).models > 0)
989
+ return workspace;
990
+ return remembered({
991
+ ...workspace,
992
+ providers: workspace.providers.filter((each) => each.id !== id),
993
+ selected: workspace.selected === providerNodeId(id)
994
+ ? agentNodeId(workspace.agents[0]?.key ?? '')
995
+ : workspace.selected,
996
+ });
997
+ }
998
+ /**
999
+ * Renames the provider `from` to `to`, and every model that runs on it with it. A blank or taken
1000
+ * `to` changes nothing. Only a provider the files do not hold yet is renamed: code names the others.
1001
+ */
1002
+ export function renameProvider(workspace, from, to) {
1003
+ const id = to.trim();
1004
+ const held = workspace.providers.some((each) => each.id === from);
1005
+ if (!held || !id || workspace.providers.some((each) => each.id === id))
1006
+ return workspace;
1007
+ const { [from]: start, ...starts } = workspace.starts.providers;
1008
+ const moved = (binding) => binding.provider === from;
1009
+ return {
1010
+ ...workspace,
1011
+ providers: workspace.providers.map((each) => (each.id === from ? { ...each, id } : each)),
1012
+ agents: workspace.agents.map((agent) => agent.modelBindings.some(moved)
1013
+ ? {
1014
+ ...agent,
1015
+ modelBindings: agent.modelBindings.map((binding) => moved(binding) ? { ...binding, provider: id } : binding),
1016
+ }
1017
+ : agent),
1018
+ selected: workspace.selected === providerNodeId(from) ? providerNodeId(id) : workspace.selected,
1019
+ starts: {
1020
+ ...workspace.starts,
1021
+ providers: start ? { ...starts, [id]: { ...start, id } } : starts,
1022
+ },
1023
+ };
1024
+ }
1025
+ /** The providers that are as they started: the ones the project's files still say. */
1026
+ export function providersAtStart(workspace) {
1027
+ return workspace.providers.flatMap((provider) => {
1028
+ const start = workspace.starts.providers[provider.id];
1029
+ return start && same(start, provider) ? [provider.id] : [];
1030
+ });
1031
+ }
1032
+ /** Puts one provider back to its start. A provider already at its start returns the same workspace. */
1033
+ export function resetProvider(workspace, id) {
1034
+ const start = workspace.starts.providers[id];
1035
+ return start ? withProvider(workspace, start) : workspace;
1036
+ }
1037
+ function scopeTree(agentKey, node) {
1038
+ return {
1039
+ ...node,
1040
+ id: agentNodeId(agentKey, node.id),
1041
+ children: node.children.map((child) => scopeTree(agentKey, child)),
1042
+ };
1043
+ }
1044
+ /** Beside other agents, one with no id yet is named as an agent, not by its Identity facet. */
1045
+ function agentRoot(agent, providers) {
1046
+ const root = scopeTree(agent.key, studioTree(draftOf(agent, [], providers)));
1047
+ return agent.identity.agentId.trim() ? root : { ...root, label: 'New agent' };
1048
+ }
1049
+ export function workspaceTree(workspace) {
1050
+ return {
1051
+ agents: workspace.agents.map((agent) => agentRoot(agent, workspace.providers)),
1052
+ tools: toolSpecNodes(workspace.toolSpecs),
1053
+ };
1054
+ }