@data-club/ai-hub 0.0.1

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 (344) hide show
  1. package/dist/auth/passwordPolicy.d.ts +55 -0
  2. package/dist/auth/passwordPolicy.d.ts.map +1 -0
  3. package/dist/auth/passwordPolicy.js +63 -0
  4. package/dist/auth/passwordPolicy.js.map +1 -0
  5. package/dist/auth/texts.d.ts +815 -0
  6. package/dist/auth/texts.d.ts.map +1 -0
  7. package/dist/auth/texts.js +429 -0
  8. package/dist/auth/texts.js.map +1 -0
  9. package/dist/client/AIHubClient.d.ts +1058 -0
  10. package/dist/client/AIHubClient.d.ts.map +1 -0
  11. package/dist/client/AIHubClient.js +950 -0
  12. package/dist/client/AIHubClient.js.map +1 -0
  13. package/dist/client/index.d.ts +11 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/client/index.js +9 -0
  16. package/dist/client/index.js.map +1 -0
  17. package/dist/client/sseParser.d.ts +41 -0
  18. package/dist/client/sseParser.d.ts.map +1 -0
  19. package/dist/client/sseParser.js +153 -0
  20. package/dist/client/sseParser.js.map +1 -0
  21. package/dist/client/tokenStorage.d.ts +56 -0
  22. package/dist/client/tokenStorage.d.ts.map +1 -0
  23. package/dist/client/tokenStorage.js +116 -0
  24. package/dist/client/tokenStorage.js.map +1 -0
  25. package/dist/components/AgentsPicker/index.d.ts +70 -0
  26. package/dist/components/AgentsPicker/index.d.ts.map +1 -0
  27. package/dist/components/AgentsPicker/index.js +63 -0
  28. package/dist/components/AgentsPicker/index.js.map +1 -0
  29. package/dist/components/AuthGate/index.d.ts +79 -0
  30. package/dist/components/AuthGate/index.d.ts.map +1 -0
  31. package/dist/components/AuthGate/index.js +61 -0
  32. package/dist/components/AuthGate/index.js.map +1 -0
  33. package/dist/components/CacheBadge/index.d.ts +88 -0
  34. package/dist/components/CacheBadge/index.d.ts.map +1 -0
  35. package/dist/components/CacheBadge/index.js +127 -0
  36. package/dist/components/CacheBadge/index.js.map +1 -0
  37. package/dist/components/ChangePassword/index.d.ts +109 -0
  38. package/dist/components/ChangePassword/index.d.ts.map +1 -0
  39. package/dist/components/ChangePassword/index.js +142 -0
  40. package/dist/components/ChangePassword/index.js.map +1 -0
  41. package/dist/components/ChatShell/ShellHeader.d.ts +44 -0
  42. package/dist/components/ChatShell/ShellHeader.d.ts.map +1 -0
  43. package/dist/components/ChatShell/ShellHeader.js +14 -0
  44. package/dist/components/ChatShell/ShellHeader.js.map +1 -0
  45. package/dist/components/ChatShell/UserMenu.d.ts +48 -0
  46. package/dist/components/ChatShell/UserMenu.d.ts.map +1 -0
  47. package/dist/components/ChatShell/UserMenu.js +86 -0
  48. package/dist/components/ChatShell/UserMenu.js.map +1 -0
  49. package/dist/components/ChatShell/index.d.ts +98 -0
  50. package/dist/components/ChatShell/index.d.ts.map +1 -0
  51. package/dist/components/ChatShell/index.js +124 -0
  52. package/dist/components/ChatShell/index.js.map +1 -0
  53. package/dist/components/DataClubAIHub/index.d.ts +132 -0
  54. package/dist/components/DataClubAIHub/index.d.ts.map +1 -0
  55. package/dist/components/DataClubAIHub/index.js +124 -0
  56. package/dist/components/DataClubAIHub/index.js.map +1 -0
  57. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts +89 -0
  58. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts.map +1 -0
  59. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js +199 -0
  60. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js.map +1 -0
  61. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts +33 -0
  62. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts.map +1 -0
  63. package/dist/components/DataClubAIHubAdmin/AgentsPage.js +137 -0
  64. package/dist/components/DataClubAIHubAdmin/AgentsPage.js.map +1 -0
  65. package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts +50 -0
  66. package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts.map +1 -0
  67. package/dist/components/DataClubAIHubAdmin/CreateUserModal.js +93 -0
  68. package/dist/components/DataClubAIHubAdmin/CreateUserModal.js.map +1 -0
  69. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts +49 -0
  70. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts.map +1 -0
  71. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js +94 -0
  72. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js.map +1 -0
  73. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts +37 -0
  74. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts.map +1 -0
  75. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js +44 -0
  76. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js.map +1 -0
  77. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts +56 -0
  78. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts.map +1 -0
  79. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js +158 -0
  80. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js.map +1 -0
  81. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts +29 -0
  82. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts.map +1 -0
  83. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js +183 -0
  84. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js.map +1 -0
  85. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts +38 -0
  86. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts.map +1 -0
  87. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js +294 -0
  88. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js.map +1 -0
  89. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts +36 -0
  90. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -0
  91. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +373 -0
  92. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -0
  93. package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts +47 -0
  94. package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts.map +1 -0
  95. package/dist/components/DataClubAIHubAdmin/UsersPage.js +233 -0
  96. package/dist/components/DataClubAIHubAdmin/UsersPage.js.map +1 -0
  97. package/dist/components/DataClubAIHubAdmin/index.d.ts +87 -0
  98. package/dist/components/DataClubAIHubAdmin/index.d.ts.map +1 -0
  99. package/dist/components/DataClubAIHubAdmin/index.js +137 -0
  100. package/dist/components/DataClubAIHubAdmin/index.js.map +1 -0
  101. package/dist/components/DataClubAIHubLogin/index.d.ts +113 -0
  102. package/dist/components/DataClubAIHubLogin/index.d.ts.map +1 -0
  103. package/dist/components/DataClubAIHubLogin/index.js +123 -0
  104. package/dist/components/DataClubAIHubLogin/index.js.map +1 -0
  105. package/dist/components/EffortControl/index.d.ts +57 -0
  106. package/dist/components/EffortControl/index.d.ts.map +1 -0
  107. package/dist/components/EffortControl/index.js +84 -0
  108. package/dist/components/EffortControl/index.js.map +1 -0
  109. package/dist/components/Layout/index.d.ts +63 -0
  110. package/dist/components/Layout/index.d.ts.map +1 -0
  111. package/dist/components/Layout/index.js +45 -0
  112. package/dist/components/Layout/index.js.map +1 -0
  113. package/dist/components/MainColumn/index.d.ts +69 -0
  114. package/dist/components/MainColumn/index.d.ts.map +1 -0
  115. package/dist/components/MainColumn/index.js +47 -0
  116. package/dist/components/MainColumn/index.js.map +1 -0
  117. package/dist/components/MarkdownRenderer/index.d.ts +66 -0
  118. package/dist/components/MarkdownRenderer/index.d.ts.map +1 -0
  119. package/dist/components/MarkdownRenderer/index.js +153 -0
  120. package/dist/components/MarkdownRenderer/index.js.map +1 -0
  121. package/dist/components/MessageInput/index.d.ts +85 -0
  122. package/dist/components/MessageInput/index.d.ts.map +1 -0
  123. package/dist/components/MessageInput/index.js +139 -0
  124. package/dist/components/MessageInput/index.js.map +1 -0
  125. package/dist/components/MessageList/index.d.ts +156 -0
  126. package/dist/components/MessageList/index.d.ts.map +1 -0
  127. package/dist/components/MessageList/index.js +352 -0
  128. package/dist/components/MessageList/index.js.map +1 -0
  129. package/dist/components/ReasoningSelector/index.d.ts +77 -0
  130. package/dist/components/ReasoningSelector/index.d.ts.map +1 -0
  131. package/dist/components/ReasoningSelector/index.js +156 -0
  132. package/dist/components/ReasoningSelector/index.js.map +1 -0
  133. package/dist/components/ReasoningView/index.d.ts +94 -0
  134. package/dist/components/ReasoningView/index.d.ts.map +1 -0
  135. package/dist/components/ReasoningView/index.js +83 -0
  136. package/dist/components/ReasoningView/index.js.map +1 -0
  137. package/dist/components/ScrollToPresentButton/index.d.ts +54 -0
  138. package/dist/components/ScrollToPresentButton/index.d.ts.map +1 -0
  139. package/dist/components/ScrollToPresentButton/index.js +44 -0
  140. package/dist/components/ScrollToPresentButton/index.js.map +1 -0
  141. package/dist/components/SessionsPanel/SessionTitleModal.d.ts +79 -0
  142. package/dist/components/SessionsPanel/SessionTitleModal.d.ts.map +1 -0
  143. package/dist/components/SessionsPanel/SessionTitleModal.js +102 -0
  144. package/dist/components/SessionsPanel/SessionTitleModal.js.map +1 -0
  145. package/dist/components/SessionsPanel/index.d.ts +100 -0
  146. package/dist/components/SessionsPanel/index.d.ts.map +1 -0
  147. package/dist/components/SessionsPanel/index.js +176 -0
  148. package/dist/components/SessionsPanel/index.js.map +1 -0
  149. package/dist/components/SystemNotice/index.d.ts +66 -0
  150. package/dist/components/SystemNotice/index.d.ts.map +1 -0
  151. package/dist/components/SystemNotice/index.js +81 -0
  152. package/dist/components/SystemNotice/index.js.map +1 -0
  153. package/dist/components/ThemeSwitcher/index.d.ts +66 -0
  154. package/dist/components/ThemeSwitcher/index.d.ts.map +1 -0
  155. package/dist/components/ThemeSwitcher/index.js +73 -0
  156. package/dist/components/ThemeSwitcher/index.js.map +1 -0
  157. package/dist/components/ToolCallView/formatContent.d.ts +36 -0
  158. package/dist/components/ToolCallView/formatContent.d.ts.map +1 -0
  159. package/dist/components/ToolCallView/formatContent.js +53 -0
  160. package/dist/components/ToolCallView/formatContent.js.map +1 -0
  161. package/dist/components/ToolCallView/index.d.ts +120 -0
  162. package/dist/components/ToolCallView/index.d.ts.map +1 -0
  163. package/dist/components/ToolCallView/index.js +104 -0
  164. package/dist/components/ToolCallView/index.js.map +1 -0
  165. package/dist/components/TpmBanner/index.d.ts +53 -0
  166. package/dist/components/TpmBanner/index.d.ts.map +1 -0
  167. package/dist/components/TpmBanner/index.js +116 -0
  168. package/dist/components/TpmBanner/index.js.map +1 -0
  169. package/dist/components/TurnStatusIndicator/index.d.ts +77 -0
  170. package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -0
  171. package/dist/components/TurnStatusIndicator/index.js +122 -0
  172. package/dist/components/TurnStatusIndicator/index.js.map +1 -0
  173. package/dist/components/internal/PasswordInput.d.ts +62 -0
  174. package/dist/components/internal/PasswordInput.d.ts.map +1 -0
  175. package/dist/components/internal/PasswordInput.js +42 -0
  176. package/dist/components/internal/PasswordInput.js.map +1 -0
  177. package/dist/context/AIHubContext.d.ts +220 -0
  178. package/dist/context/AIHubContext.d.ts.map +1 -0
  179. package/dist/context/AIHubContext.js +19 -0
  180. package/dist/context/AIHubContext.js.map +1 -0
  181. package/dist/context/AIHubProvider.d.ts +75 -0
  182. package/dist/context/AIHubProvider.d.ts.map +1 -0
  183. package/dist/context/AIHubProvider.js +954 -0
  184. package/dist/context/AIHubProvider.js.map +1 -0
  185. package/dist/context/index.d.ts +9 -0
  186. package/dist/context/index.d.ts.map +1 -0
  187. package/dist/context/index.js +9 -0
  188. package/dist/context/index.js.map +1 -0
  189. package/dist/context/theme.d.ts +43 -0
  190. package/dist/context/theme.d.ts.map +1 -0
  191. package/dist/context/theme.js +70 -0
  192. package/dist/context/theme.js.map +1 -0
  193. package/dist/context/useAIHub.d.ts +104 -0
  194. package/dist/context/useAIHub.d.ts.map +1 -0
  195. package/dist/context/useAIHub.js +63 -0
  196. package/dist/context/useAIHub.js.map +1 -0
  197. package/dist/context/useAIHubAuth.d.ts +44 -0
  198. package/dist/context/useAIHubAuth.d.ts.map +1 -0
  199. package/dist/context/useAIHubAuth.js +35 -0
  200. package/dist/context/useAIHubAuth.js.map +1 -0
  201. package/dist/index.d.ts +76 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +57 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/primitives/index.d.ts +99 -0
  206. package/dist/primitives/index.d.ts.map +1 -0
  207. package/dist/primitives/index.js +97 -0
  208. package/dist/primitives/index.js.map +1 -0
  209. package/dist/state/affordances.d.ts +182 -0
  210. package/dist/state/affordances.d.ts.map +1 -0
  211. package/dist/state/affordances.js +189 -0
  212. package/dist/state/affordances.js.map +1 -0
  213. package/dist/state/stateReducer.d.ts +72 -0
  214. package/dist/state/stateReducer.d.ts.map +1 -0
  215. package/dist/state/stateReducer.js +122 -0
  216. package/dist/state/stateReducer.js.map +1 -0
  217. package/dist/stories/EffortControl.stories.d.ts +14 -0
  218. package/dist/stories/EffortControl.stories.d.ts.map +1 -0
  219. package/dist/stories/EffortControl.stories.js +43 -0
  220. package/dist/stories/EffortControl.stories.js.map +1 -0
  221. package/dist/stories/MarkdownRenderer.stories.d.ts +25 -0
  222. package/dist/stories/MarkdownRenderer.stories.d.ts.map +1 -0
  223. package/dist/stories/MarkdownRenderer.stories.js +178 -0
  224. package/dist/stories/MarkdownRenderer.stories.js.map +1 -0
  225. package/dist/stories/MessageBubble.stories.d.ts +21 -0
  226. package/dist/stories/MessageBubble.stories.d.ts.map +1 -0
  227. package/dist/stories/MessageBubble.stories.js +59 -0
  228. package/dist/stories/MessageBubble.stories.js.map +1 -0
  229. package/dist/stories/ReasoningView.stories.d.ts +18 -0
  230. package/dist/stories/ReasoningView.stories.d.ts.map +1 -0
  231. package/dist/stories/ReasoningView.stories.js +62 -0
  232. package/dist/stories/ReasoningView.stories.js.map +1 -0
  233. package/dist/stories/ScrollToPresentButton.stories.d.ts +18 -0
  234. package/dist/stories/ScrollToPresentButton.stories.d.ts.map +1 -0
  235. package/dist/stories/ScrollToPresentButton.stories.js +34 -0
  236. package/dist/stories/ScrollToPresentButton.stories.js.map +1 -0
  237. package/dist/stories/SystemNotice.stories.d.ts +17 -0
  238. package/dist/stories/SystemNotice.stories.d.ts.map +1 -0
  239. package/dist/stories/SystemNotice.stories.js +42 -0
  240. package/dist/stories/SystemNotice.stories.js.map +1 -0
  241. package/dist/stories/ThemeSwitcher.stories.d.ts +15 -0
  242. package/dist/stories/ThemeSwitcher.stories.d.ts.map +1 -0
  243. package/dist/stories/ThemeSwitcher.stories.js +35 -0
  244. package/dist/stories/ThemeSwitcher.stories.js.map +1 -0
  245. package/dist/stories/ToolCallView.stories.d.ts +20 -0
  246. package/dist/stories/ToolCallView.stories.d.ts.map +1 -0
  247. package/dist/stories/ToolCallView.stories.js +65 -0
  248. package/dist/stories/ToolCallView.stories.js.map +1 -0
  249. package/dist/stories/TpmBanner.stories.d.ts +24 -0
  250. package/dist/stories/TpmBanner.stories.d.ts.map +1 -0
  251. package/dist/stories/TpmBanner.stories.js +63 -0
  252. package/dist/stories/TpmBanner.stories.js.map +1 -0
  253. package/dist/stories/TurnStatusIndicator.stories.d.ts +25 -0
  254. package/dist/stories/TurnStatusIndicator.stories.d.ts.map +1 -0
  255. package/dist/stories/TurnStatusIndicator.stories.js +56 -0
  256. package/dist/stories/TurnStatusIndicator.stories.js.map +1 -0
  257. package/dist/stories/_mockProvider.d.ts +44 -0
  258. package/dist/stories/_mockProvider.d.ts.map +1 -0
  259. package/dist/stories/_mockProvider.js +84 -0
  260. package/dist/stories/_mockProvider.js.map +1 -0
  261. package/dist/types.d.ts +787 -0
  262. package/dist/types.d.ts.map +1 -0
  263. package/dist/types.js +19 -0
  264. package/dist/types.js.map +1 -0
  265. package/dist/webcomponent/defineWebComponent.d.ts +106 -0
  266. package/dist/webcomponent/defineWebComponent.d.ts.map +1 -0
  267. package/dist/webcomponent/defineWebComponent.js +126 -0
  268. package/dist/webcomponent/defineWebComponent.js.map +1 -0
  269. package/dist/webcomponent/index.d.ts +7 -0
  270. package/dist/webcomponent/index.d.ts.map +1 -0
  271. package/dist/webcomponent/index.js +7 -0
  272. package/dist/webcomponent/index.js.map +1 -0
  273. package/package.json +63 -0
  274. package/src/__lint-canary__/step1-lint-canary.ts.disabled +28 -0
  275. package/src/auth/passwordPolicy.ts +75 -0
  276. package/src/auth/texts.ts +1294 -0
  277. package/src/client/AIHubClient.ts +1718 -0
  278. package/src/client/index.ts +30 -0
  279. package/src/client/sseParser.ts +168 -0
  280. package/src/client/tokenStorage.ts +131 -0
  281. package/src/components/AgentsPicker/index.tsx +147 -0
  282. package/src/components/AuthGate/index.tsx +132 -0
  283. package/src/components/CacheBadge/index.tsx +245 -0
  284. package/src/components/ChangePassword/index.tsx +375 -0
  285. package/src/components/ChatShell/ShellHeader.tsx +80 -0
  286. package/src/components/ChatShell/UserMenu.tsx +196 -0
  287. package/src/components/ChatShell/index.tsx +307 -0
  288. package/src/components/DataClubAIHub/index.tsx +300 -0
  289. package/src/components/DataClubAIHubAdmin/AgentEditorModal.tsx +505 -0
  290. package/src/components/DataClubAIHubAdmin/AgentsPage.tsx +342 -0
  291. package/src/components/DataClubAIHubAdmin/CreateUserModal.tsx +214 -0
  292. package/src/components/DataClubAIHubAdmin/PasswordRevealModal.tsx +204 -0
  293. package/src/components/DataClubAIHubAdmin/ResetPasswordModal.tsx +122 -0
  294. package/src/components/DataClubAIHubAdmin/RotatingMessageEditorModal.tsx +408 -0
  295. package/src/components/DataClubAIHubAdmin/RotatingMessagesPage.tsx +373 -0
  296. package/src/components/DataClubAIHubAdmin/SessionsOverviewPage.tsx +639 -0
  297. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +979 -0
  298. package/src/components/DataClubAIHubAdmin/UsersPage.tsx +495 -0
  299. package/src/components/DataClubAIHubAdmin/index.tsx +277 -0
  300. package/src/components/DataClubAIHubLogin/index.tsx +282 -0
  301. package/src/components/EffortControl/index.tsx +138 -0
  302. package/src/components/Layout/index.tsx +92 -0
  303. package/src/components/MainColumn/index.tsx +131 -0
  304. package/src/components/MarkdownRenderer/index.tsx +256 -0
  305. package/src/components/MessageInput/index.tsx +283 -0
  306. package/src/components/MessageList/index.tsx +753 -0
  307. package/src/components/ReasoningSelector/index.tsx +268 -0
  308. package/src/components/ReasoningView/index.tsx +183 -0
  309. package/src/components/ScrollToPresentButton/index.tsx +85 -0
  310. package/src/components/SessionsPanel/SessionTitleModal.tsx +256 -0
  311. package/src/components/SessionsPanel/index.tsx +406 -0
  312. package/src/components/SystemNotice/index.tsx +151 -0
  313. package/src/components/ThemeSwitcher/index.tsx +136 -0
  314. package/src/components/ToolCallView/formatContent.ts +50 -0
  315. package/src/components/ToolCallView/index.tsx +274 -0
  316. package/src/components/TpmBanner/index.tsx +172 -0
  317. package/src/components/TurnStatusIndicator/index.tsx +203 -0
  318. package/src/components/internal/PasswordInput.tsx +145 -0
  319. package/src/context/AIHubContext.ts +249 -0
  320. package/src/context/AIHubProvider.tsx +1084 -0
  321. package/src/context/index.ts +9 -0
  322. package/src/context/theme.ts +76 -0
  323. package/src/context/useAIHub.ts +151 -0
  324. package/src/context/useAIHubAuth.ts +65 -0
  325. package/src/index.ts +275 -0
  326. package/src/primitives/index.ts +101 -0
  327. package/src/state/affordances.ts +321 -0
  328. package/src/state/stateReducer.ts +131 -0
  329. package/src/stories/EffortControl.stories.tsx +63 -0
  330. package/src/stories/MarkdownRenderer.stories.tsx +201 -0
  331. package/src/stories/MessageBubble.stories.tsx +101 -0
  332. package/src/stories/ReasoningView.stories.tsx +81 -0
  333. package/src/stories/ScrollToPresentButton.stories.tsx +52 -0
  334. package/src/stories/SystemNotice.stories.tsx +59 -0
  335. package/src/stories/ThemeSwitcher.stories.tsx +55 -0
  336. package/src/stories/ToolCallView.stories.tsx +95 -0
  337. package/src/stories/TpmBanner.stories.tsx +93 -0
  338. package/src/stories/TurnStatusIndicator.stories.tsx +86 -0
  339. package/src/stories/_mockProvider.tsx +116 -0
  340. package/src/styles/CLASSES.md +356 -0
  341. package/src/styles/default.css +2937 -0
  342. package/src/types.ts +867 -0
  343. package/src/webcomponent/defineWebComponent.tsx +193 -0
  344. package/src/webcomponent/index.ts +11 -0
