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