@tambo-ai/react 0.73.0 → 0.74.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 (419) hide show
  1. package/README.md +12 -12
  2. package/dist/hooks/use-component-state.d.ts +1 -1
  3. package/dist/hooks/use-component-state.js.map +1 -1
  4. package/dist/hooks/use-streaming-props.d.ts +1 -1
  5. package/dist/hooks/use-streaming-props.js +1 -1
  6. package/dist/hooks/use-streaming-props.js.map +1 -1
  7. package/dist/hooks/use-tambo-stream-status.d.ts +1 -1
  8. package/dist/hooks/use-tambo-stream-status.js +1 -1
  9. package/dist/hooks/use-tambo-stream-status.js.map +1 -1
  10. package/dist/mcp/mcp-hooks.d.ts +4 -0
  11. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  12. package/dist/mcp/mcp-hooks.js +4 -0
  13. package/dist/mcp/mcp-hooks.js.map +1 -1
  14. package/dist/providers/tambo-interactable-provider-partial-updates.test.js +3 -3
  15. package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  16. package/dist/providers/tambo-interactable-provider.js +2 -2
  17. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  18. package/dist/providers/tambo-interactable-provider.test.js +3 -3
  19. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  20. package/dist/providers/tambo-provider.d.ts +3 -0
  21. package/dist/providers/tambo-provider.d.ts.map +1 -1
  22. package/dist/providers/tambo-provider.js +3 -0
  23. package/dist/providers/tambo-provider.js.map +1 -1
  24. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  25. package/dist/providers/tambo-thread-input-provider.js +1 -0
  26. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  27. package/dist/util/resource-content-resolver.d.ts.map +1 -1
  28. package/dist/util/resource-content-resolver.js +2 -0
  29. package/dist/util/resource-content-resolver.js.map +1 -1
  30. package/dist/v1/__tests__/v1-interactables.test.d.ts +2 -0
  31. package/dist/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
  32. package/dist/v1/__tests__/v1-interactables.test.js +135 -0
  33. package/dist/v1/__tests__/v1-interactables.test.js.map +1 -0
  34. package/dist/v1/components/v1-component-renderer.d.ts +48 -0
  35. package/dist/v1/components/v1-component-renderer.d.ts.map +1 -0
  36. package/dist/v1/components/v1-component-renderer.js +137 -0
  37. package/dist/v1/components/v1-component-renderer.js.map +1 -0
  38. package/dist/v1/components/v1-component-renderer.test.d.ts +2 -0
  39. package/dist/v1/components/v1-component-renderer.test.d.ts.map +1 -0
  40. package/dist/v1/components/v1-component-renderer.test.js +270 -0
  41. package/dist/v1/components/v1-component-renderer.test.js.map +1 -0
  42. package/dist/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
  43. package/dist/v1/hooks/use-tambo-v1-component-state.js +2 -25
  44. package/dist/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
  45. package/dist/v1/hooks/use-tambo-v1-component-state.test.js +2 -1
  46. package/dist/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
  47. package/dist/v1/hooks/use-tambo-v1-messages.test.js +25 -1
  48. package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
  49. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +18 -0
  50. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  51. package/dist/v1/hooks/use-tambo-v1-send-message.js +204 -17
  52. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  53. package/dist/v1/hooks/use-tambo-v1-send-message.test.js +261 -7
  54. package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
  55. package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
  56. package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
  57. package/dist/v1/hooks/use-tambo-v1-stream-status.js +179 -0
  58. package/dist/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
  59. package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
  60. package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
  61. package/dist/v1/hooks/use-tambo-v1-stream-status.test.js +371 -0
  62. package/dist/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
  63. package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
  64. package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
  65. package/dist/v1/hooks/use-tambo-v1-suggestions.js +153 -87
  66. package/dist/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
  67. package/dist/v1/hooks/use-tambo-v1-suggestions.test.js +213 -134
  68. package/dist/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
  69. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js +148 -13
  70. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
  71. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
  72. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
  73. package/dist/v1/hooks/use-tambo-v1-thread-list.js +11 -10
  74. package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
  75. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +37 -2
  76. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
  77. package/dist/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
  78. package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
  79. package/dist/v1/hooks/use-tambo-v1-thread.js +2 -7
  80. package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -1
  81. package/dist/v1/hooks/use-tambo-v1-thread.test.js +2 -0
  82. package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
  83. package/dist/v1/hooks/use-tambo-v1.d.ts +12 -28
  84. package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -1
  85. package/dist/v1/hooks/use-tambo-v1.js +164 -31
  86. package/dist/v1/hooks/use-tambo-v1.js.map +1 -1
  87. package/dist/v1/hooks/use-tambo-v1.test.js +891 -18
  88. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -1
  89. package/dist/v1/index.d.ts +7 -1
  90. package/dist/v1/index.d.ts.map +1 -1
  91. package/dist/v1/index.js +18 -1
  92. package/dist/v1/index.js.map +1 -1
  93. package/dist/v1/providers/tambo-v1-provider.d.ts +16 -6
  94. package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -1
  95. package/dist/v1/providers/tambo-v1-provider.js +14 -19
  96. package/dist/v1/providers/tambo-v1-provider.js.map +1 -1
  97. package/dist/v1/providers/tambo-v1-provider.test.js +34 -20
  98. package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -1
  99. package/dist/v1/providers/tambo-v1-stream-context.d.ts +3 -3
  100. package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
  101. package/dist/v1/providers/tambo-v1-stream-context.js +60 -12
  102. package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -1
  103. package/dist/v1/providers/tambo-v1-stream-context.test.js +49 -20
  104. package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
  105. package/dist/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
  106. package/dist/v1/providers/tambo-v1-stub-provider.js +2 -0
  107. package/dist/v1/providers/tambo-v1-stub-provider.js.map +1 -1
  108. package/dist/v1/providers/tambo-v1-stub-provider.test.js +7 -6
  109. package/dist/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
  110. package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts +1 -6
  111. package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
  112. package/dist/v1/providers/tambo-v1-thread-input-provider.js +14 -12
  113. package/dist/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
  114. package/dist/v1/types/event.d.ts +9 -1
  115. package/dist/v1/types/event.d.ts.map +1 -1
  116. package/dist/v1/types/event.js.map +1 -1
  117. package/dist/v1/types/event.test.js +5 -1
  118. package/dist/v1/types/event.test.js.map +1 -1
  119. package/dist/v1/types/message.d.ts +65 -7
  120. package/dist/v1/types/message.d.ts.map +1 -1
  121. package/dist/v1/types/message.js.map +1 -1
  122. package/dist/v1/types/thread.d.ts +4 -0
  123. package/dist/v1/types/thread.d.ts.map +1 -1
  124. package/dist/v1/types/thread.js.map +1 -1
  125. package/dist/v1/utils/event-accumulator.d.ts +40 -4
  126. package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
  127. package/dist/v1/utils/event-accumulator.js +444 -35
  128. package/dist/v1/utils/event-accumulator.js.map +1 -1
  129. package/dist/v1/utils/event-accumulator.test.js +1041 -28
  130. package/dist/v1/utils/event-accumulator.test.js.map +1 -1
  131. package/dist/v1/utils/registry-conversion.d.ts +9 -9
  132. package/dist/v1/utils/registry-conversion.d.ts.map +1 -1
  133. package/dist/v1/utils/registry-conversion.js +10 -11
  134. package/dist/v1/utils/registry-conversion.js.map +1 -1
  135. package/dist/v1/utils/registry-conversion.test.js +39 -11
  136. package/dist/v1/utils/registry-conversion.test.js.map +1 -1
  137. package/dist/v1/utils/thread-utils.d.ts +16 -0
  138. package/dist/v1/utils/thread-utils.d.ts.map +1 -0
  139. package/dist/v1/utils/thread-utils.js +34 -0
  140. package/dist/v1/utils/thread-utils.js.map +1 -0
  141. package/dist/v1/utils/tool-executor.d.ts.map +1 -1
  142. package/dist/v1/utils/tool-executor.js +2 -0
  143. package/dist/v1/utils/tool-executor.js.map +1 -1
  144. package/dist/v1/utils/tool-executor.test.js +5 -0
  145. package/dist/v1/utils/tool-executor.test.js.map +1 -1
  146. package/esm/context-helpers/context-helpers-provider.test.js +2 -2
  147. package/esm/context-helpers/context-helpers.test.js +1 -1
  148. package/esm/context-helpers/current-interactables-context-helper.d.ts +1 -1
  149. package/esm/context-helpers/current-page-context-helper.d.ts +1 -1
  150. package/esm/context-helpers/current-time-context-helper.d.ts +1 -1
  151. package/esm/context-helpers/index.d.ts +4 -4
  152. package/esm/context-helpers/index.js +4 -4
  153. package/esm/hoc/with-tambo-interactable.d.ts +1 -1
  154. package/esm/hoc/with-tambo-interactable.js +2 -2
  155. package/esm/hoc/with-tambo-interactable.test.js +3 -3
  156. package/esm/hooks/index.d.ts +8 -8
  157. package/esm/hooks/index.js +8 -8
  158. package/esm/hooks/react-query-hooks.js +1 -1
  159. package/esm/hooks/use-component-state.d.ts +1 -1
  160. package/esm/hooks/use-component-state.js +3 -3
  161. package/esm/hooks/use-component-state.js.map +1 -1
  162. package/esm/hooks/use-component-state.test.js +5 -5
  163. package/esm/hooks/use-current-message.d.ts +1 -1
  164. package/esm/hooks/use-current-message.test.js +1 -1
  165. package/esm/hooks/use-message-images.test.js +1 -1
  166. package/esm/hooks/use-streaming-props.d.ts +1 -1
  167. package/esm/hooks/use-streaming-props.js +1 -1
  168. package/esm/hooks/use-streaming-props.js.map +1 -1
  169. package/esm/hooks/use-suggestions.d.ts +2 -2
  170. package/esm/hooks/use-suggestions.js +10 -10
  171. package/esm/hooks/use-suggestions.test.js +7 -7
  172. package/esm/hooks/use-tambo-stream-status.d.ts +1 -1
  173. package/esm/hooks/use-tambo-stream-status.js +4 -4
  174. package/esm/hooks/use-tambo-stream-status.js.map +1 -1
  175. package/esm/hooks/use-tambo-stream-status.test.js +4 -4
  176. package/esm/hooks/use-tambo-threads.js +3 -3
  177. package/esm/hooks/use-tambo-threads.test.js +3 -3
  178. package/esm/hooks/use-tambo-voice.js +2 -2
  179. package/esm/hooks/use-tambo-voice.test.js +3 -3
  180. package/esm/index.d.ts +22 -22
  181. package/esm/index.js +15 -15
  182. package/esm/mcp/elicitation.d.ts +1 -1
  183. package/esm/mcp/elicitation.test.js +1 -1
  184. package/esm/mcp/index.d.ts +7 -7
  185. package/esm/mcp/index.js +3 -3
  186. package/esm/mcp/mcp-client.d.ts +1 -1
  187. package/esm/mcp/mcp-client.js +1 -1
  188. package/esm/mcp/mcp-client.test.js +1 -1
  189. package/esm/mcp/mcp-hooks.d.ts +5 -1
  190. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  191. package/esm/mcp/mcp-hooks.js +8 -4
  192. package/esm/mcp/mcp-hooks.js.map +1 -1
  193. package/esm/mcp/mcp-hooks.test.js +6 -6
  194. package/esm/mcp/tambo-mcp-provider.d.ts +4 -4
  195. package/esm/mcp/tambo-mcp-provider.js +7 -7
  196. package/esm/mcp/tambo-mcp-provider.test.js +5 -5
  197. package/esm/mcp/use-mcp-servers.test.js +4 -4
  198. package/esm/model/generate-component-response.d.ts +1 -1
  199. package/esm/model/tambo-interactable.d.ts +1 -1
  200. package/esm/model/tambo-thread.d.ts +1 -1
  201. package/esm/providers/__tests__/thread-input-resource-resolution.test.js +3 -3
  202. package/esm/providers/hooks/use-tambo-session-token.test.js +1 -1
  203. package/esm/providers/index.d.ts +12 -12
  204. package/esm/providers/index.js +10 -10
  205. package/esm/providers/tambo-client-provider.js +1 -1
  206. package/esm/providers/tambo-client-provider.test.js +2 -2
  207. package/esm/providers/tambo-component-provider.d.ts +1 -1
  208. package/esm/providers/tambo-component-provider.js +2 -2
  209. package/esm/providers/tambo-context-attachment-provider.js +1 -1
  210. package/esm/providers/tambo-context-attachment-provider.test.js +2 -2
  211. package/esm/providers/tambo-context-helpers-provider.d.ts +1 -1
  212. package/esm/providers/tambo-context-helpers-provider.js +1 -1
  213. package/esm/providers/tambo-context-helpers-provider.test.js +2 -2
  214. package/esm/providers/tambo-interactable-provider-partial-updates.test.js +4 -4
  215. package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  216. package/esm/providers/tambo-interactable-provider.d.ts +5 -5
  217. package/esm/providers/tambo-interactable-provider.js +6 -6
  218. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  219. package/esm/providers/tambo-interactable-provider.test.js +4 -4
  220. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  221. package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js +4 -4
  222. package/esm/providers/tambo-interactables-additional-context.test.js +4 -4
  223. package/esm/providers/tambo-mcp-token-provider.js +2 -2
  224. package/esm/providers/tambo-prop-stream-provider/index.d.ts +8 -8
  225. package/esm/providers/tambo-prop-stream-provider/index.js +9 -9
  226. package/esm/providers/tambo-prop-stream-provider/pending.d.ts +1 -1
  227. package/esm/providers/tambo-prop-stream-provider/pending.js +2 -2
  228. package/esm/providers/tambo-prop-stream-provider/provider.d.ts +1 -1
  229. package/esm/providers/tambo-prop-stream-provider/provider.js +2 -2
  230. package/esm/providers/tambo-prop-stream-provider/streaming.d.ts +1 -1
  231. package/esm/providers/tambo-prop-stream-provider/streaming.js +2 -2
  232. package/esm/providers/tambo-prop-stream-provider/success.d.ts +1 -1
  233. package/esm/providers/tambo-prop-stream-provider/success.js +2 -2
  234. package/esm/providers/tambo-prop-stream-provider/types.d.ts +1 -1
  235. package/esm/providers/tambo-prop-stream-provider.test.js +4 -4
  236. package/esm/providers/tambo-provider.d.ts +10 -7
  237. package/esm/providers/tambo-provider.d.ts.map +1 -1
  238. package/esm/providers/tambo-provider.js +13 -10
  239. package/esm/providers/tambo-provider.js.map +1 -1
  240. package/esm/providers/tambo-registry-provider.d.ts +3 -3
  241. package/esm/providers/tambo-registry-provider.js +3 -3
  242. package/esm/providers/tambo-registry-provider.test.js +2 -2
  243. package/esm/providers/tambo-registry-schema-compat.test.js +2 -2
  244. package/esm/providers/tambo-stubs.d.ts +4 -4
  245. package/esm/providers/tambo-stubs.js +9 -9
  246. package/esm/providers/tambo-stubs.test.js +2 -2
  247. package/esm/providers/tambo-thread-input-provider.d.ts +2 -2
  248. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  249. package/esm/providers/tambo-thread-input-provider.js +11 -10
  250. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider-initial-messages.test.js +6 -6
  252. package/esm/providers/tambo-thread-provider.d.ts +2 -2
  253. package/esm/providers/tambo-thread-provider.js +8 -8
  254. package/esm/providers/tambo-thread-provider.test.js +7 -7
  255. package/esm/schema/index.d.ts +4 -4
  256. package/esm/schema/index.js +4 -4
  257. package/esm/schema/json-schema.test.js +1 -1
  258. package/esm/schema/schema.d.ts +1 -1
  259. package/esm/schema/schema.js +2 -2
  260. package/esm/schema/schema.test.js +3 -3
  261. package/esm/schema/standard-schema.test.js +1 -1
  262. package/esm/schema/validate.js +2 -2
  263. package/esm/schema/validate.test.js +1 -1
  264. package/esm/testing/tools.d.ts +3 -3
  265. package/esm/testing/tools.js +2 -2
  266. package/esm/util/content-parts.test.js +1 -1
  267. package/esm/util/generate-component.d.ts +2 -2
  268. package/esm/util/generate-component.js +4 -4
  269. package/esm/util/generate-component.test.js +2 -2
  270. package/esm/util/is-promise.test.js +1 -1
  271. package/esm/util/mcp-server-utils.d.ts +1 -1
  272. package/esm/util/mcp-server-utils.js +1 -1
  273. package/esm/util/mcp-server-utils.test.js +2 -2
  274. package/esm/util/message-builder.d.ts +1 -1
  275. package/esm/util/message-builder.test.js +1 -1
  276. package/esm/util/query-utils.test.js +1 -1
  277. package/esm/util/registry-validators.d.ts +1 -1
  278. package/esm/util/registry-validators.js +2 -2
  279. package/esm/util/registry-validators.test.js +1 -1
  280. package/esm/util/registry.d.ts +1 -1
  281. package/esm/util/registry.js +1 -1
  282. package/esm/util/registry.test.js +2 -2
  283. package/esm/util/resource-content-resolver.d.ts +2 -2
  284. package/esm/util/resource-content-resolver.d.ts.map +1 -1
  285. package/esm/util/resource-content-resolver.js +3 -1
  286. package/esm/util/resource-content-resolver.js.map +1 -1
  287. package/esm/util/resource-content-resolver.test.js +3 -3
  288. package/esm/util/resource-validators.d.ts +1 -1
  289. package/esm/util/resource-validators.test.js +1 -1
  290. package/esm/util/tool-caller.d.ts +1 -1
  291. package/esm/util/tool-caller.js +1 -1
  292. package/esm/util/validate-component-name.test.js +1 -1
  293. package/esm/v1/__tests__/v1-interactables.test.d.ts +2 -0
  294. package/esm/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
  295. package/esm/v1/__tests__/v1-interactables.test.js +130 -0
  296. package/esm/v1/__tests__/v1-interactables.test.js.map +1 -0
  297. package/esm/v1/components/v1-component-renderer.d.ts +48 -0
  298. package/esm/v1/components/v1-component-renderer.d.ts.map +1 -0
  299. package/esm/v1/components/v1-component-renderer.js +100 -0
  300. package/esm/v1/components/v1-component-renderer.js.map +1 -0
  301. package/esm/v1/components/v1-component-renderer.test.d.ts +2 -0
  302. package/esm/v1/components/v1-component-renderer.test.d.ts.map +1 -0
  303. package/esm/v1/components/v1-component-renderer.test.js +265 -0
  304. package/esm/v1/components/v1-component-renderer.test.js.map +1 -0
  305. package/esm/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
  306. package/esm/v1/hooks/use-tambo-v1-component-state.js +4 -27
  307. package/esm/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
  308. package/esm/v1/hooks/use-tambo-v1-component-state.test.js +6 -5
  309. package/esm/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
  310. package/esm/v1/hooks/use-tambo-v1-messages.d.ts +1 -1
  311. package/esm/v1/hooks/use-tambo-v1-messages.js +1 -1
  312. package/esm/v1/hooks/use-tambo-v1-messages.test.js +27 -3
  313. package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
  314. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +20 -2
  315. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  316. package/esm/v1/hooks/use-tambo-v1-send-message.js +213 -26
  317. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  318. package/esm/v1/hooks/use-tambo-v1-send-message.test.js +266 -12
  319. package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
  320. package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
  321. package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
  322. package/esm/v1/hooks/use-tambo-v1-stream-status.js +176 -0
  323. package/esm/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
  324. package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
  325. package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
  326. package/esm/v1/hooks/use-tambo-v1-stream-status.test.js +369 -0
  327. package/esm/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
  328. package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
  329. package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
  330. package/esm/v1/hooks/use-tambo-v1-suggestions.js +157 -91
  331. package/esm/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
  332. package/esm/v1/hooks/use-tambo-v1-suggestions.test.js +218 -139
  333. package/esm/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
  334. package/esm/v1/hooks/use-tambo-v1-thread-input.d.ts +1 -1
  335. package/esm/v1/hooks/use-tambo-v1-thread-input.js +1 -1
  336. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js +151 -16
  337. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
  338. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
  339. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
  340. package/esm/v1/hooks/use-tambo-v1-thread-list.js +12 -11
  341. package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
  342. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +39 -4
  343. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
  344. package/esm/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
  345. package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
  346. package/esm/v1/hooks/use-tambo-v1-thread.js +3 -8
  347. package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -1
  348. package/esm/v1/hooks/use-tambo-v1-thread.test.js +4 -2
  349. package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
  350. package/esm/v1/hooks/use-tambo-v1.d.ts +15 -31
  351. package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -1
  352. package/esm/v1/hooks/use-tambo-v1.js +134 -34
  353. package/esm/v1/hooks/use-tambo-v1.js.map +1 -1
  354. package/esm/v1/hooks/use-tambo-v1.test.js +862 -19
  355. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -1
  356. package/esm/v1/index.d.ts +28 -22
  357. package/esm/v1/index.d.ts.map +1 -1
  358. package/esm/v1/index.js +30 -18
  359. package/esm/v1/index.js.map +1 -1
  360. package/esm/v1/providers/tambo-v1-provider.d.ts +21 -11
  361. package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -1
  362. package/esm/v1/providers/tambo-v1-provider.js +20 -25
  363. package/esm/v1/providers/tambo-v1-provider.js.map +1 -1
  364. package/esm/v1/providers/tambo-v1-provider.test.js +40 -26
  365. package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -1
  366. package/esm/v1/providers/tambo-v1-stream-context.d.ts +4 -4
  367. package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
  368. package/esm/v1/providers/tambo-v1-stream-context.js +62 -14
  369. package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -1
  370. package/esm/v1/providers/tambo-v1-stream-context.test.js +50 -21
  371. package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
  372. package/esm/v1/providers/tambo-v1-stub-provider.d.ts +3 -3
  373. package/esm/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
  374. package/esm/v1/providers/tambo-v1-stub-provider.js +7 -5
  375. package/esm/v1/providers/tambo-v1-stub-provider.js.map +1 -1
  376. package/esm/v1/providers/tambo-v1-stub-provider.test.js +12 -11
  377. package/esm/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
  378. package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts +3 -8
  379. package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
  380. package/esm/v1/providers/tambo-v1-thread-input-provider.js +18 -16
  381. package/esm/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
  382. package/esm/v1/types/event.d.ts +9 -1
  383. package/esm/v1/types/event.d.ts.map +1 -1
  384. package/esm/v1/types/event.js.map +1 -1
  385. package/esm/v1/types/event.test.js +6 -2
  386. package/esm/v1/types/event.test.js.map +1 -1
  387. package/esm/v1/types/message.d.ts +65 -7
  388. package/esm/v1/types/message.d.ts.map +1 -1
  389. package/esm/v1/types/message.js.map +1 -1
  390. package/esm/v1/types/thread.d.ts +5 -1
  391. package/esm/v1/types/thread.d.ts.map +1 -1
  392. package/esm/v1/types/thread.js.map +1 -1
  393. package/esm/v1/utils/component-renderer.test.js +1 -1
  394. package/esm/v1/utils/event-accumulator.d.ts +41 -5
  395. package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
  396. package/esm/v1/utils/event-accumulator.js +444 -36
  397. package/esm/v1/utils/event-accumulator.js.map +1 -1
  398. package/esm/v1/utils/event-accumulator.test.js +1042 -29
  399. package/esm/v1/utils/event-accumulator.test.js.map +1 -1
  400. package/esm/v1/utils/json-patch.test.js +1 -1
  401. package/esm/v1/utils/registry-conversion.d.ts +9 -9
  402. package/esm/v1/utils/registry-conversion.d.ts.map +1 -1
  403. package/esm/v1/utils/registry-conversion.js +11 -12
  404. package/esm/v1/utils/registry-conversion.js.map +1 -1
  405. package/esm/v1/utils/registry-conversion.test.js +40 -12
  406. package/esm/v1/utils/registry-conversion.test.js.map +1 -1
  407. package/esm/v1/utils/stream-handler.test.js +1 -1
  408. package/esm/v1/utils/thread-utils.d.ts +16 -0
  409. package/esm/v1/utils/thread-utils.d.ts.map +1 -0
  410. package/esm/v1/utils/thread-utils.js +31 -0
  411. package/esm/v1/utils/thread-utils.js.map +1 -0
  412. package/esm/v1/utils/tool-call-tracker.d.ts +1 -1
  413. package/esm/v1/utils/tool-executor.d.ts +1 -1
  414. package/esm/v1/utils/tool-executor.d.ts.map +1 -1
  415. package/esm/v1/utils/tool-executor.js +2 -0
  416. package/esm/v1/utils/tool-executor.js.map +1 -1
  417. package/esm/v1/utils/tool-executor.test.js +6 -1
  418. package/esm/v1/utils/tool-executor.test.js.map +1 -1
  419. package/package.json +11 -10