@@ -0,0 +1,954 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * `<AIHubProvider>` — root of every ai-hub React tree.
4
+ *
5
+ * Owns:
6
+ *
7
+ * - The shared `AIHubClient` instance (consumers call SDK methods through
8
+ * `useAIHub().client`).
9
+ * - The JWT lifecycle: read from `tokenStorage` on mount, attempt `/auth/me`
10
+ * to hydrate `user` + `deployment`; clear on logout; clear and route
11
+ * back to login on any 401 from the server (§6.7 — no silent refresh).
12
+ * - The resolved theme: client-side precedence per §7.6, FOUC-avoided via
13
+ * a cached value in `localStorage` applied on first paint.
14
+ * - Per-message reasoning effort state.
15
+ * - **Step 9 — chat state:** agents catalog, active agent, sessions list,
16
+ * active session, message history, in-flight turn state (driven by the
17
+ * §10 reducer over SSE events from `client.streamChat`), `sendMessage`,
18
+ * `stop`. The state-aware affordance gates (TPM banner, theme switcher,
19
+ * tool-call card, reasoning view) layer on top in Step 10.
20
+ *
21
+ * @see SPECIFICATIONS.md §7.2 — provider props
22
+ * @see SPECIFICATIONS.md §7.5 — state on the client
23
+ * @see SPECIFICATIONS.md §7.6 — theme resolution
24
+ * @see SPECIFICATIONS.md §6.7 — auth model
25
+ */
26
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
27
+ import { AIHubClient, AIHubRequestError, } from "../client/AIHubClient.js";
28
+ import { initialAIHubState, reduceAIHubState, } from "../state/stateReducer.js";
29
+ import { aiHubContext } from "./AIHubContext.js";
30
+ import { readCachedTheme, resolveTheme, writeCachedTheme, } from "./theme.js";
31
+ /**
32
+ * When the post-stream canonicalize refetches messages from the server,
33
+ * we merge the optimistic-streaming `reasoning` + `tool_calls` we
34
+ * captured during the turn into the canonical assistant message that
35
+ * carries the same id. This handles two cases:
36
+ *
37
+ * 1. **Race window.** Letta's `tool_return_message` commit can lag the
38
+ * SSE `tool_call_result` event by a few ms; if our refetch hits in
39
+ * that window the projector sees the tool_call but no return, so
40
+ * the canonical row's `tool_calls` is missing or stale.
41
+ * 2. **Older server build.** Operators upgrading mid-session may still
42
+ * be running a server without the §10.x aggregating projector. The
43
+ * canonical row carries no `reasoning` / `tool_calls` at all.
44
+ *
45
+ * Either way, the optimistic data we already showed during streaming
46
+ * is correct; merging it onto the canonical row keeps it visible.
47
+ *
48
+ * @param canonical - the freshly-refetched list from
49
+ * `client.getSessionMessages`.
50
+ * @param optimistic - the message list we held in state before the
51
+ * refetch (post-streaming accumulation).
52
+ * @returns a new list, same identity / shape as `canonical`, with the
53
+ * missing affordance fields filled in from `optimistic` when present.
54
+ */
55
+ function mergeOptimisticAffordances(canonical, optimistic) {
56
+ const optimisticById = new Map();
57
+ for (const m of optimistic) {
58
+ if (m.role === "assistant" && (m.reasoning || m.tool_calls?.length || m.usage)) {
59
+ optimisticById.set(m.id, m);
60
+ }
61
+ }
62
+ return canonical.map((c) => {
63
+ if (c.role !== "assistant")
64
+ return c;
65
+ const o = optimisticById.get(c.id);
66
+ if (!o)
67
+ return c;
68
+ const next = { ...c };
69
+ if (!next.reasoning && o.reasoning) {
70
+ next.reasoning = o.reasoning;
71
+ }
72
+ if ((!next.tool_calls || next.tool_calls.length === 0) && o.tool_calls?.length) {
73
+ next.tool_calls = o.tool_calls;
74
+ }
75
+ // Usage rides through too — if the canonical refetch happens
76
+ // before the server stamped `message_usage` (small race window),
77
+ // the live `usage` event we already handled keeps the badge
78
+ // rendered until the next refresh confirms it.
79
+ if (!next.usage && o.usage) {
80
+ next.usage = o.usage;
81
+ }
82
+ return next;
83
+ });
84
+ }
85
+ /**
86
+ * The provider component. Single instance per ai-hub mount — multiple
87
+ * mounts on the same page each manage their own auth state (this is the
88
+ * shape `defineWebComponent` ships).
89
+ *
90
+ * @param props - the provider props.
91
+ */
92
+ export function AIHubProvider(props) {
93
+ const { baseUrl, children, client: clientProp, defaultTheme = "light", fetch: fetchProp, onError, tokenStorage = "localStorage", } = props;
94
+ // ── client ───────────────────────────────────────────────────────────
95
+ const unauthorizedRef = useRef(null);
96
+ const client = useMemo(() => {
97
+ if (clientProp)
98
+ return clientProp;
99
+ return new AIHubClient({
100
+ baseUrl,
101
+ ...(fetchProp ? { fetch: fetchProp } : {}),
102
+ tokenStorage,
103
+ onUnauthorized: () => unauthorizedRef.current?.(),
104
+ });
105
+ }, [baseUrl, clientProp, fetchProp, tokenStorage]);
106
+ // ── auth state ──────────────────────────────────────────────────────
107
+ const [user, setUser] = useState(null);
108
+ const [deployment, setDeployment] = useState(null);
109
+ // Server feature toggles fetched from public GET /feature-toggles on mount
110
+ // (§8.4). `null` while the request is in flight; thereafter `{}` (empty
111
+ // blob — no overrides) or the typed blob. The provider does not block
112
+ // rendering on this — the §8.4 precedence ("server toggle > React prop >
113
+ // built-in default") falls through to the prop default until the fetch
114
+ // resolves, which keeps the FOUC story honest.
115
+ const [featureToggles, setFeatureToggles] = useState(null);
116
+ const [mustChangePassword, setMustChangePassword] = useState(false);
117
+ const [hydrating, setHydrating] = useState(true);
118
+ // ── effort (§7.4) + theme (§7.6) ────────────────────────────────────
119
+ const [effort, setEffort] = useState("medium");
120
+ const [theme, setThemeState] = useState(() => {
121
+ return readCachedTheme() ?? defaultTheme;
122
+ });
123
+ // ── chat state (Step 9) ─────────────────────────────────────────────
124
+ const [agents, setAgents] = useState([]);
125
+ const [agentsLoading, setAgentsLoading] = useState(false);
126
+ const [activeAgentId, setActiveAgentIdState] = useState(null);
127
+ const [sessions, setSessions] = useState([]);
128
+ const [sessionsLoading, setSessionsLoading] = useState(false);
129
+ const [includeArchivedSessions, setIncludeArchivedSessions] = useState(false);
130
+ const [activeSessionId, setActiveSessionIdState] = useState(null);
131
+ const [messages, setMessages] = useState([]);
132
+ const [messagesLoading, setMessagesLoading] = useState(false);
133
+ const [streamState, setStreamState] = useState(() => initialAIHubState());
134
+ const [streamingAssistantText, setStreamingAssistantText] = useState(null);
135
+ // Reasoning text accumulating during the current turn, when the user
136
+ // sent a non-`none` effort and the agent emitted `reasoning_delta`
137
+ // events. Cleared on `stream_end` (the canonicalized assistant
138
+ // message will carry the final reasoning).
139
+ const [streamingReasoning, setStreamingReasoning] = useState(null);
140
+ // Tool calls accumulating during the current turn. Map keyed by
141
+ // `tool_call_id`; one entry per call. Cleared on `stream_end`.
142
+ const [streamingToolCalls, setStreamingToolCalls] = useState([]);
143
+ const [lastTurnError, setLastTurnError] = useState(null);
144
+ // True from `stop()` invocation until the matching `stream_end` lands.
145
+ // The state machine's `state.status` stays `running` for the brief
146
+ // server-side cancel window — `isStoppingTurn` is the client-only flag
147
+ // that drives the "Stopping…" button label and keeps the input
148
+ // disabled until the genuine terminal event arrives.
149
+ const [isStoppingTurn, setIsStoppingTurn] = useState(false);
150
+ // Text to push into `<MessageInput>` on the next render. Populated by
151
+ // the post-stream-end branch when a cancel rolled back the user's
152
+ // message (single-step cancel: Letta deletes the entire turn from
153
+ // history — the user's typed text would be lost without this).
154
+ const [pendingDraft, setPendingDraft] = useState(null);
155
+ // The turn currently being streamed: AbortController + cached turn id
156
+ // (read off `stream_start`) so `stop()` can fire the explicit cancel
157
+ // endpoint *and* abort the SSE iterator at the same time. We carry both
158
+ // in a ref so updates don't trigger re-renders mid-stream.
159
+ const streamRef = useRef(null);
160
+ // Mirror of `activeSessionId` for use inside long-running async
161
+ // callbacks (the streamChat loop closes over `sessionId` from the
162
+ // moment `sendMessage` was called; we use this ref to detect whether
163
+ // the user has switched sessions in the meantime before applying the
164
+ // post-stream canonicalize).
165
+ const activeSessionIdRef = useRef(null);
166
+ // Mirror of `messages` used by `sendMessage` to snapshot the message
167
+ // count BEFORE the optimistic user-bubble append. After stream_end
168
+ // the post-stream branch compares the canonical message count to this
169
+ // baseline: if `canonical.length <= baseline` the cancelled turn was
170
+ // rolled back entirely (single-step cancel — Letta deletes the user
171
+ // message too), and the user's typed text is pushed into `pendingDraft`
172
+ // so `<MessageInput>` can prefill it. State refs can lag a render
173
+ // behind the value but `sendMessage` reads it synchronously at the top.
174
+ const messagesRef = useRef([]);
175
+ messagesRef.current = messages;
176
+ /**
177
+ * Resets every chat-state slot. Called on logout and on
178
+ * unauthenticated → authenticated transitions so a previous user's
179
+ * artifacts don't leak into the next session.
180
+ */
181
+ const resetChatState = useCallback(() => {
182
+ streamRef.current?.controller.abort();
183
+ streamRef.current = null;
184
+ setAgents([]);
185
+ setActiveAgentIdState(null);
186
+ setSessions([]);
187
+ setIncludeArchivedSessions(false);
188
+ setActiveSessionIdState(null);
189
+ setMessages([]);
190
+ setStreamState(initialAIHubState());
191
+ setStreamingAssistantText(null);
192
+ setLastTurnError(null);
193
+ }, []);
194
+ unauthorizedRef.current = () => {
195
+ setUser(null);
196
+ setDeployment(null);
197
+ setMustChangePassword(false);
198
+ resetChatState();
199
+ };
200
+ // Keep the ref in sync so the post-stream canonicalize sees the
201
+ // current value (it closes over a stale `activeSessionId` otherwise).
202
+ activeSessionIdRef.current = activeSessionId;
203
+ // ── hydrate on mount ────────────────────────────────────────────────
204
+ useEffect(() => {
205
+ let cancelled = false;
206
+ const token = client.getToken();
207
+ if (!token) {
208
+ setHydrating(false);
209
+ return;
210
+ }
211
+ void (async () => {
212
+ try {
213
+ const context = await client.me();
214
+ if (cancelled)
215
+ return;
216
+ setUser(context.user);
217
+ setDeployment(context.deployment);
218
+ setMustChangePassword(false);
219
+ }
220
+ catch (err) {
221
+ if (cancelled)
222
+ return;
223
+ if (err instanceof AIHubRequestError && err.code !== "unauthorized") {
224
+ onError?.({
225
+ code: err.code,
226
+ message: err.message,
227
+ ...(err.status !== undefined ? { status: err.status } : {}),
228
+ ...(err.request_id !== undefined ? { request_id: err.request_id } : {}),
229
+ });
230
+ }
231
+ }
232
+ finally {
233
+ if (!cancelled)
234
+ setHydrating(false);
235
+ }
236
+ })();
237
+ return () => {
238
+ cancelled = true;
239
+ };
240
+ }, [client, onError]);
241
+ // ── load deployment-wide feature toggles on mount ───────────────────
242
+ // The endpoint is public (no auth) so we fetch as soon as the provider
243
+ // mounts — even before login lands — so the chat shell can apply the
244
+ // §8.4 precedence rules on first paint. Errors are logged via
245
+ // `onError` but never block rendering (we fall through to React-prop
246
+ // defaults).
247
+ useEffect(() => {
248
+ let cancelled = false;
249
+ void (async () => {
250
+ try {
251
+ const toggles = await client.getFeatureToggles();
252
+ if (cancelled)
253
+ return;
254
+ setFeatureToggles(toggles);
255
+ }
256
+ catch (err) {
257
+ if (cancelled)
258
+ return;
259
+ if (err instanceof AIHubRequestError) {
260
+ onError?.({
261
+ code: err.code,
262
+ message: err.message,
263
+ ...(err.status !== undefined ? { status: err.status } : {}),
264
+ });
265
+ }
266
+ // Fall through with an empty blob so every prop default applies.
267
+ setFeatureToggles({});
268
+ }
269
+ })();
270
+ return () => {
271
+ cancelled = true;
272
+ };
273
+ }, [client, onError]);
274
+ // ── reconcile resolved theme whenever inputs change ────────────────
275
+ useEffect(() => {
276
+ if (!deployment)
277
+ return;
278
+ const resolved = resolveTheme(user, deployment, defaultTheme);
279
+ setThemeState(resolved);
280
+ writeCachedTheme(resolved);
281
+ }, [user, deployment, defaultTheme]);
282
+ // ── load /agents whenever an authenticated user becomes available ───
283
+ const refreshAgents = useCallback(async () => {
284
+ setAgentsLoading(true);
285
+ try {
286
+ const list = await client.listAgents();
287
+ setAgents(list);
288
+ }
289
+ catch (err) {
290
+ if (err instanceof AIHubRequestError) {
291
+ onError?.({
292
+ code: err.code,
293
+ message: err.message,
294
+ ...(err.status !== undefined ? { status: err.status } : {}),
295
+ });
296
+ }
297
+ setAgents([]);
298
+ }
299
+ finally {
300
+ setAgentsLoading(false);
301
+ }
302
+ }, [client, onError]);
303
+ useEffect(() => {
304
+ if (!user || mustChangePassword)
305
+ return;
306
+ void refreshAgents();
307
+ }, [user, mustChangePassword, refreshAgents]);
308
+ // ── load /sessions whenever an agent is opened ──────────────────────
309
+ const refreshSessions = useCallback(async () => {
310
+ if (!activeAgentId) {
311
+ setSessions([]);
312
+ return;
313
+ }
314
+ setSessionsLoading(true);
315
+ try {
316
+ const list = await client.listSessions({
317
+ agentId: activeAgentId,
318
+ includeArchived: includeArchivedSessions,
319
+ });
320
+ setSessions(list);
321
+ }
322
+ catch (err) {
323
+ if (err instanceof AIHubRequestError) {
324
+ onError?.({
325
+ code: err.code,
326
+ message: err.message,
327
+ ...(err.status !== undefined ? { status: err.status } : {}),
328
+ });
329
+ }
330
+ setSessions([]);
331
+ }
332
+ finally {
333
+ setSessionsLoading(false);
334
+ }
335
+ }, [activeAgentId, client, includeArchivedSessions, onError]);
336
+ useEffect(() => {
337
+ if (!activeAgentId) {
338
+ setSessions([]);
339
+ return;
340
+ }
341
+ void refreshSessions();
342
+ }, [activeAgentId, includeArchivedSessions, refreshSessions]);
343
+ // ── load /sessions/{id}/messages whenever a session is opened ──────
344
+ //
345
+ // Note: `setActiveSession` / `createSession` already clear `messages`
346
+ // and set `messagesLoading=true` synchronously in the same batch as
347
+ // the id flip, so the previous session's bubbles never flash during
348
+ // this fetch. We don't repeat those resets here — only the fetch +
349
+ // the loading-flag flip on completion are this effect's job.
350
+ useEffect(() => {
351
+ if (!activeSessionId) {
352
+ setMessages([]);
353
+ setMessagesLoading(false);
354
+ return;
355
+ }
356
+ let cancelled = false;
357
+ setMessagesLoading(true);
358
+ void (async () => {
359
+ try {
360
+ const list = await client.getSessionMessages(activeSessionId);
361
+ if (cancelled)
362
+ return;
363
+ setMessages(list);
364
+ }
365
+ catch (err) {
366
+ if (cancelled)
367
+ return;
368
+ if (err instanceof AIHubRequestError) {
369
+ onError?.({
370
+ code: err.code,
371
+ message: err.message,
372
+ ...(err.status !== undefined ? { status: err.status } : {}),
373
+ });
374
+ }
375
+ setMessages([]);
376
+ }
377
+ finally {
378
+ if (!cancelled)
379
+ setMessagesLoading(false);
380
+ }
381
+ })();
382
+ return () => {
383
+ cancelled = true;
384
+ };
385
+ }, [activeSessionId, client, onError]);
386
+ // ── action callbacks (auth + theme) ────────────────────────────────
387
+ const login = async (username, password) => {
388
+ const result = await client.login(username, password);
389
+ setMustChangePassword(result.mustChangePassword);
390
+ setUser(result.user);
391
+ setDeployment(result.deployment ?? null);
392
+ return result;
393
+ };
394
+ const logout = async () => {
395
+ try {
396
+ await client.logout();
397
+ }
398
+ finally {
399
+ setUser(null);
400
+ setDeployment(null);
401
+ setMustChangePassword(false);
402
+ resetChatState();
403
+ }
404
+ };
405
+ const changePassword = async (currentPassword, newPassword) => {
406
+ await client.changePassword(currentPassword, newPassword);
407
+ const context = await client.me();
408
+ setUser(context.user);
409
+ setDeployment(context.deployment);
410
+ setMustChangePassword(false);
411
+ };
412
+ const setTheme = async (next) => {
413
+ const previous = theme;
414
+ setThemeState(next);
415
+ writeCachedTheme(next);
416
+ try {
417
+ const context = await client.updatePreferences({ theme: next });
418
+ setUser(context.user);
419
+ setDeployment(context.deployment);
420
+ }
421
+ catch (err) {
422
+ setThemeState(previous);
423
+ writeCachedTheme(previous);
424
+ throw err;
425
+ }
426
+ };
427
+ // ── chat actions ───────────────────────────────────────────────────
428
+ const setActiveAgent = (id) => {
429
+ streamRef.current?.controller.abort();
430
+ streamRef.current = null;
431
+ setActiveAgentIdState(id);
432
+ setActiveSessionIdState(null);
433
+ setMessages([]);
434
+ setStreamingAssistantText(null);
435
+ setLastTurnError(null);
436
+ setIsStoppingTurn(false);
437
+ setPendingDraft(null);
438
+ setStreamState(initialAIHubState());
439
+ };
440
+ const setActiveSession = (id) => {
441
+ if (id === activeSessionId)
442
+ return;
443
+ streamRef.current?.controller.abort();
444
+ streamRef.current = null;
445
+ setActiveSessionIdState(id);
446
+ // Clear messages synchronously in the same setState batch as the id
447
+ // change — otherwise the previous session's bubbles flash for the
448
+ // duration of the next session's `/sessions/:id/messages` fetch.
449
+ setMessages([]);
450
+ setMessagesLoading(id !== null);
451
+ setStreamingAssistantText(null);
452
+ setLastTurnError(null);
453
+ setIsStoppingTurn(false);
454
+ setPendingDraft(null);
455
+ setStreamState(initialAIHubState());
456
+ };
457
+ const createSession = async (title) => {
458
+ if (!activeAgentId) {
459
+ throw new AIHubRequestError("validation", "Open an agent before creating a session.");
460
+ }
461
+ const session = await client.createSession({
462
+ agentId: activeAgentId,
463
+ title,
464
+ });
465
+ // Prepend so the new row appears at the top of the list (server orders
466
+ // by updated_at DESC).
467
+ setSessions((prev) => [session, ...prev.filter((s) => s.id !== session.id)]);
468
+ // Same synchronous-clear story as `setActiveSession` — otherwise the
469
+ // previous session's bubbles flash during the brand-new session's
470
+ // (empty) history fetch.
471
+ streamRef.current?.controller.abort();
472
+ streamRef.current = null;
473
+ setActiveSessionIdState(session.id);
474
+ setMessages([]);
475
+ setMessagesLoading(true);
476
+ setStreamingAssistantText(null);
477
+ setLastTurnError(null);
478
+ setStreamState(initialAIHubState());
479
+ // Refresh the agents catalog so the picker's per-caller session_count
480
+ // reflects the new row on the next visit. Best-effort — failure isn't
481
+ // fatal.
482
+ void refreshAgents();
483
+ return session;
484
+ };
485
+ const patchSession = async (id, patch) => {
486
+ const updated = await client.patchSession(id, patch);
487
+ setSessions((prev) => {
488
+ const next = prev.map((s) => (s.id === id ? updated : s));
489
+ // If the row was archived and the filter is off, drop it from view.
490
+ if (patch.isArchived === true &&
491
+ !includeArchivedSessions) {
492
+ return next.filter((s) => !s.is_archived);
493
+ }
494
+ return next;
495
+ });
496
+ if (patch.isArchived === true && id === activeSessionId) {
497
+ setActiveSessionIdState(null);
498
+ }
499
+ return updated;
500
+ };
501
+ const sendMessage = async (text) => {
502
+ const sessionId = activeSessionId;
503
+ if (!sessionId) {
504
+ throw new AIHubRequestError("validation", "Open a session before sending a message.");
505
+ }
506
+ if (typeof text !== "string" || text.length === 0) {
507
+ throw new AIHubRequestError("validation", "Cannot send an empty message.");
508
+ }
509
+ // Optimistically append the user's message so the bubble shows up
510
+ // immediately. The server doesn't echo user messages on the stream —
511
+ // they're already in the conversation by the time we ask Letta to
512
+ // produce a reply. Stream-end refetches `getSessionMessages` and
513
+ // replaces the array with Letta's canonical history, so this
514
+ // pending-* id is short-lived; we dedupe defensively anyway.
515
+ const optimisticUser = {
516
+ id: `pending-${Date.now()}`,
517
+ role: "user",
518
+ content: text,
519
+ created_at: new Date().toISOString(),
520
+ };
521
+ setMessages((prev) => {
522
+ if (prev.some((m) => m.id === optimisticUser.id))
523
+ return prev;
524
+ return [...prev, optimisticUser];
525
+ });
526
+ setStreamingAssistantText("");
527
+ setLastTurnError(null);
528
+ const controller = new AbortController();
529
+ streamRef.current = { controller, turnId: null };
530
+ let accumulated = "";
531
+ // Streaming reasoning + tool-call accumulators — kept locally
532
+ // (closure-scoped) so we can flush them into the canonicalized
533
+ // message at end-of-stream OR clear them on cancel/error. Both
534
+ // mirror to React state so the live bubble renders while the turn
535
+ // runs.
536
+ let accumulatedReasoning = "";
537
+ const toolCallsByCallId = new Map();
538
+ function snapshotToolCalls() {
539
+ const out = [];
540
+ for (const [callId, info] of toolCallsByCallId.entries()) {
541
+ out.push({
542
+ tool_call_id: callId,
543
+ tool_name: info.tool_name,
544
+ input: info.input,
545
+ status: info.status,
546
+ output: info.output,
547
+ error: info.error,
548
+ });
549
+ }
550
+ return out;
551
+ }
552
+ setStreamingReasoning(null);
553
+ setStreamingToolCalls([]);
554
+ // Track outcome signals collected during the stream. Used after the
555
+ // loop to decide whether to:
556
+ // - canonicalize against Letta's history (replace optimistic
557
+ // state with the real one)
558
+ // - leave the optimistic state in place + surface an error
559
+ // - leave the optimistic state in place + stay silent (user
560
+ // cancelled — they didn't ask for an error)
561
+ let sawAssistantOutput = false;
562
+ let sawError = false;
563
+ let endReason = null;
564
+ try {
565
+ const iter = client.streamChat({ sessionId, userMessage: text, effort }, { signal: controller.signal });
566
+ for await (const event of iter) {
567
+ // Drive the §10 reducer (turn state).
568
+ setStreamState((prev) => reduceAIHubState(prev, event));
569
+ if (event.event === "stream_start") {
570
+ if (streamRef.current) {
571
+ streamRef.current.turnId = event.data.turn_id;
572
+ }
573
+ }
574
+ else if (event.event === "assistant_delta") {
575
+ accumulated += event.data.delta;
576
+ setStreamingAssistantText(accumulated);
577
+ sawAssistantOutput = true;
578
+ }
579
+ else if (event.event === "reasoning_delta") {
580
+ accumulatedReasoning += event.data.delta;
581
+ setStreamingReasoning(accumulatedReasoning);
582
+ }
583
+ else if (event.event === "tool_call_started") {
584
+ const callId = event.data.tool_call_id;
585
+ if (!toolCallsByCallId.has(callId)) {
586
+ toolCallsByCallId.set(callId, {
587
+ tool_name: event.data.tool_name,
588
+ input: "",
589
+ status: "success",
590
+ output: null,
591
+ error: null,
592
+ });
593
+ setStreamingToolCalls(snapshotToolCalls());
594
+ }
595
+ }
596
+ else if (event.event === "tool_call_input_delta") {
597
+ const info = toolCallsByCallId.get(event.data.tool_call_id);
598
+ if (info) {
599
+ info.input += event.data.partial_json;
600
+ setStreamingToolCalls(snapshotToolCalls());
601
+ }
602
+ }
603
+ else if (event.event === "tool_call_dispatched") {
604
+ // Phase transition is handled by the reducer; we don't need to
605
+ // touch the accumulator (the input is already captured via
606
+ // tool_call_input_delta — `event.data.input` may be empty per
607
+ // the server's translator).
608
+ }
609
+ else if (event.event === "tool_call_result") {
610
+ const info = toolCallsByCallId.get(event.data.tool_call_id);
611
+ if (info) {
612
+ if ("output" in event.data) {
613
+ info.status = "success";
614
+ info.output =
615
+ typeof event.data.output === "string"
616
+ ? event.data.output
617
+ : JSON.stringify(event.data.output, null, 2);
618
+ info.error = null;
619
+ }
620
+ else if ("error" in event.data) {
621
+ info.status = "error";
622
+ info.error =
623
+ typeof event.data.error === "string"
624
+ ? event.data.error
625
+ : JSON.stringify(event.data.error, null, 2);
626
+ info.output = null;
627
+ }
628
+ setStreamingToolCalls(snapshotToolCalls());
629
+ }
630
+ }
631
+ else if (event.event === "assistant_message_complete") {
632
+ // Dedupe — if `assistant_message_complete` fires twice for the
633
+ // same id (server quirk, hot-reload, SSE reconnect), append
634
+ // only the first occurrence. Enrich the final message with
635
+ // the streaming-accumulated reasoning + tool calls so the
636
+ // historical bubble carries the same data the live bubble
637
+ // showed (the server's `/sessions/:id/messages` projector
638
+ // also returns these fields, but we don't wait for a refetch).
639
+ const finalMsg = { ...event.data.message };
640
+ if (accumulatedReasoning.length > 0 && !finalMsg.reasoning) {
641
+ finalMsg.reasoning = accumulatedReasoning;
642
+ }
643
+ const calls = snapshotToolCalls();
644
+ if (calls.length > 0 && !finalMsg.tool_calls) {
645
+ finalMsg.tool_calls = [...calls];
646
+ }
647
+ setMessages((prev) => prev.some((m) => m.id === finalMsg.id) ? prev : [...prev, finalMsg]);
648
+ accumulated = "";
649
+ accumulatedReasoning = "";
650
+ toolCallsByCallId.clear();
651
+ setStreamingAssistantText(null);
652
+ setStreamingReasoning(null);
653
+ setStreamingToolCalls([]);
654
+ sawAssistantOutput = true;
655
+ }
656
+ else if (event.event === "usage") {
657
+ // Live usage stamp — the runner emits this right before
658
+ // `stream_end{done}` carrying the cache-hit/write counts for
659
+ // the turn. We attach it to the matching assistant bubble so
660
+ // `<CacheBadge />` appears immediately (without waiting for
661
+ // the post-stream `/messages` refetch). The refetch path
662
+ // also stamps the same `usage` from the persisted
663
+ // `message_usage` row, so refresh + history retrieval show
664
+ // the badge too.
665
+ const targetId = event.data.letta_message_id;
666
+ if (typeof targetId === "string") {
667
+ const usage = {
668
+ input: event.data.input ?? null,
669
+ output: event.data.output ?? null,
670
+ cache_read: event.data.cache_read ?? null,
671
+ cache_write: event.data.cache_write ?? null,
672
+ reasoning: event.data.reasoning ?? null,
673
+ context: event.data.context ?? null,
674
+ total: event.data.total ?? null,
675
+ };
676
+ setMessages((prev) => prev.map((m) => (m.id === targetId ? { ...m, usage } : m)));
677
+ }
678
+ }
679
+ else if (event.event === "error") {
680
+ setLastTurnError(event.data.message);
681
+ sawError = true;
682
+ }
683
+ else if (event.event === "stream_end") {
684
+ // Terminal — record the reason and clear the streaming
685
+ // accumulator. The final assistant message arrived via
686
+ // `assistant_message_complete` in a healthy stream; in
687
+ // `cancelled` / `error` outcomes the accumulator may carry
688
+ // partial text the UI still wants to show, so we flush it
689
+ // into a synthetic message before clearing.
690
+ endReason = event.data.reason;
691
+ if (accumulated.length > 0) {
692
+ const partialId = `partial-${streamRef.current?.turnId ?? Date.now()}`;
693
+ const partialMsg = {
694
+ id: partialId,
695
+ role: "assistant",
696
+ content: accumulated,
697
+ created_at: new Date().toISOString(),
698
+ };
699
+ if (accumulatedReasoning.length > 0) {
700
+ partialMsg.reasoning = accumulatedReasoning;
701
+ }
702
+ const calls = snapshotToolCalls();
703
+ if (calls.length > 0) {
704
+ partialMsg.tool_calls = [...calls];
705
+ }
706
+ setMessages((prev) => [...prev, partialMsg]);
707
+ accumulated = "";
708
+ sawAssistantOutput = true;
709
+ }
710
+ accumulatedReasoning = "";
711
+ toolCallsByCallId.clear();
712
+ setStreamingAssistantText(null);
713
+ setStreamingReasoning(null);
714
+ setStreamingToolCalls([]);
715
+ }
716
+ }
717
+ }
718
+ catch (err) {
719
+ // Aborts (from `stop()` or a session switch) are not errors. We
720
+ // record the outcome explicitly so the post-stream logic below
721
+ // can canonicalize Letta's history (the server may have persisted
722
+ // a partial response before the cancel landed). Anything else is
723
+ // a genuine transport failure.
724
+ if (controller.signal.aborted) {
725
+ endReason = "cancelled";
726
+ setStreamingAssistantText(null);
727
+ setStreamingReasoning(null);
728
+ setStreamingToolCalls([]);
729
+ accumulatedReasoning = "";
730
+ toolCallsByCallId.clear();
731
+ // Transition state out of `running` so the input re-enables
732
+ // immediately. The reducer normally drives this off the server's
733
+ // `(status, phase)` snapshot on `stream_end`, but the abort
734
+ // severed the connection before that event arrived.
735
+ setStreamState((s) => ({ ...s, status: "cancelled", phase: null }));
736
+ }
737
+ else {
738
+ const msg = err instanceof Error ? err.message : "Stream failed.";
739
+ setLastTurnError(msg);
740
+ setStreamingAssistantText(null);
741
+ setStreamingReasoning(null);
742
+ setStreamingToolCalls([]);
743
+ accumulatedReasoning = "";
744
+ toolCallsByCallId.clear();
745
+ if (err instanceof AIHubRequestError) {
746
+ onError?.({
747
+ code: err.code,
748
+ message: err.message,
749
+ ...(err.status !== undefined ? { status: err.status } : {}),
750
+ });
751
+ }
752
+ }
753
+ }
754
+ finally {
755
+ if (streamRef.current?.controller === controller) {
756
+ streamRef.current = null;
757
+ }
758
+ }
759
+ // Post-stream housekeeping — three branches, gated on the outcome
760
+ // recorded during the loop / catch:
761
+ //
762
+ // - `cancelled` — user pressed Stop (or switched sessions). Don't
763
+ // show an error; canonicalize so any partial
764
+ // response the agent finished generating before
765
+ // the cancel landed shows up properly.
766
+ // - `error` — server emitted an explicit error event during
767
+ // the turn. `lastTurnError` already carries the
768
+ // specific message; don't canonicalize (the
769
+ // server's history may be empty / partial and
770
+ // overwriting `messages` would erase the
771
+ // optimistic user bubble).
772
+ // - `done` / unknown — happy path OR "stream ended cleanly without
773
+ // producing assistant output" (e.g. an upstream
774
+ // agent-configuration problem returning an empty
775
+ // completion). Canonicalize on real output;
776
+ // otherwise surface a generic user-facing error
777
+ // pointing at the administrator.
778
+ if (endReason === "cancelled") {
779
+ try {
780
+ const canonical = await client.getSessionMessages(sessionId);
781
+ if (activeSessionIdRef.current === sessionId) {
782
+ const merged = mergeOptimisticAffordances(canonical, messagesRef.current);
783
+ setMessages(merged);
784
+ // Rollback detection: when Letta's authoritative cancel
785
+ // rolled back the in-progress step AND there were no
786
+ // previously-committed steps, the user's message disappears
787
+ // from history entirely (single-step cancel — empirically
788
+ // verified). Test by content (the rolled-back optimistic
789
+ // message had `content === text`; if no canonical user
790
+ // message carries that content, it was rolled back). The
791
+ // earlier `length <= baseline` check was right in the happy
792
+ // case but missed scenarios where the session already had
793
+ // sibling rows (e.g. memory_metadata) whose count made the
794
+ // comparison wobbly.
795
+ const trimmed = text.trim();
796
+ const userMessagePreserved = canonical.some((m) => m.role === "user" && m.content.trim() === trimmed);
797
+ if (!userMessagePreserved) {
798
+ setPendingDraft(text);
799
+ }
800
+ }
801
+ }
802
+ catch {
803
+ // Canonical refetch failed (network blip while the user was
804
+ // cancelling). Restore the typed text so they can retry; we'd
805
+ // rather over-restore than lose the input.
806
+ setPendingDraft(text);
807
+ }
808
+ setIsStoppingTurn(false);
809
+ }
810
+ else if (sawError) {
811
+ /* `lastTurnError` already set in the loop — do nothing */
812
+ setIsStoppingTurn(false);
813
+ }
814
+ else if (!sawAssistantOutput) {
815
+ // The stream finished but produced nothing renderable. Surface a
816
+ // user-facing error — kept generic on purpose (no upstream
817
+ // implementation names; users are pointed at the operator).
818
+ setLastTurnError("The agent didn't produce a response. Contact your administrator if this persists.");
819
+ setIsStoppingTurn(false);
820
+ }
821
+ else if (!controller.signal.aborted) {
822
+ try {
823
+ const canonical = await client.getSessionMessages(sessionId);
824
+ if (activeSessionIdRef.current === sessionId) {
825
+ // Merge optimistic reasoning + tool_calls into the canonical
826
+ // list: the streaming accumulators captured rich data the
827
+ // server's projector might not yet (Letta's cross-row
828
+ // aggregation has a small window where the
829
+ // tool_return_message hasn't been committed yet, or the
830
+ // operator is running an older server build without the
831
+ // §10.x projector). Preserves the affordances either way.
832
+ const merged = mergeOptimisticAffordances(canonical, messagesRef.current);
833
+ setMessages(merged);
834
+ }
835
+ }
836
+ catch {
837
+ /* keep optimistic state; user can refresh */
838
+ }
839
+ setIsStoppingTurn(false);
840
+ }
841
+ else {
842
+ setIsStoppingTurn(false);
843
+ }
844
+ };
845
+ const stop = async () => {
846
+ const active = streamRef.current;
847
+ if (!active)
848
+ return;
849
+ const { turnId } = active;
850
+ // The right design here is the OPPOSITE of "abort the iterator":
851
+ // we want the server's authoritative cancel (Letta's
852
+ // `agents.messages.cancel`, fired by `/chat/:id/cancel`) to wind
853
+ // the run down server-side, then deliver `stream_end{cancelled}`
854
+ // naturally on the SSE stream. The post-stream-end branch in
855
+ // `sendMessage` then canonicalizes against Letta's history — which
856
+ // correctly shows the rolled-back state (in-progress step gone;
857
+ // completed steps preserved).
858
+ //
859
+ // Aborting the iterator here would race the cancel: the connection
860
+ // closes before the server can deliver `stream_end{cancelled}`,
861
+ // and we'd lose the chance to capture the canonical end-state
862
+ // through the established post-stream path.
863
+ //
864
+ // The UX gap (network round-trip to fire the cancel + Letta
865
+ // processing time, typically ~300ms) is covered by
866
+ // `isStoppingTurn` — `<MessageInput>` shows "Stopping…" and keeps
867
+ // the input disabled. We don't need a hard-abort timeout for the
868
+ // common case; even a slow Letta cancel resolves in low seconds.
869
+ // If the cancel POST itself fails the runner's local AbortSignal
870
+ // (fired server-side as fallback) still produces `stream_end`.
871
+ setIsStoppingTurn(true);
872
+ if (turnId) {
873
+ try {
874
+ await client.cancelTurn(turnId);
875
+ }
876
+ catch {
877
+ // Ignored — the server's `/cancel` handler also fires the
878
+ // local registry abort, so the runner will emit `stream_end`
879
+ // even if our POST hit a transient failure.
880
+ }
881
+ }
882
+ };
883
+ const themeSwitcherEnabled = deployment?.theme_switcher_enabled ?? true;
884
+ const role = user?.role ?? null;
885
+ // Refresh the feature toggles on demand — used after the super-admin
886
+ // settings page saves a toggle change (Step 12), so the calling
887
+ // super_admin sees the effect without a page reload.
888
+ const refreshFeatureToggles = useCallback(async () => {
889
+ try {
890
+ const toggles = await client.getFeatureToggles();
891
+ setFeatureToggles(toggles);
892
+ }
893
+ catch (err) {
894
+ if (err instanceof AIHubRequestError) {
895
+ onError?.({
896
+ code: err.code,
897
+ message: err.message,
898
+ ...(err.status !== undefined ? { status: err.status } : {}),
899
+ });
900
+ }
901
+ }
902
+ }, [client, onError]);
903
+ const value = {
904
+ client,
905
+ user,
906
+ role,
907
+ deployment,
908
+ mustChangePassword,
909
+ hydrating,
910
+ login,
911
+ logout,
912
+ changePassword,
913
+ state: streamState,
914
+ effort,
915
+ setEffort,
916
+ theme,
917
+ setTheme,
918
+ themeSwitcherEnabled,
919
+ agents,
920
+ agentsLoading,
921
+ refreshAgents,
922
+ activeAgentId,
923
+ setActiveAgent,
924
+ sessions,
925
+ sessionsLoading,
926
+ refreshSessions,
927
+ includeArchivedSessions,
928
+ setIncludeArchivedSessions,
929
+ activeSessionId,
930
+ setActiveSession,
931
+ createSession,
932
+ patchSession,
933
+ messages,
934
+ messagesLoading,
935
+ streamingAssistantText,
936
+ streamingReasoning,
937
+ streamingToolCalls,
938
+ sendMessage,
939
+ stop,
940
+ isStoppingTurn,
941
+ pendingDraft,
942
+ consumePendingDraft: () => {
943
+ const draft = pendingDraft;
944
+ if (draft !== null)
945
+ setPendingDraft(null);
946
+ return draft;
947
+ },
948
+ lastTurnError,
949
+ featureToggles,
950
+ refreshFeatureToggles,
951
+ };
952
+ return (_jsx(aiHubContext.Provider, { value: value, children: _jsx("div", { className: "data-club-ai-hub", "data-theme": theme, children: children }) }));
953
+ }
954
+ //# sourceMappingURL=AIHubProvider.js.map