@tambo-ai/react 0.70.0 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/dist/v1/hooks/use-tambo-v1-component-state.d.ts +44 -0
  2. package/dist/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -0
  3. package/dist/v1/hooks/use-tambo-v1-component-state.js +134 -0
  4. package/dist/v1/hooks/use-tambo-v1-component-state.js.map +1 -0
  5. package/dist/v1/hooks/use-tambo-v1-component-state.test.d.ts +2 -0
  6. package/dist/v1/hooks/use-tambo-v1-component-state.test.d.ts.map +1 -0
  7. package/dist/v1/hooks/use-tambo-v1-component-state.test.js +292 -0
  8. package/dist/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -0
  9. package/dist/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  10. package/dist/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  11. package/dist/v1/hooks/use-tambo-v1-messages.js +54 -0
  12. package/dist/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  13. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  14. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  15. package/dist/v1/hooks/use-tambo-v1-messages.test.js +137 -0
  16. package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  17. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  18. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  19. package/dist/v1/hooks/use-tambo-v1-send-message.js +227 -0
  20. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  21. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  22. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  23. package/dist/v1/hooks/use-tambo-v1-send-message.test.js +827 -0
  24. package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  25. package/dist/v1/hooks/use-tambo-v1-thread-input.d.ts +62 -0
  26. package/dist/v1/hooks/use-tambo-v1-thread-input.d.ts.map +1 -0
  27. package/dist/v1/hooks/use-tambo-v1-thread-input.js +76 -0
  28. package/dist/v1/hooks/use-tambo-v1-thread-input.js.map +1 -0
  29. package/dist/v1/hooks/use-tambo-v1-thread-input.test.d.ts +2 -0
  30. package/dist/v1/hooks/use-tambo-v1-thread-input.test.d.ts.map +1 -0
  31. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js +168 -0
  32. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -0
  33. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  34. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  35. package/dist/v1/hooks/use-tambo-v1-thread-list.js +56 -0
  36. package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  37. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  38. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  39. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +98 -0
  40. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  41. package/dist/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  42. package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  43. package/dist/v1/hooks/use-tambo-v1-thread.js +49 -0
  44. package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  45. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  46. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  47. package/dist/v1/hooks/use-tambo-v1-thread.test.js +83 -0
  48. package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  49. package/dist/v1/hooks/use-tambo-v1.d.ts +107 -0
  50. package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  51. package/dist/v1/hooks/use-tambo-v1.js +87 -0
  52. package/dist/v1/hooks/use-tambo-v1.js.map +1 -0
  53. package/dist/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  54. package/dist/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  55. package/dist/v1/hooks/use-tambo-v1.test.js +150 -0
  56. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -0
  57. package/dist/v1/index.d.ts +65 -16
  58. package/dist/v1/index.d.ts.map +1 -1
  59. package/dist/v1/index.js +119 -26
  60. package/dist/v1/index.js.map +1 -1
  61. package/dist/v1/providers/tambo-v1-provider.d.ts +133 -0
  62. package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  63. package/dist/v1/providers/tambo-v1-provider.js +131 -0
  64. package/dist/v1/providers/tambo-v1-provider.js.map +1 -0
  65. package/dist/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  66. package/dist/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  67. package/dist/v1/providers/tambo-v1-provider.test.js +181 -0
  68. package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -0
  69. package/dist/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  70. package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  71. package/dist/v1/providers/tambo-v1-stream-context.js +230 -0
  72. package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -0
  73. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  74. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  75. package/dist/v1/providers/tambo-v1-stream-context.test.js +85 -0
  76. package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  77. package/dist/v1/types/component.d.ts +5 -2
  78. package/dist/v1/types/component.d.ts.map +1 -1
  79. package/dist/v1/types/component.js +5 -2
  80. package/dist/v1/types/component.js.map +1 -1
  81. package/dist/v1/types/event.d.ts +21 -12
  82. package/dist/v1/types/event.d.ts.map +1 -1
  83. package/dist/v1/types/event.js +46 -1
  84. package/dist/v1/types/event.js.map +1 -1
  85. package/dist/v1/types/event.test.d.ts +2 -0
  86. package/dist/v1/types/event.test.d.ts.map +1 -0
  87. package/dist/v1/types/event.test.js +70 -0
  88. package/dist/v1/types/event.test.js.map +1 -0
  89. package/dist/v1/types/message.d.ts +30 -9
  90. package/dist/v1/types/message.d.ts.map +1 -1
  91. package/dist/v1/types/message.js +1 -1
  92. package/dist/v1/types/message.js.map +1 -1
  93. package/dist/v1/types/thread.d.ts +1 -3
  94. package/dist/v1/types/thread.d.ts.map +1 -1
  95. package/dist/v1/types/thread.js +1 -1
  96. package/dist/v1/types/thread.js.map +1 -1
  97. package/dist/v1/utils/component-renderer.d.ts +89 -0
  98. package/dist/v1/utils/component-renderer.d.ts.map +1 -0
  99. package/dist/v1/utils/component-renderer.js +216 -0
  100. package/dist/v1/utils/component-renderer.js.map +1 -0
  101. package/dist/v1/utils/component-renderer.test.d.ts +2 -0
  102. package/dist/v1/utils/component-renderer.test.d.ts.map +1 -0
  103. package/dist/v1/utils/component-renderer.test.js +380 -0
  104. package/dist/v1/utils/component-renderer.test.js.map +1 -0
  105. package/dist/v1/utils/event-accumulator.d.ts +100 -0
  106. package/dist/v1/utils/event-accumulator.d.ts.map +1 -0
  107. package/dist/v1/utils/event-accumulator.js +735 -0
  108. package/dist/v1/utils/event-accumulator.js.map +1 -0
  109. package/dist/v1/utils/event-accumulator.test.d.ts +2 -0
  110. package/dist/v1/utils/event-accumulator.test.d.ts.map +1 -0
  111. package/dist/v1/utils/event-accumulator.test.js +1205 -0
  112. package/dist/v1/utils/event-accumulator.test.js.map +1 -0
  113. package/dist/v1/utils/json-patch.d.ts +18 -0
  114. package/dist/v1/utils/json-patch.d.ts.map +1 -0
  115. package/dist/v1/utils/json-patch.js +35 -0
  116. package/dist/v1/utils/json-patch.js.map +1 -0
  117. package/dist/v1/utils/json-patch.test.d.ts +2 -0
  118. package/dist/v1/utils/json-patch.test.d.ts.map +1 -0
  119. package/dist/v1/utils/json-patch.test.js +28 -0
  120. package/dist/v1/utils/json-patch.test.js.map +1 -0
  121. package/dist/v1/utils/registry-conversion.d.ts +53 -0
  122. package/dist/v1/utils/registry-conversion.d.ts.map +1 -0
  123. package/dist/v1/utils/registry-conversion.js +114 -0
  124. package/dist/v1/utils/registry-conversion.js.map +1 -0
  125. package/dist/v1/utils/registry-conversion.test.d.ts +2 -0
  126. package/dist/v1/utils/registry-conversion.test.d.ts.map +1 -0
  127. package/dist/v1/utils/registry-conversion.test.js +179 -0
  128. package/dist/v1/utils/registry-conversion.test.js.map +1 -0
  129. package/dist/v1/utils/stream-handler.d.ts +45 -0
  130. package/dist/v1/utils/stream-handler.d.ts.map +1 -0
  131. package/dist/v1/utils/stream-handler.js +47 -0
  132. package/dist/v1/utils/stream-handler.js.map +1 -0
  133. package/dist/v1/utils/stream-handler.test.d.ts +2 -0
  134. package/dist/v1/utils/stream-handler.test.d.ts.map +1 -0
  135. package/dist/v1/utils/stream-handler.test.js +74 -0
  136. package/dist/v1/utils/stream-handler.test.js.map +1 -0
  137. package/dist/v1/utils/tool-call-tracker.d.ts +41 -0
  138. package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -0
  139. package/dist/v1/utils/tool-call-tracker.js +90 -0
  140. package/dist/v1/utils/tool-call-tracker.js.map +1 -0
  141. package/dist/v1/utils/tool-executor.d.ts +33 -0
  142. package/dist/v1/utils/tool-executor.d.ts.map +1 -0
  143. package/dist/v1/utils/tool-executor.js +103 -0
  144. package/dist/v1/utils/tool-executor.js.map +1 -0
  145. package/dist/v1/utils/tool-executor.test.d.ts +2 -0
  146. package/dist/v1/utils/tool-executor.test.d.ts.map +1 -0
  147. package/dist/v1/utils/tool-executor.test.js +222 -0
  148. package/dist/v1/utils/tool-executor.test.js.map +1 -0
  149. package/esm/v1/hooks/use-tambo-v1-component-state.d.ts +44 -0
  150. package/esm/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -0
  151. package/esm/v1/hooks/use-tambo-v1-component-state.js +131 -0
  152. package/esm/v1/hooks/use-tambo-v1-component-state.js.map +1 -0
  153. package/esm/v1/hooks/use-tambo-v1-component-state.test.d.ts +2 -0
  154. package/esm/v1/hooks/use-tambo-v1-component-state.test.d.ts.map +1 -0
  155. package/esm/v1/hooks/use-tambo-v1-component-state.test.js +290 -0
  156. package/esm/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -0
  157. package/esm/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  158. package/esm/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  159. package/esm/v1/hooks/use-tambo-v1-messages.js +51 -0
  160. package/esm/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  161. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  162. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  163. package/esm/v1/hooks/use-tambo-v1-messages.test.js +132 -0
  164. package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  165. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  166. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  167. package/esm/v1/hooks/use-tambo-v1-send-message.js +223 -0
  168. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  169. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  170. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  171. package/esm/v1/hooks/use-tambo-v1-send-message.test.js +822 -0
  172. package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  173. package/esm/v1/hooks/use-tambo-v1-thread-input.d.ts +62 -0
  174. package/esm/v1/hooks/use-tambo-v1-thread-input.d.ts.map +1 -0
  175. package/esm/v1/hooks/use-tambo-v1-thread-input.js +73 -0
  176. package/esm/v1/hooks/use-tambo-v1-thread-input.js.map +1 -0
  177. package/esm/v1/hooks/use-tambo-v1-thread-input.test.d.ts +2 -0
  178. package/esm/v1/hooks/use-tambo-v1-thread-input.test.d.ts.map +1 -0
  179. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js +166 -0
  180. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -0
  181. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  182. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  183. package/esm/v1/hooks/use-tambo-v1-thread-list.js +53 -0
  184. package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  185. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  186. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  187. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +93 -0
  188. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  189. package/esm/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  190. package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  191. package/esm/v1/hooks/use-tambo-v1-thread.js +46 -0
  192. package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  193. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  194. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  195. package/esm/v1/hooks/use-tambo-v1-thread.test.js +78 -0
  196. package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  197. package/esm/v1/hooks/use-tambo-v1.d.ts +107 -0
  198. package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  199. package/esm/v1/hooks/use-tambo-v1.js +84 -0
  200. package/esm/v1/hooks/use-tambo-v1.js.map +1 -0
  201. package/esm/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  202. package/esm/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  203. package/esm/v1/hooks/use-tambo-v1.test.js +145 -0
  204. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -0
  205. package/esm/v1/index.d.ts +65 -16
  206. package/esm/v1/index.d.ts.map +1 -1
  207. package/esm/v1/index.js +83 -27
  208. package/esm/v1/index.js.map +1 -1
  209. package/esm/v1/providers/tambo-v1-provider.d.ts +133 -0
  210. package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  211. package/esm/v1/providers/tambo-v1-provider.js +94 -0
  212. package/esm/v1/providers/tambo-v1-provider.js.map +1 -0
  213. package/esm/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  214. package/esm/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  215. package/esm/v1/providers/tambo-v1-provider.test.js +176 -0
  216. package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -0
  217. package/esm/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  218. package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  219. package/esm/v1/providers/tambo-v1-stream-context.js +191 -0
  220. package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -0
  221. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  222. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  223. package/esm/v1/providers/tambo-v1-stream-context.test.js +80 -0
  224. package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  225. package/esm/v1/types/component.d.ts +5 -2
  226. package/esm/v1/types/component.d.ts.map +1 -1
  227. package/esm/v1/types/component.js +5 -2
  228. package/esm/v1/types/component.js.map +1 -1
  229. package/esm/v1/types/event.d.ts +21 -12
  230. package/esm/v1/types/event.d.ts.map +1 -1
  231. package/esm/v1/types/event.js +44 -2
  232. package/esm/v1/types/event.js.map +1 -1
  233. package/esm/v1/types/event.test.d.ts +2 -0
  234. package/esm/v1/types/event.test.d.ts.map +1 -0
  235. package/esm/v1/types/event.test.js +68 -0
  236. package/esm/v1/types/event.test.js.map +1 -0
  237. package/esm/v1/types/message.d.ts +30 -9
  238. package/esm/v1/types/message.d.ts.map +1 -1
  239. package/esm/v1/types/message.js +1 -1
  240. package/esm/v1/types/message.js.map +1 -1
  241. package/esm/v1/types/thread.d.ts +1 -3
  242. package/esm/v1/types/thread.d.ts.map +1 -1
  243. package/esm/v1/types/thread.js +1 -1
  244. package/esm/v1/types/thread.js.map +1 -1
  245. package/esm/v1/utils/component-renderer.d.ts +89 -0
  246. package/esm/v1/utils/component-renderer.d.ts.map +1 -0
  247. package/esm/v1/utils/component-renderer.js +175 -0
  248. package/esm/v1/utils/component-renderer.js.map +1 -0
  249. package/esm/v1/utils/component-renderer.test.d.ts +2 -0
  250. package/esm/v1/utils/component-renderer.test.d.ts.map +1 -0
  251. package/esm/v1/utils/component-renderer.test.js +375 -0
  252. package/esm/v1/utils/component-renderer.test.js.map +1 -0
  253. package/esm/v1/utils/event-accumulator.d.ts +100 -0
  254. package/esm/v1/utils/event-accumulator.d.ts.map +1 -0
  255. package/esm/v1/utils/event-accumulator.js +728 -0
  256. package/esm/v1/utils/event-accumulator.js.map +1 -0
  257. package/esm/v1/utils/event-accumulator.test.d.ts +2 -0
  258. package/esm/v1/utils/event-accumulator.test.d.ts.map +1 -0
  259. package/esm/v1/utils/event-accumulator.test.js +1203 -0
  260. package/esm/v1/utils/event-accumulator.test.js.map +1 -0
  261. package/esm/v1/utils/json-patch.d.ts +18 -0
  262. package/esm/v1/utils/json-patch.d.ts.map +1 -0
  263. package/esm/v1/utils/json-patch.js +32 -0
  264. package/esm/v1/utils/json-patch.js.map +1 -0
  265. package/esm/v1/utils/json-patch.test.d.ts +2 -0
  266. package/esm/v1/utils/json-patch.test.d.ts.map +1 -0
  267. package/esm/v1/utils/json-patch.test.js +26 -0
  268. package/esm/v1/utils/json-patch.test.js.map +1 -0
  269. package/esm/v1/utils/registry-conversion.d.ts +53 -0
  270. package/esm/v1/utils/registry-conversion.d.ts.map +1 -0
  271. package/esm/v1/utils/registry-conversion.js +108 -0
  272. package/esm/v1/utils/registry-conversion.js.map +1 -0
  273. package/esm/v1/utils/registry-conversion.test.d.ts +2 -0
  274. package/esm/v1/utils/registry-conversion.test.d.ts.map +1 -0
  275. package/esm/v1/utils/registry-conversion.test.js +177 -0
  276. package/esm/v1/utils/registry-conversion.test.js.map +1 -0
  277. package/esm/v1/utils/stream-handler.d.ts +45 -0
  278. package/esm/v1/utils/stream-handler.d.ts.map +1 -0
  279. package/esm/v1/utils/stream-handler.js +44 -0
  280. package/esm/v1/utils/stream-handler.js.map +1 -0
  281. package/esm/v1/utils/stream-handler.test.d.ts +2 -0
  282. package/esm/v1/utils/stream-handler.test.d.ts.map +1 -0
  283. package/esm/v1/utils/stream-handler.test.js +72 -0
  284. package/esm/v1/utils/stream-handler.test.js.map +1 -0
  285. package/esm/v1/utils/tool-call-tracker.d.ts +41 -0
  286. package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -0
  287. package/esm/v1/utils/tool-call-tracker.js +86 -0
  288. package/esm/v1/utils/tool-call-tracker.js.map +1 -0
  289. package/esm/v1/utils/tool-executor.d.ts +33 -0
  290. package/esm/v1/utils/tool-executor.d.ts.map +1 -0
  291. package/esm/v1/utils/tool-executor.js +99 -0
  292. package/esm/v1/utils/tool-executor.js.map +1 -0
  293. package/esm/v1/utils/tool-executor.test.d.ts +2 -0
  294. package/esm/v1/utils/tool-executor.test.d.ts.map +1 -0
  295. package/esm/v1/utils/tool-executor.test.js +220 -0
  296. package/esm/v1/utils/tool-executor.test.js.map +1 -0
  297. package/package.json +7 -6
  298. package/dist/v1/types/tool.d.ts +0 -52
  299. package/dist/v1/types/tool.d.ts.map +0 -1
  300. package/dist/v1/types/tool.js +0 -11
  301. package/dist/v1/types/tool.js.map +0 -1
  302. package/esm/v1/types/tool.d.ts +0 -52
  303. package/esm/v1/types/tool.d.ts.map +0 -1
  304. package/esm/v1/types/tool.js +0 -10
  305. package/esm/v1/types/tool.js.map +0 -1