@@ -29,6 +29,7 @@ function createTestThreadState(threadId) {
29
29
  // Use fixed timestamps for snapshot stability
30
30
  createdAt: "2024-01-01T00:00:00.000Z",
31
31
  updatedAt: "2024-01-01T00:00:00.000Z",
32
+ lastRunCancelled: false,
32
33
  },
33
34
  };
34
35
  }
@@ -53,10 +54,13 @@ describe("createInitialThreadState", () => {
53
54
  });
54
55
  });
55
56
  describe("createInitialState", () => {
56
- it("creates empty stream state", () => {
57
+ it("creates initial state with placeholder thread", () => {
57
58
  const state = (0, event_accumulator_1.createInitialState)();
58
- expect(state.threadMap).toEqual({});
59
- expect(state.currentThreadId).toBeNull();
59
+ expect(state.currentThreadId).toBe("placeholder");
60
+ expect(state.threadMap.placeholder).toBeDefined();
61
+ expect(state.threadMap.placeholder.thread.id).toBe("placeholder");
62
+ expect(state.threadMap.placeholder.thread.messages).toEqual([]);
63
+ expect(state.threadMap.placeholder.streaming.status).toBe("idle");
60
64
  });
61
65
  });
62
66
  describe("streamReducer", () => {
@@ -152,6 +156,24 @@ describe("streamReducer", () => {
152
156
  expect(result.threadMap.thread_1.streaming.status).toBe("streaming");
153
157
  expect(result.threadMap.thread_1.streaming.runId).toBe("run_123");
154
158
  });
159
+ it("resets lastRunCancelled to false when a new run starts", () => {
160
+ // Start with a thread that was cancelled
161
+ const state = createTestStreamState("thread_1");
162
+ state.threadMap.thread_1.thread.lastRunCancelled = true;
163
+ const event = {
164
+ type: core_1.EventType.RUN_STARTED,
165
+ runId: "run_123",
166
+ threadId: "thread_1",
167
+ timestamp: 1704067200000,
168
+ };
169
+ const result = (0, event_accumulator_1.streamReducer)(state, {
170
+ type: "EVENT",
171
+ event,
172
+ threadId: "thread_1",
173
+ });
174
+ // lastRunCancelled should be reset to false
175
+ expect(result.threadMap.thread_1.thread.lastRunCancelled).toBe(false);
176
+ });
155
177
  it("uses provided timestamp for startTime", () => {
156
178
  const state = createTestStreamState("thread_1");
157
179
  const event = {
@@ -167,6 +189,198 @@ describe("streamReducer", () => {
167
189
  });
168
190
  expect(result.threadMap.thread_1.streaming.startTime).toBe(1704067200000);
169
191
  });
192
+ it("does not switch currentThreadId when placeholder has no messages", () => {
193
+ const state = (0, event_accumulator_1.createInitialState)();
194
+ const realThreadId = "thread_real_123";
195
+ const runStartedEvent = {
196
+ type: core_1.EventType.RUN_STARTED,
197
+ runId: "run_456",
198
+ threadId: realThreadId,
199
+ timestamp: 1704067200000,
200
+ };
201
+ const result = (0, event_accumulator_1.streamReducer)(state, {
202
+ type: "EVENT",
203
+ event: runStartedEvent,
204
+ threadId: realThreadId,
205
+ });
206
+ expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
207
+ expect(result.currentThreadId).toBe("placeholder");
208
+ });
209
+ it("migrates messages from placeholder thread to real thread", () => {
210
+ // Start with initial state (which has placeholder thread)
211
+ const state = (0, event_accumulator_1.createInitialState)();
212
+ // Verify placeholder thread exists
213
+ expect(state.currentThreadId).toBe("placeholder");
214
+ // Add a user message to the placeholder thread
215
+ const userMsgStart = {
216
+ type: core_1.EventType.TEXT_MESSAGE_START,
217
+ messageId: "user_msg_1",
218
+ role: "user",
219
+ };
220
+ const userMsgContent = {
221
+ type: core_1.EventType.TEXT_MESSAGE_CONTENT,
222
+ messageId: "user_msg_1",
223
+ delta: "Hello",
224
+ };
225
+ const userMsgEnd = {
226
+ type: core_1.EventType.TEXT_MESSAGE_END,
227
+ messageId: "user_msg_1",
228
+ };
229
+ let stateWithUserMsg = (0, event_accumulator_1.streamReducer)(state, {
230
+ type: "EVENT",
231
+ event: userMsgStart,
232
+ threadId: "placeholder",
233
+ });
234
+ stateWithUserMsg = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
235
+ type: "EVENT",
236
+ event: userMsgContent,
237
+ threadId: "placeholder",
238
+ });
239
+ stateWithUserMsg = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
240
+ type: "EVENT",
241
+ event: userMsgEnd,
242
+ threadId: "placeholder",
243
+ });
244
+ // Verify placeholder thread has the message
245
+ expect(stateWithUserMsg.currentThreadId).toBe("placeholder");
246
+ expect(stateWithUserMsg.threadMap.placeholder.thread.messages).toHaveLength(1);
247
+ expect(stateWithUserMsg.threadMap.placeholder.thread.messages[0].content[0]).toEqual({
248
+ type: "text",
249
+ text: "Hello",
250
+ });
251
+ // Now RUN_STARTED arrives with the real thread ID
252
+ const realThreadId = "thread_real_123";
253
+ const runStartedEvent = {
254
+ type: core_1.EventType.RUN_STARTED,
255
+ runId: "run_456",
256
+ threadId: realThreadId,
257
+ timestamp: 1704067200000,
258
+ };
259
+ const finalState = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
260
+ type: "EVENT",
261
+ event: runStartedEvent,
262
+ threadId: realThreadId,
263
+ });
264
+ // Placeholder thread should be reset to empty (not removed)
265
+ expect(finalState.threadMap.placeholder).toBeDefined();
266
+ expect(finalState.threadMap.placeholder.thread.messages).toHaveLength(0);
267
+ // Real thread should have the migrated user message
268
+ expect(finalState.threadMap[realThreadId]).toBeDefined();
269
+ expect(finalState.threadMap[realThreadId].thread.messages).toHaveLength(1);
270
+ expect(finalState.threadMap[realThreadId].thread.messages[0].content[0]).toEqual({
271
+ type: "text",
272
+ text: "Hello",
273
+ });
274
+ // currentThreadId should be updated to real thread
275
+ expect(finalState.currentThreadId).toBe(realThreadId);
276
+ // Real thread should be in streaming state
277
+ expect(finalState.threadMap[realThreadId].thread.status).toBe("streaming");
278
+ expect(finalState.threadMap[realThreadId].streaming.runId).toBe("run_456");
279
+ });
280
+ it("migrates messages even if currentThreadId changes away from placeholder", () => {
281
+ let state = (0, event_accumulator_1.createInitialState)();
282
+ state = (0, event_accumulator_1.streamReducer)(state, {
283
+ type: "INIT_THREAD",
284
+ threadId: "thread_1",
285
+ });
286
+ state = (0, event_accumulator_1.streamReducer)(state, {
287
+ type: "SET_CURRENT_THREAD",
288
+ threadId: "thread_1",
289
+ });
290
+ const userMsgStart = {
291
+ type: core_1.EventType.TEXT_MESSAGE_START,
292
+ messageId: "user_msg_1",
293
+ role: "user",
294
+ };
295
+ const userMsgContent = {
296
+ type: core_1.EventType.TEXT_MESSAGE_CONTENT,
297
+ messageId: "user_msg_1",
298
+ delta: "Hello",
299
+ };
300
+ const userMsgEnd = {
301
+ type: core_1.EventType.TEXT_MESSAGE_END,
302
+ messageId: "user_msg_1",
303
+ };
304
+ state = (0, event_accumulator_1.streamReducer)(state, {
305
+ type: "EVENT",
306
+ event: userMsgStart,
307
+ threadId: "placeholder",
308
+ });
309
+ state = (0, event_accumulator_1.streamReducer)(state, {
310
+ type: "EVENT",
311
+ event: userMsgContent,
312
+ threadId: "placeholder",
313
+ });
314
+ state = (0, event_accumulator_1.streamReducer)(state, {
315
+ type: "EVENT",
316
+ event: userMsgEnd,
317
+ threadId: "placeholder",
318
+ });
319
+ const realThreadId = "thread_real_123";
320
+ const runStartedEvent = {
321
+ type: core_1.EventType.RUN_STARTED,
322
+ runId: "run_456",
323
+ threadId: realThreadId,
324
+ timestamp: 1704067200000,
325
+ };
326
+ const result = (0, event_accumulator_1.streamReducer)(state, {
327
+ type: "EVENT",
328
+ event: runStartedEvent,
329
+ threadId: realThreadId,
330
+ });
331
+ expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
332
+ expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
333
+ expect(result.currentThreadId).toBe("thread_1");
334
+ });
335
+ it("prefers event.threadId over action threadId when RUN_STARTED is dispatched", () => {
336
+ // Start with initial state (which has placeholder thread)
337
+ const state = (0, event_accumulator_1.createInitialState)();
338
+ // Add a user message to the placeholder thread
339
+ const userMsgStart = {
340
+ type: core_1.EventType.TEXT_MESSAGE_START,
341
+ messageId: "user_msg_1",
342
+ role: "user",
343
+ };
344
+ const userMsgContent = {
345
+ type: core_1.EventType.TEXT_MESSAGE_CONTENT,
346
+ messageId: "user_msg_1",
347
+ delta: "Hello",
348
+ };
349
+ const userMsgEnd = {
350
+ type: core_1.EventType.TEXT_MESSAGE_END,
351
+ messageId: "user_msg_1",
352
+ };
353
+ let stateWithUserMsg = (0, event_accumulator_1.streamReducer)(state, {
354
+ type: "EVENT",
355
+ event: userMsgStart,
356
+ threadId: "placeholder",
357
+ });
358
+ stateWithUserMsg = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
359
+ type: "EVENT",
360
+ event: userMsgContent,
361
+ threadId: "placeholder",
362
+ });
363
+ stateWithUserMsg = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
364
+ type: "EVENT",
365
+ event: userMsgEnd,
366
+ threadId: "placeholder",
367
+ });
368
+ const realThreadId = "thread_real_123";
369
+ const runStartedEvent = {
370
+ type: core_1.EventType.RUN_STARTED,
371
+ runId: "run_456",
372
+ threadId: realThreadId,
373
+ timestamp: 1704067200000,
374
+ };
375
+ const result = (0, event_accumulator_1.streamReducer)(stateWithUserMsg, {
376
+ type: "EVENT",
377
+ event: runStartedEvent,
378
+ threadId: "placeholder",
379
+ });
380
+ expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
381
+ expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
382
+ expect(result.currentThreadId).toBe(realThreadId);
383
+ });
170
384
  });