@@ -0,0 +1,1203 @@
1
+ import { EventType, } from "@ag-ui/core";
2
+ import { createInitialState, createInitialThreadState, streamReducer, } from "./event-accumulator";
3
+ /**
4
+ * Helper to extract a ToolUseContent from a message content array.
5
+ * @param content - Content array from a message
6
+ * @param index - Index of the content item
7
+ * @returns The content as ToolUseContent
8
+ */
9
+ function asToolUseContent(content, index) {
10
+ return content[index];
11
+ }
12
+ /**
13
+ * Helper to extract a V1ComponentContent from a message content array.
14
+ * @param content - Content array from a message
15
+ * @param index - Index of the content item
16
+ * @returns The content as V1ComponentContent
17
+ */
18
+ function asComponentContent(content, index) {
19
+ return content[index];
20
+ }
21
+ // Helper to create a base thread state for testing
22
+ function createTestThreadState(threadId) {
23
+ return {
24
+ ...createInitialThreadState(threadId),
25
+ thread: {
26
+ ...createInitialThreadState(threadId).thread,
27
+ // Use fixed timestamps for snapshot stability
28
+ createdAt: "2024-01-01T00:00:00.000Z",
29
+ updatedAt: "2024-01-01T00:00:00.000Z",
30
+ },
31
+ };
32
+ }
33
+ // Helper to create stream state with a thread
34
+ function createTestStreamState(threadId) {
35
+ return {
36
+ threadMap: {
37
+ [threadId]: createTestThreadState(threadId),
38
+ },
39
+ currentThreadId: threadId,
40
+ };
41
+ }
42
+ describe("createInitialThreadState", () => {
43
+ it("creates thread state with correct structure", () => {
44
+ const state = createInitialThreadState("thread_123");
45
+ expect(state.thread.id).toBe("thread_123");
46
+ expect(state.thread.messages).toEqual([]);
47
+ expect(state.thread.status).toBe("idle");
48
+ expect(state.streaming.status).toBe("idle");
49
+ expect(state.accumulatingToolArgs).toBeInstanceOf(Map);
50
+ expect(state.accumulatingToolArgs.size).toBe(0);
51
+ });
52
+ });
53
+ describe("createInitialState", () => {
54
+ it("creates empty stream state", () => {
55
+ const state = createInitialState();
56
+ expect(state.threadMap).toEqual({});
57
+ expect(state.currentThreadId).toBeNull();
58
+ });
59
+ });
60
+ describe("streamReducer", () => {
61
+ // Mock console.warn for tests that check logging
62
+ let consoleWarnSpy;
63
+ beforeEach(() => {
64
+ consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
65
+ });
66
+ afterEach(() => {
67
+ consoleWarnSpy.mockRestore();
68
+ });
69
+ describe("unknown thread handling", () => {
70
+ it("auto-initializes unknown thread when receiving events", () => {
71
+ const state = createInitialState();
72
+ const event = {
73
+ type: EventType.RUN_STARTED,
74
+ runId: "run_1",
75
+ threadId: "unknown_thread",
76
+ };
77
+ const result = streamReducer(state, {
78
+ type: "EVENT",
79
+ event,
80
+ threadId: "unknown_thread",
81
+ });
82
+ // Should auto-initialize the thread rather than dropping the event
83
+ expect(result.threadMap.unknown_thread).toBeDefined();
84
+ expect(result.threadMap.unknown_thread.thread.id).toBe("unknown_thread");
85
+ expect(result.threadMap.unknown_thread.streaming.status).toBe("streaming");
86
+ expect(result.threadMap.unknown_thread.streaming.runId).toBe("run_1");
87
+ // No warning should be logged for auto-initialization
88
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
89
+ });
90
+ });
91
+ describe("unsupported event types", () => {
92
+ it("logs warning for unsupported AG-UI events", () => {
93
+ const state = createTestStreamState("thread_1");
94
+ const event = {
95
+ type: EventType.STATE_SNAPSHOT,
96
+ snapshot: {},
97
+ };
98
+ const result = streamReducer(state, {
99
+ type: "EVENT",
100
+ event,
101
+ threadId: "thread_1",
102
+ });
103
+ expect(result).toBe(state);
104
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("unsupported event type"));
105
+ });
106
+ it("throws for completely unknown event types (fail-fast)", () => {
107
+ const state = createTestStreamState("thread_1");
108
+ // Create an event with an unknown type (not in EventType enum)
109
+ // This tests fail-fast behavior when SDK returns unexpected event types
110
+ const event = {
111
+ type: "TOTALLY_UNKNOWN_EVENT_TYPE",
112
+ };
113
+ expect(() => streamReducer(state, {
114
+ type: "EVENT",
115
+ event: event,
116
+ threadId: "thread_1",
117
+ })).toThrow("Unreachable case");
118
+ });
119
+ it("logs warning for unknown custom event names", () => {
120
+ const state = createTestStreamState("thread_1");
121
+ const event = {
122
+ type: EventType.CUSTOM,
123
+ name: "unknown.custom.event",
124
+ value: {},
125
+ };
126
+ const result = streamReducer(state, {
127
+ type: "EVENT",
128
+ event,
129
+ threadId: "thread_1",
130
+ });
131
+ expect(result.threadMap.thread_1).toBe(state.threadMap.thread_1);
132
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Unknown custom event name"));
133
+ });
134
+ });
135
+ describe("RUN_STARTED event", () => {
136
+ it("updates thread status to streaming", () => {
137
+ const state = createTestStreamState("thread_1");
138
+ const event = {
139
+ type: EventType.RUN_STARTED,
140
+ runId: "run_123",
141
+ threadId: "thread_1",
142
+ timestamp: 1704067200000,
143
+ };
144
+ const result = streamReducer(state, {
145
+ type: "EVENT",
146
+ event,
147
+ threadId: "thread_1",
148
+ });
149
+ expect(result.threadMap.thread_1.thread.status).toBe("streaming");
150
+ expect(result.threadMap.thread_1.streaming.status).toBe("streaming");
151
+ expect(result.threadMap.thread_1.streaming.runId).toBe("run_123");
152
+ });
153
+ it("uses provided timestamp for startTime", () => {
154
+ const state = createTestStreamState("thread_1");
155
+ const event = {
156
+ type: EventType.RUN_STARTED,
157
+ runId: "run_123",
158
+ threadId: "thread_1",
159
+ timestamp: 1704067200000,
160
+ };
161
+ const result = streamReducer(state, {
162
+ type: "EVENT",
163
+ event,
164
+ threadId: "thread_1",
165
+ });
166
+ expect(result.threadMap.thread_1.streaming.startTime).toBe(1704067200000);
167
+ });
168
+ });
169
+ describe("RUN_FINISHED event", () => {
170
+ it("updates thread status to complete", () => {
171
+ const state = createTestStreamState("thread_1");
172
+ state.threadMap.thread_1.thread.status = "streaming";
173
+ state.threadMap.thread_1.streaming.status = "streaming";
174
+ const event = {
175
+ type: EventType.RUN_FINISHED,
176
+ runId: "run_123",
177
+ threadId: "thread_1",
178
+ };
179
+ const result = streamReducer(state, {
180
+ type: "EVENT",
181
+ event,
182
+ threadId: "thread_1",
183
+ });
184
+ expect(result.threadMap.thread_1.thread.status).toBe("complete");
185
+ expect(result.threadMap.thread_1.streaming.status).toBe("complete");
186
+ });
187
+ });
188
+ describe("RUN_ERROR event", () => {
189
+ it("updates thread status to error with details", () => {
190
+ const state = createTestStreamState("thread_1");
191
+ const event = {
192
+ type: EventType.RUN_ERROR,
193
+ message: "Something went wrong",
194
+ code: "ERR_001",
195
+ };
196
+ const result = streamReducer(state, {
197
+ type: "EVENT",
198
+ event,
199
+ threadId: "thread_1",
200
+ });
201
+ expect(result.threadMap.thread_1.thread.status).toBe("error");
202
+ expect(result.threadMap.thread_1.streaming.status).toBe("error");
203
+ expect(result.threadMap.thread_1.streaming.error).toEqual({
204
+ message: "Something went wrong",
205
+ code: "ERR_001",
206
+ });
207
+ });
208
+ });
209
+ describe("TEXT_MESSAGE_START event", () => {
210
+ it("creates new message in thread", () => {
211
+ const state = createTestStreamState("thread_1");
212
+ const event = {
213
+ type: EventType.TEXT_MESSAGE_START,
214
+ messageId: "msg_1",
215
+ role: "assistant",
216
+ };
217
+ const result = streamReducer(state, {
218
+ type: "EVENT",
219
+ event,
220
+ threadId: "thread_1",
221
+ });
222
+ const messages = result.threadMap.thread_1.thread.messages;
223
+ expect(messages).toHaveLength(1);
224
+ expect(messages[0].id).toBe("msg_1");
225
+ expect(messages[0].role).toBe("assistant");
226
+ expect(messages[0].content).toEqual([]);
227
+ });
228
+ it("creates user message when role is user", () => {
229
+ const state = createTestStreamState("thread_1");
230
+ const event = {
231
+ type: EventType.TEXT_MESSAGE_START,
232
+ messageId: "msg_1",
233
+ role: "user",
234
+ };
235
+ const result = streamReducer(state, {
236
+ type: "EVENT",
237
+ event,
238
+ threadId: "thread_1",
239
+ });
240
+ const messages = result.threadMap.thread_1.thread.messages;
241
+ expect(messages).toHaveLength(1);
242
+ expect(messages[0].role).toBe("user");
243
+ });
244
+ });
245
+ describe("TEXT_MESSAGE_CONTENT event", () => {
246
+ it("appends text content to message", () => {
247
+ const state = createTestStreamState("thread_1");
248
+ // Add a message first
249
+ state.threadMap.thread_1.thread.messages = [
250
+ {
251
+ id: "msg_1",
252
+ role: "assistant",
253
+ content: [],
254
+ createdAt: "2024-01-01T00:00:00.000Z",
255
+ },
256
+ ];
257
+ const event = {
258
+ type: EventType.TEXT_MESSAGE_CONTENT,
259
+ messageId: "msg_1",
260
+ delta: "Hello ",
261
+ };
262
+ const result = streamReducer(state, {
263
+ type: "EVENT",
264
+ event,
265
+ threadId: "thread_1",
266
+ });
267
+ const content = result.threadMap.thread_1.thread.messages[0].content;
268
+ expect(content).toHaveLength(1);
269
+ expect(content[0]).toEqual({ type: "text", text: "Hello " });
270
+ });
271
+ it("creates new text block after non-text content", () => {
272
+ const state = createTestStreamState("thread_1");
273
+ state.threadMap.thread_1.thread.messages = [
274
+ {
275
+ id: "msg_1",
276
+ role: "assistant",
277
+ content: [
278
+ { type: "tool_use", id: "tool_1", name: "test", input: {} },
279
+ ],
280
+ createdAt: "2024-01-01T00:00:00.000Z",
281
+ },
282
+ ];
283
+ const event = {
284
+ type: EventType.TEXT_MESSAGE_CONTENT,
285
+ messageId: "msg_1",
286
+ delta: "After tool call",
287
+ };
288
+ const result = streamReducer(state, {
289
+ type: "EVENT",
290
+ event,
291
+ threadId: "thread_1",
292
+ });
293
+ const content = result.threadMap.thread_1.thread.messages[0].content;
294
+ expect(content).toHaveLength(2);
295
+ expect(content[0]).toEqual({
296
+ type: "tool_use",
297
+ id: "tool_1",
298
+ name: "test",
299
+ input: {},
300
+ });
301
+ expect(content[1]).toEqual({ type: "text", text: "After tool call" });
302
+ });
303
+ it("accumulates text content deltas", () => {
304
+ const state = createTestStreamState("thread_1");
305
+ state.threadMap.thread_1.thread.messages = [
306
+ {
307
+ id: "msg_1",
308
+ role: "assistant",
309
+ content: [{ type: "text", text: "Hello " }],
310
+ createdAt: "2024-01-01T00:00:00.000Z",
311
+ },
312
+ ];
313
+ const event = {
314
+ type: EventType.TEXT_MESSAGE_CONTENT,
315
+ messageId: "msg_1",
316
+ delta: "world!",
317
+ };
318
+ const result = streamReducer(state, {
319
+ type: "EVENT",
320
+ event,
321
+ threadId: "thread_1",
322
+ });
323
+ const content = result.threadMap.thread_1.thread.messages[0].content;
324
+ expect(content).toHaveLength(1);
325
+ expect(content[0]).toEqual({ type: "text", text: "Hello world!" });
326
+ });
327
+ it("throws when message not found", () => {
328
+ const state = createTestStreamState("thread_1");
329
+ state.threadMap.thread_1.thread.messages = [
330
+ {
331
+ id: "msg_1",
332
+ role: "assistant",
333
+ content: [],
334
+ createdAt: "2024-01-01T00:00:00.000Z",
335
+ },
336
+ ];
337
+ const event = {
338
+ type: EventType.TEXT_MESSAGE_CONTENT,
339
+ messageId: "unknown_msg",
340
+ delta: "Hello",
341
+ };
342
+ expect(() => {
343
+ streamReducer(state, {
344
+ type: "EVENT",
345
+ event,
346
+ threadId: "thread_1",
347
+ });
348
+ }).toThrow("Message unknown_msg not found");
349
+ });
350
+ });
351
+ describe("TEXT_MESSAGE_END event", () => {
352
+ it("throws when messageId doesn't match active message", () => {
353
+ const state = createTestStreamState("thread_1");
354
+ // Set up an active message in streaming state
355
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
356
+ state.threadMap.thread_1.thread.messages = [
357
+ {
358
+ id: "msg_1",
359
+ role: "assistant",
360
+ content: [{ type: "text", text: "Hello" }],
361
+ createdAt: "2024-01-01T00:00:00.000Z",
362
+ },
363
+ ];
364
+ const event = {
365
+ type: EventType.TEXT_MESSAGE_END,
366
+ messageId: "wrong_msg_id",
367
+ };
368
+ expect(() => {
369
+ streamReducer(state, {
370
+ type: "EVENT",
371
+ event,
372
+ threadId: "thread_1",
373
+ });
374
+ }).toThrow("TEXT_MESSAGE_END messageId mismatch");
375
+ });
376
+ it("clears active messageId on success", () => {
377
+ const state = createTestStreamState("thread_1");
378
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
379
+ state.threadMap.thread_1.thread.messages = [
380
+ {
381
+ id: "msg_1",
382
+ role: "assistant",
383
+ content: [{ type: "text", text: "Hello" }],
384
+ createdAt: "2024-01-01T00:00:00.000Z",
385
+ },
386
+ ];
387
+ const event = {
388
+ type: EventType.TEXT_MESSAGE_END,
389
+ messageId: "msg_1",
390
+ };
391
+ const result = streamReducer(state, {
392
+ type: "EVENT",
393
+ event,
394
+ threadId: "thread_1",
395
+ });
396
+ expect(result.threadMap.thread_1.streaming.messageId).toBeUndefined();
397
+ });
398
+ });
399
+ describe("TOOL_CALL_START event", () => {
400
+ it("adds tool_use content block to message", () => {
401
+ const state = createTestStreamState("thread_1");
402
+ state.threadMap.thread_1.thread.messages = [
403
+ {
404
+ id: "msg_1",
405
+ role: "assistant",
406
+ content: [],
407
+ createdAt: "2024-01-01T00:00:00.000Z",
408
+ },
409
+ ];
410
+ const event = {
411
+ type: EventType.TOOL_CALL_START,
412
+ toolCallId: "tool_1",
413
+ toolCallName: "get_weather",
414
+ parentMessageId: "msg_1",
415
+ };
416
+ const result = streamReducer(state, {
417
+ type: "EVENT",
418
+ event,
419
+ threadId: "thread_1",
420
+ });
421
+ const content = result.threadMap.thread_1.thread.messages[0].content;
422
+ expect(content).toHaveLength(1);
423
+ expect(content[0]).toEqual({
424
+ type: "tool_use",
425
+ id: "tool_1",
426
+ name: "get_weather",
427
+ input: {},
428
+ });
429
+ });
430
+ });
431
+ describe("TOOL_CALL_START event", () => {
432
+ it("uses last message when no parentMessageId provided", () => {
433
+ const state = createTestStreamState("thread_1");
434
+ state.threadMap.thread_1.thread.messages = [
435
+ {
436
+ id: "msg_1",
437
+ role: "assistant",
438
+ content: [],
439
+ createdAt: "2024-01-01T00:00:00.000Z",
440
+ },
441
+ ];
442
+ const event = {
443
+ type: EventType.TOOL_CALL_START,
444
+ toolCallId: "tool_1",
445
+ toolCallName: "get_weather",
446
+ // No parentMessageId - should use last message
447
+ };
448
+ const result = streamReducer(state, {
449
+ type: "EVENT",
450
+ event,
451
+ threadId: "thread_1",
452
+ });
453
+ const content = result.threadMap.thread_1.thread.messages[0].content;
454
+ expect(content).toHaveLength(1);
455
+ expect(content[0]).toEqual({
456
+ type: "tool_use",
457
+ id: "tool_1",
458
+ name: "get_weather",
459
+ input: {},
460
+ });
461
+ });
462
+ it("throws when parentMessageId message not found", () => {
463
+ const state = createTestStreamState("thread_1");
464
+ state.threadMap.thread_1.thread.messages = [
465
+ {
466
+ id: "msg_1",
467
+ role: "assistant",
468
+ content: [],
469
+ createdAt: "2024-01-01T00:00:00.000Z",
470
+ },
471
+ ];
472
+ const event = {
473
+ type: EventType.TOOL_CALL_START,
474
+ toolCallId: "tool_1",
475
+ toolCallName: "get_weather",
476
+ parentMessageId: "unknown_msg",
477
+ };
478
+ expect(() => {
479
+ streamReducer(state, {
480
+ type: "EVENT",
481
+ event,
482
+ threadId: "thread_1",
483
+ });
484
+ }).toThrow("Message unknown_msg not found for TOOL_CALL_START event");
485
+ });
486
+ it("throws when no messages exist", () => {
487
+ const state = createTestStreamState("thread_1");
488
+ state.threadMap.thread_1.thread.messages = [];
489
+ const event = {
490
+ type: EventType.TOOL_CALL_START,
491
+ toolCallId: "tool_1",
492
+ toolCallName: "get_weather",
493
+ // No parentMessageId, no messages
494
+ };
495
+ expect(() => {
496
+ streamReducer(state, {
497
+ type: "EVENT",
498
+ event,
499
+ threadId: "thread_1",
500
+ });
501
+ }).toThrow("No messages exist for TOOL_CALL_START event");
502
+ });
503
+ });
504
+ describe("TOOL_CALL_ARGS and TOOL_CALL_END events", () => {
505
+ it("handles TOOL_CALL_END with no accumulated args", () => {
506
+ const state = createTestStreamState("thread_1");
507
+ state.threadMap.thread_1.thread.messages = [
508
+ {
509
+ id: "msg_1",
510
+ role: "assistant",
511
+ content: [
512
+ { type: "tool_use", id: "tool_1", name: "test", input: {} },
513
+ ],
514
+ createdAt: "2024-01-01T00:00:00.000Z",
515
+ },
516
+ ];
517
+ // Send TOOL_CALL_END without any TOOL_CALL_ARGS first
518
+ const endEvent = {
519
+ type: EventType.TOOL_CALL_END,
520
+ toolCallId: "tool_1",
521
+ };
522
+ const result = streamReducer(state, {
523
+ type: "EVENT",
524
+ event: endEvent,
525
+ threadId: "thread_1",
526
+ });
527
+ // Should return unchanged state since no args were accumulated
528
+ expect(result.threadMap.thread_1).toBe(state.threadMap.thread_1);
529
+ });
530
+ it("accumulates and parses tool arguments", () => {
531
+ const state = createTestStreamState("thread_1");
532
+ state.threadMap.thread_1.thread.messages = [
533
+ {
534
+ id: "msg_1",
535
+ role: "assistant",
536
+ content: [
537
+ { type: "tool_use", id: "tool_1", name: "test", input: {} },
538
+ ],
539
+ createdAt: "2024-01-01T00:00:00.000Z",
540
+ },
541
+ ];
542
+ // Send TOOL_CALL_ARGS
543
+ const argsEvent1 = {
544
+ type: EventType.TOOL_CALL_ARGS,
545
+ toolCallId: "tool_1",
546
+ delta: '{"city":',
547
+ };
548
+ let result = streamReducer(state, {
549
+ type: "EVENT",
550
+ event: argsEvent1,
551
+ threadId: "thread_1",
552
+ });
553
+ const argsEvent2 = {
554
+ type: EventType.TOOL_CALL_ARGS,
555
+ toolCallId: "tool_1",
556
+ delta: '"NYC"}',
557
+ };
558
+ result = streamReducer(result, {
559
+ type: "EVENT",
560
+ event: argsEvent2,
561
+ threadId: "thread_1",
562
+ });
563
+ // Send TOOL_CALL_END
564
+ const endEvent = {
565
+ type: EventType.TOOL_CALL_END,
566
+ toolCallId: "tool_1",
567
+ };
568
+ result = streamReducer(result, {
569
+ type: "EVENT",
570
+ event: endEvent,
571
+ threadId: "thread_1",
572
+ });
573
+ const toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
574
+ expect(toolContent.input).toEqual({ city: "NYC" });
575
+ });
576
+ it("throws when tool arguments JSON is invalid", () => {
577
+ const state = createTestStreamState("thread_1");
578
+ state.threadMap.thread_1.thread.messages = [
579
+ {
580
+ id: "msg_1",
581
+ role: "assistant",
582
+ content: [
583
+ { type: "tool_use", id: "tool_1", name: "test", input: {} },
584
+ ],
585
+ createdAt: "2024-01-01T00:00:00.000Z",
586
+ },
587
+ ];
588
+ // Send invalid JSON via TOOL_CALL_ARGS
589
+ const argsEvent = {
590
+ type: EventType.TOOL_CALL_ARGS,
591
+ toolCallId: "tool_1",
592
+ delta: "{invalid json",
593
+ };
594
+ const result = streamReducer(state, {
595
+ type: "EVENT",
596
+ event: argsEvent,
597
+ threadId: "thread_1",
598
+ });
599
+ // Send TOOL_CALL_END - should throw when parsing
600
+ const endEvent = {
601
+ type: EventType.TOOL_CALL_END,
602
+ toolCallId: "tool_1",
603
+ };
604
+ expect(() => {
605
+ streamReducer(result, {
606
+ type: "EVENT",
607
+ event: endEvent,
608
+ threadId: "thread_1",
609
+ });
610
+ }).toThrow("Failed to parse tool call arguments");
611
+ });
612
+ });
613
+ describe("custom component events", () => {
614
+ it("handles tambo.component.start event", () => {
615
+ const state = createTestStreamState("thread_1");
616
+ state.threadMap.thread_1.thread.messages = [
617
+ {
618
+ id: "msg_1",
619
+ role: "assistant",
620
+ content: [],
621
+ createdAt: "2024-01-01T00:00:00.000Z",
622
+ },
623
+ ];
624
+ const event = {
625
+ type: EventType.CUSTOM,
626
+ name: "tambo.component.start",
627
+ value: {
628
+ messageId: "msg_1",
629
+ componentId: "comp_1",
630
+ componentName: "WeatherCard",
631
+ },
632
+ };
633
+ const result = streamReducer(state, {
634
+ type: "EVENT",
635
+ event,
636
+ threadId: "thread_1",
637
+ });
638
+ const content = result.threadMap.thread_1.thread.messages[0].content;
639
+ expect(content).toHaveLength(1);
640
+ expect(content[0]).toEqual({
641
+ type: "component",
642
+ id: "comp_1",
643
+ name: "WeatherCard",
644
+ props: {},
645
+ streamingState: "started",
646
+ });
647
+ });
648
+ it("handles tambo.component.props_delta event", () => {
649
+ const state = createTestStreamState("thread_1");
650
+ state.threadMap.thread_1.thread.messages = [
651
+ {
652
+ id: "msg_1",
653
+ role: "assistant",
654
+ content: [
655
+ {
656
+ type: "component",
657
+ id: "comp_1",
658
+ name: "Test",
659
+ props: {},
660
+ streamingState: "started",
661
+ },
662
+ ],
663
+ createdAt: "2024-01-01T00:00:00.000Z",
664
+ },
665
+ ];
666
+ const event = {
667
+ type: EventType.CUSTOM,
668
+ name: "tambo.component.props_delta",
669
+ value: {
670
+ componentId: "comp_1",
671
+ operations: [{ op: "add", path: "/temperature", value: 72 }],
672
+ },
673
+ };
674
+ const result = streamReducer(state, {
675
+ type: "EVENT",
676
+ event,
677
+ threadId: "thread_1",
678
+ });
679
+ const component = asComponentContent(result.threadMap.thread_1.thread.messages[0].content, 0);
680
+ expect(component.props).toEqual({ temperature: 72 });
681
+ expect(component.streamingState).toBe("streaming");
682
+ });
683
+ it("throws when component not found for props_delta", () => {
684
+ const state = createTestStreamState("thread_1");
685
+ state.threadMap.thread_1.thread.messages = [
686
+ {
687
+ id: "msg_1",
688
+ role: "assistant",
689
+ content: [], // No component
690
+ createdAt: "2024-01-01T00:00:00.000Z",
691
+ },
692
+ ];
693
+ const event = {
694
+ type: EventType.CUSTOM,
695
+ name: "tambo.component.props_delta",
696
+ value: {
697
+ componentId: "unknown_comp",
698
+ operations: [{ op: "add", path: "/value", value: 1 }],
699
+ },
700
+ };
701
+ expect(() => {
702
+ streamReducer(state, {
703
+ type: "EVENT",
704
+ event,
705
+ threadId: "thread_1",
706
+ });
707
+ }).toThrow("component unknown_comp not found");
708
+ });
709
+ it("handles tambo.run.awaiting_input event", () => {
710
+ const state = createTestStreamState("thread_1");
711
+ state.threadMap.thread_1.thread.status = "streaming";
712
+ const event = {
713
+ type: EventType.CUSTOM,
714
+ name: "tambo.run.awaiting_input",
715
+ value: { pendingToolCallIds: ["tool_1", "tool_2"] },
716
+ };
717
+ const result = streamReducer(state, {
718
+ type: "EVENT",
719
+ event,
720
+ threadId: "thread_1",
721
+ });
722
+ expect(result.threadMap.thread_1.thread.status).toBe("waiting");
723
+ expect(result.threadMap.thread_1.streaming.status).toBe("waiting");
724
+ });
725
+ it("handles tambo.component.state_delta event", () => {
726
+ const state = createTestStreamState("thread_1");
727
+ state.threadMap.thread_1.thread.messages = [
728
+ {
729
+ id: "msg_1",
730
+ role: "assistant",
731
+ content: [
732
+ {
733
+ type: "component",
734
+ id: "comp_1",
735
+ name: "Counter",
736
+ props: {},
737
+ streamingState: "started",
738
+ },
739
+ ],
740
+ createdAt: "2024-01-01T00:00:00.000Z",
741
+ },
742
+ ];
743
+ const event = {
744
+ type: EventType.CUSTOM,
745
+ name: "tambo.component.state_delta",
746
+ value: {
747
+ componentId: "comp_1",
748
+ operations: [{ op: "add", path: "/count", value: 42 }],
749
+ },
750
+ };
751
+ const result = streamReducer(state, {
752
+ type: "EVENT",
753
+ event,
754
+ threadId: "thread_1",
755
+ });
756
+ const component = asComponentContent(result.threadMap.thread_1.thread.messages[0].content, 0);
757
+ expect(component.state).toEqual({ count: 42 });
758
+ expect(component.streamingState).toBe("streaming");
759
+ });
760
+ it("handles tambo.component.end event", () => {
761
+ const state = createTestStreamState("thread_1");
762
+ state.threadMap.thread_1.thread.messages = [
763
+ {
764
+ id: "msg_1",
765
+ role: "assistant",
766
+ content: [
767
+ {
768
+ type: "component",
769
+ id: "comp_1",
770
+ name: "Test",
771
+ props: {},
772
+ streamingState: "streaming",
773
+ },
774
+ ],
775
+ createdAt: "2024-01-01T00:00:00.000Z",
776
+ },
777
+ ];
778
+ const event = {
779
+ type: EventType.CUSTOM,
780
+ name: "tambo.component.end",
781
+ value: { componentId: "comp_1" },
782
+ };
783
+ const result = streamReducer(state, {
784
+ type: "EVENT",
785
+ event,
786
+ threadId: "thread_1",
787
+ });
788
+ // component.end sets streamingState to 'done'
789
+ expect(result.threadMap.thread_1.thread.messages[0].content[0]).toEqual({
790
+ type: "component",
791
+ id: "comp_1",
792
+ name: "Test",
793
+ props: {},
794
+ streamingState: "done",
795
+ });
796
+ });
797
+ it("throws when message not found for tambo.component.start", () => {
798
+ const state = createTestStreamState("thread_1");
799
+ state.threadMap.thread_1.thread.messages = [
800
+ {
801
+ id: "msg_1",
802
+ role: "assistant",
803
+ content: [],
804
+ createdAt: "2024-01-01T00:00:00.000Z",
805
+ },
806
+ ];
807
+ const event = {
808
+ type: EventType.CUSTOM,
809
+ name: "tambo.component.start",
810
+ value: {
811
+ messageId: "unknown_msg",
812
+ componentId: "comp_1",
813
+ componentName: "Test",
814
+ },
815
+ };
816
+ expect(() => {
817
+ streamReducer(state, {
818
+ type: "EVENT",
819
+ event,
820
+ threadId: "thread_1",
821
+ });
822
+ }).toThrow("Message unknown_msg not found for tambo.component.start event");
823
+ });
824
+ it("throws when component not found for tambo.component.end", () => {
825
+ const state = createTestStreamState("thread_1");
826
+ state.threadMap.thread_1.thread.messages = [
827
+ {
828
+ id: "msg_1",
829
+ role: "assistant",
830
+ content: [],
831
+ createdAt: "2024-01-01T00:00:00.000Z",
832
+ },
833
+ ];
834
+ const event = {
835
+ type: EventType.CUSTOM,
836
+ name: "tambo.component.end",
837
+ value: { componentId: "unknown_comp" },
838
+ };
839
+ expect(() => {
840
+ streamReducer(state, {
841
+ type: "EVENT",
842
+ event,
843
+ threadId: "thread_1",
844
+ });
845
+ }).toThrow("component unknown_comp not found");
846
+ });
847
+ it("handles multiple component props_delta operations", () => {
848
+ const state = createTestStreamState("thread_1");
849
+ state.threadMap.thread_1.thread.messages = [
850
+ {
851
+ id: "msg_1",
852
+ role: "assistant",
853
+ content: [
854
+ {
855
+ type: "component",
856
+ id: "comp_1",
857
+ name: "Test",
858
+ props: { existing: "value" },
859
+ streamingState: "started",
860
+ },
861
+ ],
862
+ createdAt: "2024-01-01T00:00:00.000Z",
863
+ },
864
+ ];
865
+ const event = {
866
+ type: EventType.CUSTOM,
867
+ name: "tambo.component.props_delta",
868
+ value: {
869
+ componentId: "comp_1",
870
+ operations: [
871
+ { op: "add", path: "/newProp", value: "new" },
872
+ { op: "replace", path: "/existing", value: "updated" },
873
+ ],
874
+ },
875
+ };
876
+ const result = streamReducer(state, {
877
+ type: "EVENT",
878
+ event,
879
+ threadId: "thread_1",
880
+ });
881
+ const component = asComponentContent(result.threadMap.thread_1.thread.messages[0].content, 0);
882
+ expect(component.props).toEqual({
883
+ existing: "updated",
884
+ newProp: "new",
885
+ });
886
+ });
887
+ it("handles multiple components in same message", () => {
888
+ const state = createTestStreamState("thread_1");
889
+ state.threadMap.thread_1.thread.messages = [
890
+ {
891
+ id: "msg_1",
892
+ role: "assistant",
893
+ content: [
894
+ {
895
+ type: "component",
896
+ id: "comp_1",
897
+ name: "First",
898
+ props: {},
899
+ streamingState: "done",
900
+ },
901
+ ],
902
+ createdAt: "2024-01-01T00:00:00.000Z",
903
+ },
904
+ ];
905
+ // Add second component
906
+ const event = {
907
+ type: EventType.CUSTOM,
908
+ name: "tambo.component.start",
909
+ value: {
910
+ messageId: "msg_1",
911
+ componentId: "comp_2",
912
+ componentName: "Second",
913
+ },
914
+ };
915
+ const result = streamReducer(state, {
916
+ type: "EVENT",
917
+ event,
918
+ threadId: "thread_1",
919
+ });
920
+ const content = result.threadMap.thread_1.thread.messages[0].content;
921
+ expect(content).toHaveLength(2);
922
+ expect(asComponentContent(content, 0).id).toBe("comp_1");
923
+ expect(asComponentContent(content, 1).id).toBe("comp_2");
924
+ });
925
+ it("updates correct component when multiple exist", () => {
926
+ const state = createTestStreamState("thread_1");
927
+ state.threadMap.thread_1.thread.messages = [
928
+ {
929
+ id: "msg_1",
930
+ role: "assistant",
931
+ content: [
932
+ {
933
+ type: "component",
934
+ id: "comp_1",
935
+ name: "First",
936
+ props: { value: 1 },
937
+ streamingState: "done",
938
+ },
939
+ {
940
+ type: "component",
941
+ id: "comp_2",
942
+ name: "Second",
943
+ props: { value: 2 },
944
+ streamingState: "streaming",
945
+ },
946
+ ],
947
+ createdAt: "2024-01-01T00:00:00.000Z",
948
+ },
949
+ ];
950
+ // Update second component
951
+ const event = {
952
+ type: EventType.CUSTOM,
953
+ name: "tambo.component.props_delta",
954
+ value: {
955
+ componentId: "comp_2",
956
+ operations: [{ op: "replace", path: "/value", value: 99 }],
957
+ },
958
+ };
959
+ const result = streamReducer(state, {
960
+ type: "EVENT",
961
+ event,
962
+ threadId: "thread_1",
963
+ });
964
+ const content = result.threadMap.thread_1.thread.messages[0].content;
965
+ // First component unchanged
966
+ expect(asComponentContent(content, 0).props).toEqual({ value: 1 });
967
+ // Second component updated
968
+ expect(asComponentContent(content, 1).props).toEqual({ value: 99 });
969
+ });
970
+ });
971
+ describe("snapshot tests for complex state transitions", () => {
972
+ it("matches snapshot for full message flow", () => {
973
+ let state = createTestStreamState("thread_1");
974
+ // RUN_STARTED
975
+ const runStarted = {
976
+ type: EventType.RUN_STARTED,
977
+ runId: "run_1",
978
+ threadId: "thread_1",
979
+ timestamp: 1704067200000,
980
+ };
981
+ state = streamReducer(state, {
982
+ type: "EVENT",
983
+ event: runStarted,
984
+ threadId: "thread_1",
985
+ });
986
+ // TEXT_MESSAGE_START
987
+ const msgStart = {
988
+ type: EventType.TEXT_MESSAGE_START,
989
+ messageId: "msg_1",
990
+ role: "assistant",
991
+ };
992
+ state = streamReducer(state, {
993
+ type: "EVENT",
994
+ event: msgStart,
995
+ threadId: "thread_1",
996
+ });
997
+ // TEXT_MESSAGE_CONTENT
998
+ const msgContent = {
999
+ type: EventType.TEXT_MESSAGE_CONTENT,
1000
+ messageId: "msg_1",
1001
+ delta: "Hello, how can I help?",
1002
+ };
1003
+ state = streamReducer(state, {
1004
+ type: "EVENT",
1005
+ event: msgContent,
1006
+ threadId: "thread_1",
1007
+ });
1008
+ // TEXT_MESSAGE_END
1009
+ const msgEnd = {
1010
+ type: EventType.TEXT_MESSAGE_END,
1011
+ messageId: "msg_1",
1012
+ };
1013
+ state = streamReducer(state, {
1014
+ type: "EVENT",
1015
+ event: msgEnd,
1016
+ threadId: "thread_1",
1017
+ });
1018
+ // RUN_FINISHED
1019
+ const runFinished = {
1020
+ type: EventType.RUN_FINISHED,
1021
+ runId: "run_1",
1022
+ threadId: "thread_1",
1023
+ };
1024
+ state = streamReducer(state, {
1025
+ type: "EVENT",
1026
+ event: runFinished,
1027
+ threadId: "thread_1",
1028
+ });
1029
+ // Normalize timestamps for snapshot stability
1030
+ const snapshot = {
1031
+ ...state,
1032
+ threadMap: {
1033
+ thread_1: {
1034
+ ...state.threadMap.thread_1,
1035
+ thread: {
1036
+ ...state.threadMap.thread_1.thread,
1037
+ messages: state.threadMap.thread_1.thread.messages.map((m) => ({
1038
+ ...m,
1039
+ createdAt: "[TIMESTAMP]",
1040
+ })),
1041
+ createdAt: "[TIMESTAMP]",
1042
+ updatedAt: "[TIMESTAMP]",
1043
+ },
1044
+ },
1045
+ },
1046
+ };
1047
+ expect(snapshot).toMatchSnapshot();
1048
+ });
1049
+ it("matches snapshot for component streaming flow", () => {
1050
+ let state = createTestStreamState("thread_1");
1051
+ // Add a message
1052
+ state.threadMap.thread_1.thread.messages = [
1053
+ {
1054
+ id: "msg_1",
1055
+ role: "assistant",
1056
+ content: [],
1057
+ createdAt: "2024-01-01T00:00:00.000Z",
1058
+ },
1059
+ ];
1060
+ // COMPONENT_START
1061
+ const compStart = {
1062
+ type: EventType.CUSTOM,
1063
+ name: "tambo.component.start",
1064
+ value: {
1065
+ messageId: "msg_1",
1066
+ componentId: "comp_1",
1067
+ componentName: "WeatherCard",
1068
+ },
1069
+ };
1070
+ state = streamReducer(state, {
1071
+ type: "EVENT",
1072
+ event: compStart,
1073
+ threadId: "thread_1",
1074
+ });
1075
+ // COMPONENT_PROPS_DELTA - add city
1076
+ const propsDelta1 = {
1077
+ type: EventType.CUSTOM,
1078
+ name: "tambo.component.props_delta",
1079
+ value: {
1080
+ componentId: "comp_1",
1081
+ operations: [{ op: "add", path: "/city", value: "New York" }],
1082
+ },
1083
+ };
1084
+ state = streamReducer(state, {
1085
+ type: "EVENT",
1086
+ event: propsDelta1,
1087
+ threadId: "thread_1",
1088
+ });
1089
+ // COMPONENT_PROPS_DELTA - add temperature
1090
+ const propsDelta2 = {
1091
+ type: EventType.CUSTOM,
1092
+ name: "tambo.component.props_delta",
1093
+ value: {
1094
+ componentId: "comp_1",
1095
+ operations: [{ op: "add", path: "/temperature", value: 72 }],
1096
+ },
1097
+ };
1098
+ state = streamReducer(state, {
1099
+ type: "EVENT",
1100
+ event: propsDelta2,
1101
+ threadId: "thread_1",
1102
+ });
1103
+ // COMPONENT_END
1104
+ const compEnd = {
1105
+ type: EventType.CUSTOM,
1106
+ name: "tambo.component.end",
1107
+ value: { componentId: "comp_1" },
1108
+ };
1109
+ state = streamReducer(state, {
1110
+ type: "EVENT",
1111
+ event: compEnd,
1112
+ threadId: "thread_1",
1113
+ });
1114
+ // Normalize timestamps for snapshot stability
1115
+ const snapshot = {
1116
+ ...state,
1117
+ threadMap: {
1118
+ thread_1: {
1119
+ ...state.threadMap.thread_1,
1120
+ thread: {
1121
+ ...state.threadMap.thread_1.thread,
1122
+ messages: state.threadMap.thread_1.thread.messages.map((m) => ({
1123
+ ...m,
1124
+ createdAt: "[TIMESTAMP]",
1125
+ })),
1126
+ createdAt: "[TIMESTAMP]",
1127
+ updatedAt: "[TIMESTAMP]",
1128
+ },
1129
+ },
1130
+ },
1131
+ };
1132
+ expect(snapshot).toMatchSnapshot();
1133
+ });
1134
+ it("matches snapshot for tool call flow", () => {
1135
+ let state = createTestStreamState("thread_1");
1136
+ // Add a message with tool_use
1137
+ state.threadMap.thread_1.thread.messages = [
1138
+ {
1139
+ id: "msg_1",
1140
+ role: "assistant",
1141
+ content: [
1142
+ { type: "tool_use", id: "tool_1", name: "get_weather", input: {} },
1143
+ ],
1144
+ createdAt: "2024-01-01T00:00:00.000Z",
1145
+ },
1146
+ ];
1147
+ // TOOL_CALL_ARGS
1148
+ const toolArgs = {
1149
+ type: EventType.TOOL_CALL_ARGS,
1150
+ toolCallId: "tool_1",
1151
+ delta: '{"city":"San Francisco","units":"fahrenheit"}',
1152
+ };
1153
+ state = streamReducer(state, {
1154
+ type: "EVENT",
1155
+ event: toolArgs,
1156
+ threadId: "thread_1",
1157
+ });
1158
+ // TOOL_CALL_END
1159
+ const toolEnd = {
1160
+ type: EventType.TOOL_CALL_END,
1161
+ toolCallId: "tool_1",
1162
+ };
1163
+ state = streamReducer(state, {
1164
+ type: "EVENT",
1165
+ event: toolEnd,
1166
+ threadId: "thread_1",
1167
+ });
1168
+ // TOOL_CALL_RESULT
1169
+ const toolResult = {
1170
+ type: EventType.TOOL_CALL_RESULT,
1171
+ toolCallId: "tool_1",
1172
+ messageId: "msg_1",
1173
+ content: "Temperature: 65°F, Sunny",
1174
+ role: "tool",
1175
+ };
1176
+ state = streamReducer(state, {
1177
+ type: "EVENT",
1178
+ event: toolResult,
1179
+ threadId: "thread_1",
1180
+ });
1181
+ // Normalize timestamps for snapshot stability
1182
+ const snapshot = {
1183
+ ...state,
1184
+ threadMap: {
1185
+ thread_1: {
1186
+ ...state.threadMap.thread_1,
1187
+ thread: {
1188
+ ...state.threadMap.thread_1.thread,
1189
+ messages: state.threadMap.thread_1.thread.messages.map((m) => ({
1190
+ ...m,
1191
+ createdAt: "[TIMESTAMP]",
1192
+ })),
1193
+ createdAt: "[TIMESTAMP]",
1194
+ updatedAt: "[TIMESTAMP]",
1195
+ },
1196
+ },
1197
+ },
1198
+ };
1199
+ expect(snapshot).toMatchSnapshot();
1200
+ });
1201
+ });
1202
+ });
1203
+ //# sourceMappingURL=event-accumulator.test.js.map