171
385
  describe("RUN_FINISHED event", () => {
172
386
  it("updates thread status to complete", () => {
@@ -207,6 +421,26 @@ describe("streamReducer", () => {
207
421
  code: "ERR_001",
208
422
  });
209
423
  });
424
+ it("sets lastRunCancelled and idle status when code is CANCELLED", () => {
425
+ const state = createTestStreamState("thread_1");
426
+ const event = {
427
+ type: core_1.EventType.RUN_ERROR,
428
+ message: "Run cancelled",
429
+ code: "CANCELLED",
430
+ };
431
+ const result = (0, event_accumulator_1.streamReducer)(state, {
432
+ type: "EVENT",
433
+ event,
434
+ threadId: "thread_1",
435
+ });
436
+ // Cancelled runs should show as idle, not error
437
+ expect(result.threadMap.thread_1.thread.status).toBe("idle");
438
+ expect(result.threadMap.thread_1.streaming.status).toBe("idle");
439
+ // lastRunCancelled should be set
440
+ expect(result.threadMap.thread_1.thread.lastRunCancelled).toBe(true);
441
+ // No error should be stored for cancelled runs
442
+ expect(result.threadMap.thread_1.streaming.error).toBeUndefined();
443
+ });
210
444
  });
211
445
  describe("TEXT_MESSAGE_START event", () => {
212
446
  it("creates new message in thread", () => {
@@ -461,7 +695,7 @@ describe("streamReducer", () => {
461
695
  input: {},
462
696
  });
463
697
  });
464
- it("throws when parentMessageId message not found", () => {
698
+ it("creates synthetic message when parentMessageId not found", () => {
465
699
  const state = createTestStreamState("thread_1");
466
700
  state.threadMap.thread_1.thread.messages = [
467
701
  {
@@ -477,15 +711,25 @@ describe("streamReducer", () => {
477
711
  toolCallName: "get_weather",
478
712
  parentMessageId: "unknown_msg",
479
713
  };
480
- expect(() => {
481
- (0, event_accumulator_1.streamReducer)(state, {
482
- type: "EVENT",
483
- event,
484
- threadId: "thread_1",
485
- });
486
- }).toThrow("Message unknown_msg not found for TOOL_CALL_START event");
714
+ // When parentMessageId not found, creates a synthetic message
715
+ const result = (0, event_accumulator_1.streamReducer)(state, {
716
+ type: "EVENT",
717
+ event,
718
+ threadId: "thread_1",
719
+ });
720
+ // Should create a synthetic message with the tool call
721
+ const messages = result.threadMap.thread_1.thread.messages;
722
+ expect(messages).toHaveLength(2); // Original + synthetic
723
+ expect(messages[1].id).toBe("unknown_msg");
724
+ expect(messages[1].role).toBe("assistant");
725
+ expect(messages[1].content).toHaveLength(1);
726
+ expect(messages[1].content[0]).toMatchObject({
727
+ type: "tool_use",
728
+ id: "tool_1",
729
+ name: "get_weather",
730
+ });
487
731
  });
488
- it("throws when no messages exist", () => {
732
+ it("creates synthetic message when no messages exist", () => {
489
733
  const state = createTestStreamState("thread_1");
490
734
  state.threadMap.thread_1.thread.messages = [];
491
735
  const event = {
@@ -494,13 +738,22 @@ describe("streamReducer", () => {
494
738
  toolCallName: "get_weather",
495
739
  // No parentMessageId, no messages
496
740
  };
497
- expect(() => {
498
- (0, event_accumulator_1.streamReducer)(state, {
499
- type: "EVENT",
500
- event,
501
- threadId: "thread_1",
502
- });
503
- }).toThrow("No messages exist for TOOL_CALL_START event");
741
+ // When no messages exist, creates a synthetic message
742
+ const result = (0, event_accumulator_1.streamReducer)(state, {
743
+ type: "EVENT",
744
+ event,
745
+ threadId: "thread_1",
746
+ });
747
+ const messages = result.threadMap.thread_1.thread.messages;
748
+ expect(messages).toHaveLength(1);
749
+ expect(messages[0].id).toBe("msg_tool_tool_1");
750
+ expect(messages[0].role).toBe("assistant");
751
+ expect(messages[0].content).toHaveLength(1);
752
+ expect(messages[0].content[0]).toMatchObject({
753
+ type: "tool_use",
754
+ id: "tool_1",
755
+ name: "get_weather",
756
+ });
504
757
  });
505
758
  });
506
759
  describe("TOOL_CALL_ARGS and TOOL_CALL_END events", () => {
@@ -575,6 +828,67 @@ describe("streamReducer", () => {
575
828
  const toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
576
829
  expect(toolContent.input).toEqual({ city: "NYC" });
577
830
  });
831
+ it("optimistically parses partial tool args during streaming", () => {
832
+ const state = createTestStreamState("thread_1");
833
+ state.threadMap.thread_1.thread.messages = [
834
+ {
835
+ id: "msg_1",
836
+ role: "assistant",
837
+ content: [
838
+ { type: "tool_use", id: "tool_1", name: "test", input: {} },
839
+ ],
840
+ createdAt: "2024-01-01T00:00:00.000Z",
841
+ },
842
+ ];
843
+ // First chunk: partial key — partial-json can parse this into { city: "" }
844
+ let result = (0, event_accumulator_1.streamReducer)(state, {
845
+ type: "EVENT",
846
+ event: {
847
+ type: core_1.EventType.TOOL_CALL_ARGS,
848
+ toolCallId: "tool_1",
849
+ delta: '{"city": "N',
850
+ },
851
+ threadId: "thread_1",
852
+ });
853
+ let toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
854
+ // partial-json parses incomplete string values
855
+ expect(toolContent.input).toEqual({ city: "N" });
856
+ // Second chunk: complete city, start of units
857
+ result = (0, event_accumulator_1.streamReducer)(result, {
858
+ type: "EVENT",
859
+ event: {
860
+ type: core_1.EventType.TOOL_CALL_ARGS,
861
+ toolCallId: "tool_1",
862
+ delta: 'YC", "units": "fahr',
863
+ },
864
+ threadId: "thread_1",
865
+ });
866
+ toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
867
+ expect(toolContent.input).toEqual({ city: "NYC", units: "fahr" });
868
+ // Final chunk + TOOL_CALL_END
869
+ result = (0, event_accumulator_1.streamReducer)(result, {
870
+ type: "EVENT",
871
+ event: {
872
+ type: core_1.EventType.TOOL_CALL_ARGS,
873
+ toolCallId: "tool_1",
874
+ delta: 'enheit"}',
875
+ },
876
+ threadId: "thread_1",
877
+ });
878
+ result = (0, event_accumulator_1.streamReducer)(result, {
879
+ type: "EVENT",
880
+ event: {
881
+ type: core_1.EventType.TOOL_CALL_END,
882
+ toolCallId: "tool_1",
883
+ },
884
+ threadId: "thread_1",
885
+ });
886
+ toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
887
+ expect(toolContent.input).toEqual({
888
+ city: "NYC",
889
+ units: "fahrenheit",
890
+ });
891
+ });
578
892
  it("throws when tool arguments JSON is invalid", () => {
579
893
  const state = createTestStreamState("thread_1");
580
894
  state.threadMap.thread_1.thread.messages = [
@@ -714,7 +1028,12 @@ describe("streamReducer", () => {
714
1028
  const event = {
715
1029
  type: core_1.EventType.CUSTOM,
716
1030
  name: "tambo.run.awaiting_input",
717
- value: { pendingToolCallIds: ["tool_1", "tool_2"] },
1031
+ value: {
1032
+ pendingToolCalls: [
1033
+ { toolCallId: "tool_1", toolName: "test1", arguments: "{}" },
1034
+ { toolCallId: "tool_2", toolName: "test2", arguments: "{}" },
1035
+ ],
1036
+ },
718
1037
  };
719
1038
  const result = (0, event_accumulator_1.streamReducer)(state, {
720
1039
  type: "EVENT",
@@ -796,7 +1115,7 @@ describe("streamReducer", () => {
796
1115
  streamingState: "done",
797
1116
  });
798
1117
  });
799
- it("throws when message not found for tambo.component.start", () => {
1118
+ it("creates message on-demand when not found for tambo.component.start", () => {
800
1119
  const state = createTestStreamState("thread_1");
801
1120
  state.threadMap.thread_1.thread.messages = [
802
1121
  {
@@ -815,13 +1134,24 @@ describe("streamReducer", () => {
815
1134
  componentName: "Test",
816
1135
  },
817
1136
  };
818
- expect(() => {
819
- (0, event_accumulator_1.streamReducer)(state, {
820
- type: "EVENT",
821
- event,
822
- threadId: "thread_1",
823
- });
824
- }).toThrow("Message unknown_msg not found for tambo.component.start event");
1137
+ // Should create the message on-demand instead of throwing
1138
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1139
+ type: "EVENT",
1140
+ event,
1141
+ threadId: "thread_1",
1142
+ });
1143
+ // Verify the message was created
1144
+ const messages = result.threadMap.thread_1.thread.messages;
1145
+ expect(messages).toHaveLength(2);
1146
+ expect(messages[1].id).toBe("unknown_msg");
1147
+ expect(messages[1].role).toBe("assistant");
1148
+ // And the component was added to it
1149
+ expect(messages[1].content).toHaveLength(1);
1150
+ expect(messages[1].content[0]).toMatchObject({
1151
+ type: "component",
1152
+ id: "comp_1",
1153
+ name: "Test",
1154
+ });
825
1155
  });
826
1156
  it("throws when component not found for tambo.component.end", () => {
827
1157
  const state = createTestStreamState("thread_1");
@@ -1201,5 +1531,688 @@ describe("streamReducer", () => {
1201
1531
  expect(snapshot).toMatchSnapshot();
1202
1532
  });
1203
1533
  });
1534
+ describe("thinking events", () => {
1535
+ it("handles THINKING_TEXT_MESSAGE_START event", () => {
1536
+ const state = createTestStreamState("thread_1");
1537
+ state.threadMap.thread_1.thread.messages = [
1538
+ {
1539
+ id: "msg_1",
1540
+ role: "assistant",
1541
+ content: [],
1542
+ createdAt: "2024-01-01T00:00:00.000Z",
1543
+ },
1544
+ ];
1545
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1546
+ const event = {
1547
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1548
+ timestamp: 1704067200000,
1549
+ };
1550
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1551
+ type: "EVENT",
1552
+ event,
1553
+ threadId: "thread_1",
1554
+ });
1555
+ const message = result.threadMap.thread_1.thread.messages[0];
1556
+ expect(message.reasoning).toEqual([""]);
1557
+ expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBeDefined();
1558
+ });
1559
+ it("handles THINKING_TEXT_MESSAGE_CONTENT event", () => {
1560
+ const state = createTestStreamState("thread_1");
1561
+ state.threadMap.thread_1.thread.messages = [
1562
+ {
1563
+ id: "msg_1",
1564
+ role: "assistant",
1565
+ content: [],
1566
+ createdAt: "2024-01-01T00:00:00.000Z",
1567
+ reasoning: [""],
1568
+ },
1569
+ ];
1570
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1571
+ state.threadMap.thread_1.streaming.reasoningStartTime = 1704067200000;
1572
+ const event = {
1573
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1574
+ delta: "Let me think about this...",
1575
+ };
1576
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1577
+ type: "EVENT",
1578
+ event,
1579
+ threadId: "thread_1",
1580
+ });
1581
+ const message = result.threadMap.thread_1.thread.messages[0];
1582
+ expect(message.reasoning).toEqual(["Let me think about this..."]);
1583
+ });
1584
+ it("accumulates multiple thinking content deltas", () => {
1585
+ const state = createTestStreamState("thread_1");
1586
+ state.threadMap.thread_1.thread.messages = [
1587
+ {
1588
+ id: "msg_1",
1589
+ role: "assistant",
1590
+ content: [],
1591
+ createdAt: "2024-01-01T00:00:00.000Z",
1592
+ reasoning: ["First part "],
1593
+ },
1594
+ ];
1595
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1596
+ const event = {
1597
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1598
+ delta: "second part",
1599
+ };
1600
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1601
+ type: "EVENT",
1602
+ event,
1603
+ threadId: "thread_1",
1604
+ });
1605
+ const message = result.threadMap.thread_1.thread.messages[0];
1606
+ expect(message.reasoning).toEqual(["First part second part"]);
1607
+ });
1608
+ it("handles THINKING_TEXT_MESSAGE_END event and calculates duration", () => {
1609
+ const startTime = 1704067200000; // Fixed start time
1610
+ const endTime = 1704067205000; // 5 seconds later
1611
+ const state = createTestStreamState("thread_1");
1612
+ state.threadMap.thread_1.thread.messages = [
1613
+ {
1614
+ id: "msg_1",
1615
+ role: "assistant",
1616
+ content: [],
1617
+ createdAt: "2024-01-01T00:00:00.000Z",
1618
+ reasoning: ["Some thinking content"],
1619
+ },
1620
+ ];
1621
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1622
+ state.threadMap.thread_1.streaming.reasoningStartTime = startTime;
1623
+ const event = {
1624
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_END,
1625
+ timestamp: endTime,
1626
+ };
1627
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1628
+ type: "EVENT",
1629
+ event,
1630
+ threadId: "thread_1",
1631
+ });
1632
+ const message = result.threadMap.thread_1.thread.messages[0];
1633
+ expect(message.reasoningDurationMS).toBe(5000);
1634
+ });
1635
+ it("handles multiple thinking chunks", () => {
1636
+ let state = createTestStreamState("thread_1");
1637
+ state.threadMap.thread_1.thread.messages = [
1638
+ {
1639
+ id: "msg_1",
1640
+ role: "assistant",
1641
+ content: [],
1642
+ createdAt: "2024-01-01T00:00:00.000Z",
1643
+ },
1644
+ ];
1645
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1646
+ // First thinking chunk
1647
+ const start1 = {
1648
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1649
+ };
1650
+ state = (0, event_accumulator_1.streamReducer)(state, {
1651
+ type: "EVENT",
1652
+ event: start1,
1653
+ threadId: "thread_1",
1654
+ });
1655
+ const content1 = {
1656
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1657
+ delta: "First thought",
1658
+ };
1659
+ state = (0, event_accumulator_1.streamReducer)(state, {
1660
+ type: "EVENT",
1661
+ event: content1,
1662
+ threadId: "thread_1",
1663
+ });
1664
+ const end1 = {
1665
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_END,
1666
+ };
1667
+ state = (0, event_accumulator_1.streamReducer)(state, {
1668
+ type: "EVENT",
1669
+ event: end1,
1670
+ threadId: "thread_1",
1671
+ });
1672
+ // Second thinking chunk
1673
+ const start2 = {
1674
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1675
+ };
1676
+ state = (0, event_accumulator_1.streamReducer)(state, {
1677
+ type: "EVENT",
1678
+ event: start2,
1679
+ threadId: "thread_1",
1680
+ });
1681
+ const content2 = {
1682
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1683
+ delta: "Second thought",
1684
+ };
1685
+ state = (0, event_accumulator_1.streamReducer)(state, {
1686
+ type: "EVENT",
1687
+ event: content2,
1688
+ threadId: "thread_1",
1689
+ });
1690
+ const end2 = {
1691
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_END,
1692
+ };
1693
+ state = (0, event_accumulator_1.streamReducer)(state, {
1694
+ type: "EVENT",
1695
+ event: end2,
1696
+ threadId: "thread_1",
1697
+ });
1698
+ const message = state.threadMap.thread_1.thread.messages[0];
1699
+ expect(message.reasoning).toEqual(["First thought", "Second thought"]);
1700
+ });
1701
+ it("handles thinking content without explicit start event", () => {
1702
+ const state = createTestStreamState("thread_1");
1703
+ state.threadMap.thread_1.thread.messages = [
1704
+ {
1705
+ id: "msg_1",
1706
+ role: "assistant",
1707
+ content: [],
1708
+ createdAt: "2024-01-01T00:00:00.000Z",
1709
+ // No thinking array yet
1710
+ },
1711
+ ];
1712
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1713
+ const event = {
1714
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1715
+ delta: "Implicit start",
1716
+ };
1717
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1718
+ type: "EVENT",
1719
+ event,
1720
+ threadId: "thread_1",
1721
+ });
1722
+ const message = result.threadMap.thread_1.thread.messages[0];
1723
+ expect(message.reasoning).toEqual(["Implicit start"]);
1724
+ expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBeDefined();
1725
+ });
1726
+ it("creates ephemeral message when no message exists", () => {
1727
+ const state = createTestStreamState("thread_1");
1728
+ state.threadMap.thread_1.thread.messages = [];
1729
+ // No messageId set
1730
+ const event = {
1731
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1732
+ timestamp: 1704067200000,
1733
+ };
1734
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1735
+ type: "EVENT",
1736
+ event,
1737
+ threadId: "thread_1",
1738
+ });
1739
+ // Should have created an ephemeral assistant message with reasoning
1740
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
1741
+ const message = result.threadMap.thread_1.thread.messages[0];
1742
+ expect(message.id).toMatch(/^ephemeral_/);
1743
+ expect(message.role).toBe("assistant");
1744
+ expect(message.reasoning).toEqual([""]);
1745
+ expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBe(1704067200000);
1746
+ expect(result.threadMap.thread_1.streaming.messageId).toBe(message.id);
1747
+ });
1748
+ it("uses last message when no messageId in streaming state", () => {
1749
+ const state = createTestStreamState("thread_1");
1750
+ state.threadMap.thread_1.thread.messages = [
1751
+ {
1752
+ id: "msg_1",
1753
+ role: "assistant",
1754
+ content: [],
1755
+ createdAt: "2024-01-01T00:00:00.000Z",
1756
+ },
1757
+ ];
1758
+ // No messageId set in streaming state
1759
+ const event = {
1760
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1761
+ };
1762
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1763
+ type: "EVENT",
1764
+ event,
1765
+ threadId: "thread_1",
1766
+ });
1767
+ const message = result.threadMap.thread_1.thread.messages[0];
1768
+ expect(message.reasoning).toEqual([""]);
1769
+ });
1770
+ it("merges ephemeral reasoning message with subsequent TEXT_MESSAGE_START", () => {
1771
+ let state = createTestStreamState("thread_1");
1772
+ state.threadMap.thread_1.thread.messages = [];
1773
+ // Simulate reasoning events arriving before text message
1774
+ const thinkingStart = {
1775
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1776
+ timestamp: 1704067200000,
1777
+ };
1778
+ state = (0, event_accumulator_1.streamReducer)(state, {
1779
+ type: "EVENT",
1780
+ event: thinkingStart,
1781
+ threadId: "thread_1",
1782
+ });
1783
+ const thinkingContent = {
1784
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1785
+ delta: "Let me think about this...",
1786
+ };
1787
+ state = (0, event_accumulator_1.streamReducer)(state, {
1788
+ type: "EVENT",
1789
+ event: thinkingContent,
1790
+ threadId: "thread_1",
1791
+ });
1792
+ // Verify ephemeral message was created
1793
+ expect(state.threadMap.thread_1.thread.messages).toHaveLength(1);
1794
+ const ephemeralMessage = state.threadMap.thread_1.thread.messages[0];
1795
+ expect(ephemeralMessage.id).toMatch(/^ephemeral_/);
1796
+ expect(ephemeralMessage.reasoning).toEqual([
1797
+ "Let me think about this...",
1798
+ ]);
1799
+ // Now TEXT_MESSAGE_START arrives - should merge with ephemeral message
1800
+ const textStart = {
1801
+ type: core_1.EventType.TEXT_MESSAGE_START,
1802
+ messageId: "msg_real_123",
1803
+ role: "assistant",
1804
+ };
1805
+ state = (0, event_accumulator_1.streamReducer)(state, {
1806
+ type: "EVENT",
1807
+ event: textStart,
1808
+ threadId: "thread_1",
1809
+ });
1810
+ // Should still have only one message (merged)
1811
+ expect(state.threadMap.thread_1.thread.messages).toHaveLength(1);
1812
+ const mergedMessage = state.threadMap.thread_1.thread.messages[0];
1813
+ // The message should have the real ID now
1814
+ expect(mergedMessage.id).toBe("msg_real_123");
1815
+ // But should preserve the reasoning
1816
+ expect(mergedMessage.reasoning).toEqual(["Let me think about this..."]);
1817
+ expect(mergedMessage.role).toBe("assistant");
1818
+ // Streaming state should track the new message ID
1819
+ expect(state.threadMap.thread_1.streaming.messageId).toBe("msg_real_123");
1820
+ });
1821
+ it("matches snapshot for full thinking flow", () => {
1822
+ let state = createTestStreamState("thread_1");
1823
+ // Add an assistant message
1824
+ state.threadMap.thread_1.thread.messages = [
1825
+ {
1826
+ id: "msg_1",
1827
+ role: "assistant",
1828
+ content: [],
1829
+ createdAt: "2024-01-01T00:00:00.000Z",
1830
+ },
1831
+ ];
1832
+ state.threadMap.thread_1.streaming.messageId = "msg_1";
1833
+ // THINKING_TEXT_MESSAGE_START
1834
+ const thinkingStart = {
1835
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_START,
1836
+ timestamp: 1704067200000,
1837
+ };
1838
+ state = (0, event_accumulator_1.streamReducer)(state, {
1839
+ type: "EVENT",
1840
+ event: thinkingStart,
1841
+ threadId: "thread_1",
1842
+ });
1843
+ // THINKING_TEXT_MESSAGE_CONTENT
1844
+ const thinkingContent = {
1845
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_CONTENT,
1846
+ delta: "Let me analyze this step by step...",
1847
+ };
1848
+ state = (0, event_accumulator_1.streamReducer)(state, {
1849
+ type: "EVENT",
1850
+ event: thinkingContent,
1851
+ threadId: "thread_1",
1852
+ });
1853
+ // THINKING_TEXT_MESSAGE_END
1854
+ const thinkingEnd = {
1855
+ type: core_1.EventType.THINKING_TEXT_MESSAGE_END,
1856
+ };
1857
+ state = (0, event_accumulator_1.streamReducer)(state, {
1858
+ type: "EVENT",
1859
+ event: thinkingEnd,
1860
+ threadId: "thread_1",
1861
+ });
1862
+ // TEXT_MESSAGE_CONTENT (actual response after thinking)
1863
+ const msgContent = {
1864
+ type: core_1.EventType.TEXT_MESSAGE_CONTENT,
1865
+ messageId: "msg_1",
1866
+ delta: "Based on my analysis, here's what I think...",
1867
+ };
1868
+ state = (0, event_accumulator_1.streamReducer)(state, {
1869
+ type: "EVENT",
1870
+ event: msgContent,
1871
+ threadId: "thread_1",
1872
+ });
1873
+ // Normalize for snapshot stability
1874
+ const snapshot = {
1875
+ ...state,
1876
+ threadMap: {
1877
+ thread_1: {
1878
+ ...state.threadMap.thread_1,
1879
+ thread: {
1880
+ ...state.threadMap.thread_1.thread,
1881
+ messages: state.threadMap.thread_1.thread.messages.map((m) => ({
1882
+ ...m,
1883
+ createdAt: "[TIMESTAMP]",
1884
+ // Keep reasoningDurationMS but normalize it for snapshot
1885
+ reasoningDurationMS: m.reasoningDurationMS
1886
+ ? "[DURATION]"
1887
+ : undefined,
1888
+ })),
1889
+ createdAt: "[TIMESTAMP]",
1890
+ updatedAt: "[TIMESTAMP]",
1891
+ },
1892
+ streaming: {
1893
+ ...state.threadMap.thread_1.streaming,
1894
+ reasoningStartTime: state.threadMap.thread_1.streaming
1895
+ .reasoningStartTime
1896
+ ? "[TIMESTAMP]"
1897
+ : undefined,
1898
+ },
1899
+ },
1900
+ },
1901
+ };
1902
+ expect(snapshot).toMatchSnapshot();
1903
+ });
1904
+ });
1905
+ describe("LOAD_THREAD_MESSAGES action", () => {
1906
+ it("loads messages into empty thread", () => {
1907
+ const state = createTestStreamState("thread_1");
1908
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1909
+ type: "LOAD_THREAD_MESSAGES",
1910
+ threadId: "thread_1",
1911
+ messages: [
1912
+ {
1913
+ id: "msg_1",
1914
+ role: "user",
1915
+ content: [{ type: "text", text: "Hello" }],
1916
+ createdAt: "2024-01-01T00:00:00.000Z",
1917
+ },
1918
+ {
1919
+ id: "msg_2",
1920
+ role: "assistant",
1921
+ content: [{ type: "text", text: "Hi there!" }],
1922
+ createdAt: "2024-01-01T00:00:01.000Z",
1923
+ },
1924
+ ],
1925
+ });
1926
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
1927
+ expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_1");
1928
+ expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
1929
+ });
1930
+ it("creates thread if it does not exist", () => {
1931
+ const state = (0, event_accumulator_1.createInitialState)();
1932
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1933
+ type: "LOAD_THREAD_MESSAGES",
1934
+ threadId: "new_thread",
1935
+ messages: [
1936
+ {
1937
+ id: "msg_1",
1938
+ role: "user",
1939
+ content: [{ type: "text", text: "Hello" }],
1940
+ createdAt: "2024-01-01T00:00:00.000Z",
1941
+ },
1942
+ ],
1943
+ });
1944
+ expect(result.threadMap.new_thread).toBeDefined();
1945
+ expect(result.threadMap.new_thread.thread.id).toBe("new_thread");
1946
+ expect(result.threadMap.new_thread.thread.messages).toHaveLength(1);
1947
+ });
1948
+ it("deduplicates by message ID, keeping existing messages", () => {
1949
+ const state = createTestStreamState("thread_1");
1950
+ // Add an existing message
1951
+ state.threadMap.thread_1.thread.messages = [
1952
+ {
1953
+ id: "msg_1",
1954
+ role: "user",
1955
+ content: [{ type: "text", text: "Existing content" }],
1956
+ createdAt: "2024-01-01T00:00:00.000Z",
1957
+ },
1958
+ ];
1959
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1960
+ type: "LOAD_THREAD_MESSAGES",
1961
+ threadId: "thread_1",
1962
+ messages: [
1963
+ {
1964
+ id: "msg_1",
1965
+ role: "user",
1966
+ content: [{ type: "text", text: "New content" }], // Different content
1967
+ createdAt: "2024-01-01T00:00:00.000Z",
1968
+ },
1969
+ {
1970
+ id: "msg_2",
1971
+ role: "assistant",
1972
+ content: [{ type: "text", text: "Response" }],
1973
+ createdAt: "2024-01-01T00:00:01.000Z",
1974
+ },
1975
+ ],
1976
+ });
1977
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
1978
+ // Existing message is kept (not replaced)
1979
+ expect(result.threadMap.thread_1.thread.messages[0].content[0]).toEqual({
1980
+ type: "text",
1981
+ text: "Existing content",
1982
+ });
1983
+ // New message is added
1984
+ expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
1985
+ });
1986
+ it("skips merge when streaming and skipIfStreaming is true", () => {
1987
+ const state = createTestStreamState("thread_1");
1988
+ state.threadMap.thread_1.streaming.status = "streaming";
1989
+ state.threadMap.thread_1.thread.messages = [
1990
+ {
1991
+ id: "msg_1",
1992
+ role: "user",
1993
+ content: [{ type: "text", text: "Hello" }],
1994
+ createdAt: "2024-01-01T00:00:00.000Z",
1995
+ },
1996
+ ];
1997
+ const result = (0, event_accumulator_1.streamReducer)(state, {
1998
+ type: "LOAD_THREAD_MESSAGES",
1999
+ threadId: "thread_1",
2000
+ messages: [
2001
+ {
2002
+ id: "msg_2",
2003
+ role: "assistant",
2004
+ content: [{ type: "text", text: "Response" }],
2005
+ createdAt: "2024-01-01T00:00:01.000Z",
2006
+ },
2007
+ ],
2008
+ skipIfStreaming: true,
2009
+ });
2010
+ // State should be unchanged
2011
+ expect(result).toBe(state);
2012
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
2013
+ });
2014
+ it("does merge when streaming and skipIfStreaming is false", () => {
2015
+ const state = createTestStreamState("thread_1");
2016
+ state.threadMap.thread_1.streaming.status = "streaming";
2017
+ state.threadMap.thread_1.thread.messages = [];
2018
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2019
+ type: "LOAD_THREAD_MESSAGES",
2020
+ threadId: "thread_1",
2021
+ messages: [
2022
+ {
2023
+ id: "msg_1",
2024
+ role: "user",
2025
+ content: [{ type: "text", text: "Hello" }],
2026
+ createdAt: "2024-01-01T00:00:00.000Z",
2027
+ },
2028
+ ],
2029
+ skipIfStreaming: false,
2030
+ });
2031
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
2032
+ });
2033
+ it("sorts messages by createdAt", () => {
2034
+ const state = createTestStreamState("thread_1");
2035
+ state.threadMap.thread_1.thread.messages = [
2036
+ {
2037
+ id: "msg_2",
2038
+ role: "assistant",
2039
+ content: [{ type: "text", text: "Response" }],
2040
+ createdAt: "2024-01-01T00:00:02.000Z",
2041
+ },
2042
+ ];
2043
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2044
+ type: "LOAD_THREAD_MESSAGES",
2045
+ threadId: "thread_1",
2046
+ messages: [
2047
+ {
2048
+ id: "msg_1",
2049
+ role: "user",
2050
+ content: [{ type: "text", text: "Hello" }],
2051
+ createdAt: "2024-01-01T00:00:01.000Z",
2052
+ },
2053
+ {
2054
+ id: "msg_3",
2055
+ role: "assistant",
2056
+ content: [{ type: "text", text: "Goodbye" }],
2057
+ createdAt: "2024-01-01T00:00:03.000Z",
2058
+ },
2059
+ ],
2060
+ });
2061
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(3);
2062
+ // Should be sorted by createdAt
2063
+ expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_1");
2064
+ expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
2065
+ expect(result.threadMap.thread_1.thread.messages[2].id).toBe("msg_3");
2066
+ });
2067
+ it("handles messages without createdAt (places them at the end)", () => {
2068
+ const state = createTestStreamState("thread_1");
2069
+ state.threadMap.thread_1.thread.messages = [];
2070
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2071
+ type: "LOAD_THREAD_MESSAGES",
2072
+ threadId: "thread_1",
2073
+ messages: [
2074
+ {
2075
+ id: "msg_no_date",
2076
+ role: "assistant",
2077
+ content: [{ type: "text", text: "No date" }],
2078
+ // No createdAt
2079
+ },
2080
+ {
2081
+ id: "msg_with_date",
2082
+ role: "user",
2083
+ content: [{ type: "text", text: "Has date" }],
2084
+ createdAt: "2024-01-01T00:00:00.000Z",
2085
+ },
2086
+ ],
2087
+ });
2088
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
2089
+ // Message with date comes first, no date goes to end
2090
+ expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_with_date");
2091
+ expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_no_date");
2092
+ });
2093
+ it("sets streamingState to 'done' on component content blocks without streamingState", () => {
2094
+ const state = createTestStreamState("thread_1");
2095
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2096
+ type: "LOAD_THREAD_MESSAGES",
2097
+ threadId: "thread_1",
2098
+ messages: [
2099
+ {
2100
+ id: "msg_1",
2101
+ role: "assistant",
2102
+ content: [
2103
+ { type: "text", text: "Here is a component" },
2104
+ {
2105
+ type: "component",
2106
+ id: "comp_1",
2107
+ name: "WeatherCard",
2108
+ props: { city: "SF", temperature: 72 },
2109
+ // No streamingState — simulates API-loaded message
2110
+ },
2111
+ ],
2112
+ createdAt: "2024-01-01T00:00:00.000Z",
2113
+ },
2114
+ ],
2115
+ });
2116
+ const messages = result.threadMap.thread_1.thread.messages;
2117
+ expect(messages).toHaveLength(1);
2118
+ const componentBlock = messages[0].content.find((c) => c.type === "component");
2119
+ expect(componentBlock).toBeDefined();
2120
+ expect(componentBlock.type).toBe("component");
2121
+ expect(componentBlock.streamingState).toBe("done");
2122
+ });
2123
+ it("overwrites non-done streamingState and warns", () => {
2124
+ const state = createTestStreamState("thread_1");
2125
+ const warnSpy = jest.spyOn(console, "warn").mockImplementation();
2126
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2127
+ type: "LOAD_THREAD_MESSAGES",
2128
+ threadId: "thread_1",
2129
+ messages: [
2130
+ {
2131
+ id: "msg_1",
2132
+ role: "assistant",
2133
+ content: [
2134
+ {
2135
+ type: "component",
2136
+ id: "comp_1",
2137
+ name: "WeatherCard",
2138
+ props: { city: "SF" },
2139
+ streamingState: "streaming",
2140
+ },
2141
+ ],
2142
+ createdAt: "2024-01-01T00:00:00.000Z",
2143
+ },
2144
+ ],
2145
+ });
2146
+ const messages = result.threadMap.thread_1.thread.messages;
2147
+ const componentBlock = messages[0].content.find((c) => c.type === "component");
2148
+ // Always set to "done" for API-loaded messages
2149
+ expect(componentBlock.streamingState).toBe("done");
2150
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('unexpected streamingState "streaming"'));
2151
+ warnSpy.mockRestore();
2152
+ });
2153
+ it("handles system role messages from API", () => {
2154
+ const state = createTestStreamState("thread_1");
2155
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2156
+ type: "LOAD_THREAD_MESSAGES",
2157
+ threadId: "thread_1",
2158
+ messages: [
2159
+ {
2160
+ id: "msg_system",
2161
+ role: "system",
2162
+ content: [{ type: "text", text: "System prompt" }],
2163
+ createdAt: "2024-01-01T00:00:00.000Z",
2164
+ },
2165
+ {
2166
+ id: "msg_user",
2167
+ role: "user",
2168
+ content: [{ type: "text", text: "Hello" }],
2169
+ createdAt: "2024-01-01T00:00:01.000Z",
2170
+ },
2171
+ ],
2172
+ });
2173
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
2174
+ expect(result.threadMap.thread_1.thread.messages[0].role).toBe("system");
2175
+ expect(result.threadMap.thread_1.thread.messages[1].role).toBe("user");
2176
+ });
2177
+ });
2178
+ describe("UPDATE_THREAD_TITLE action", () => {
2179
+ it("updates the title on an existing thread", () => {
2180
+ const state = createTestStreamState("thread_1");
2181
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2182
+ type: "UPDATE_THREAD_TITLE",
2183
+ threadId: "thread_1",
2184
+ title: "My Chat Thread",
2185
+ });
2186
+ expect(result.threadMap.thread_1.thread.title).toBe("My Chat Thread");
2187
+ });
2188
+ it("returns unchanged state when thread does not exist", () => {
2189
+ const state = createTestStreamState("thread_1");
2190
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2191
+ type: "UPDATE_THREAD_TITLE",
2192
+ threadId: "nonexistent_thread",
2193
+ title: "My Chat Thread",
2194
+ });
2195
+ expect(result).toBe(state);
2196
+ });
2197
+ it("preserves other thread properties when updating title", () => {
2198
+ const state = createTestStreamState("thread_1");
2199
+ state.threadMap.thread_1.thread.messages = [
2200
+ {
2201
+ id: "msg_1",
2202
+ role: "user",
2203
+ content: [{ type: "text", text: "Hello" }],
2204
+ createdAt: "2024-01-01T00:00:00.000Z",
2205
+ },
2206
+ ];
2207
+ const result = (0, event_accumulator_1.streamReducer)(state, {
2208
+ type: "UPDATE_THREAD_TITLE",
2209
+ threadId: "thread_1",
2210
+ title: "My Chat Thread",
2211
+ });
2212
+ expect(result.threadMap.thread_1.thread.title).toBe("My Chat Thread");
2213
+ expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
2214
+ expect(result.threadMap.thread_1.thread.id).toBe("thread_1");
2215
+ });
2216
+ });
1204
2217
  });
1205
2218
  //# sourceMappingURL=event-accumulator.test.js.map