@tambo-ai/react 0.69.1 → 0.71.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 (402) hide show
  1. package/README.md +7 -7
  2. package/dist/hooks/use-tambo-threads.test.js.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/mcp/index.d.ts +4 -5
  7. package/dist/mcp/index.d.ts.map +1 -1
  8. package/dist/mcp/index.js +4 -5
  9. package/dist/mcp/index.js.map +1 -1
  10. package/dist/model/component-metadata.d.ts +88 -241
  11. package/dist/model/component-metadata.d.ts.map +1 -1
  12. package/dist/model/component-metadata.js.map +1 -1
  13. package/dist/model/mcp-server-info.d.ts +3 -3
  14. package/dist/model/mcp-server-info.js.map +1 -1
  15. package/dist/providers/hooks/use-tambo-session-token.test.js.map +1 -1
  16. package/dist/providers/tambo-component-provider.d.ts +2 -2
  17. package/dist/providers/tambo-component-provider.d.ts.map +1 -1
  18. package/dist/providers/tambo-component-provider.js.map +1 -1
  19. package/dist/providers/tambo-interactable-provider.d.ts +1 -1
  20. package/dist/providers/tambo-registry-provider.d.ts +4 -4
  21. package/dist/providers/tambo-registry-provider.d.ts.map +1 -1
  22. package/dist/providers/tambo-registry-provider.js +11 -8
  23. package/dist/providers/tambo-registry-provider.js.map +1 -1
  24. package/dist/providers/tambo-registry-provider.test.js +31 -0
  25. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  26. package/dist/providers/tambo-registry-schema-compat.test.js +42 -52
  27. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
  28. package/dist/providers/tambo-stubs.d.ts +2 -2
  29. package/dist/providers/tambo-stubs.d.ts.map +1 -1
  30. package/dist/providers/tambo-stubs.js.map +1 -1
  31. package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  32. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  33. package/dist/providers/tambo-thread-provider.js +107 -141
  34. package/dist/providers/tambo-thread-provider.js.map +1 -1
  35. package/dist/providers/tambo-thread-provider.test.js +274 -445
  36. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  37. package/dist/schema/index.d.ts +1 -2
  38. package/dist/schema/index.d.ts.map +1 -1
  39. package/dist/schema/index.js +1 -5
  40. package/dist/schema/index.js.map +1 -1
  41. package/dist/schema/schema.d.ts +7 -24
  42. package/dist/schema/schema.d.ts.map +1 -1
  43. package/dist/schema/schema.js +34 -105
  44. package/dist/schema/schema.js.map +1 -1
  45. package/dist/schema/schema.test.js +26 -124
  46. package/dist/schema/schema.test.js.map +1 -1
  47. package/dist/testing/tools.d.ts +2 -12
  48. package/dist/testing/tools.d.ts.map +1 -1
  49. package/dist/testing/tools.js +1 -20
  50. package/dist/testing/tools.js.map +1 -1
  51. package/dist/testing/types.d.ts +2 -2
  52. package/dist/testing/types.d.ts.map +1 -1
  53. package/dist/testing/types.js.map +1 -1
  54. package/dist/util/registry-validators.d.ts +2 -2
  55. package/dist/util/registry-validators.d.ts.map +1 -1
  56. package/dist/util/registry-validators.js +37 -17
  57. package/dist/util/registry-validators.js.map +1 -1
  58. package/dist/util/registry-validators.test.js +64 -25
  59. package/dist/util/registry-validators.test.js.map +1 -1
  60. package/dist/util/registry.d.ts +4 -10
  61. package/dist/util/registry.d.ts.map +1 -1
  62. package/dist/util/registry.js +6 -22
  63. package/dist/util/registry.js.map +1 -1
  64. package/dist/util/registry.test.js +1 -47
  65. package/dist/util/registry.test.js.map +1 -1
  66. package/dist/util/tool-caller.d.ts +2 -2
  67. package/dist/util/tool-caller.d.ts.map +1 -1
  68. package/dist/util/tool-caller.js +5 -12
  69. package/dist/util/tool-caller.js.map +1 -1
  70. package/dist/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  71. package/dist/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  72. package/dist/v1/hooks/use-tambo-v1-messages.js +54 -0
  73. package/dist/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  74. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  75. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  76. package/dist/v1/hooks/use-tambo-v1-messages.test.js +137 -0
  77. package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  78. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  79. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  80. package/dist/v1/hooks/use-tambo-v1-send-message.js +227 -0
  81. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  82. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  83. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  84. package/dist/v1/hooks/use-tambo-v1-send-message.test.js +827 -0
  85. package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  86. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  87. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  88. package/dist/v1/hooks/use-tambo-v1-thread-list.js +56 -0
  89. package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  90. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  91. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  92. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +98 -0
  93. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  94. package/dist/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  95. package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  96. package/dist/v1/hooks/use-tambo-v1-thread.js +49 -0
  97. package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  98. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  99. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  100. package/dist/v1/hooks/use-tambo-v1-thread.test.js +83 -0
  101. package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  102. package/dist/v1/hooks/use-tambo-v1.d.ts +107 -0
  103. package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  104. package/dist/v1/hooks/use-tambo-v1.js +87 -0
  105. package/dist/v1/hooks/use-tambo-v1.js.map +1 -0
  106. package/dist/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  107. package/dist/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  108. package/dist/v1/hooks/use-tambo-v1.test.js +150 -0
  109. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -0
  110. package/dist/v1/index.d.ts +73 -0
  111. package/dist/v1/index.d.ts.map +1 -0
  112. package/dist/v1/index.js +106 -0
  113. package/dist/v1/index.js.map +1 -0
  114. package/dist/v1/providers/tambo-v1-provider.d.ts +91 -0
  115. package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  116. package/dist/v1/providers/tambo-v1-provider.js +110 -0
  117. package/dist/v1/providers/tambo-v1-provider.js.map +1 -0
  118. package/dist/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  119. package/dist/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  120. package/dist/v1/providers/tambo-v1-provider.test.js +123 -0
  121. package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -0
  122. package/dist/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  123. package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  124. package/dist/v1/providers/tambo-v1-stream-context.js +230 -0
  125. package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -0
  126. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  127. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  128. package/dist/v1/providers/tambo-v1-stream-context.test.js +85 -0
  129. package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  130. package/dist/v1/types/component.d.ts +50 -0
  131. package/dist/v1/types/component.d.ts.map +1 -0
  132. package/dist/v1/types/component.js +14 -0
  133. package/dist/v1/types/component.js.map +1 -0
  134. package/dist/v1/types/event.d.ts +72 -0
  135. package/dist/v1/types/event.d.ts.map +1 -0
  136. package/dist/v1/types/event.js +54 -0
  137. package/dist/v1/types/event.js.map +1 -0
  138. package/dist/v1/types/event.test.d.ts +2 -0
  139. package/dist/v1/types/event.test.d.ts.map +1 -0
  140. package/dist/v1/types/event.test.js +70 -0
  141. package/dist/v1/types/event.test.js.map +1 -0
  142. package/dist/v1/types/message.d.ts +35 -0
  143. package/dist/v1/types/message.d.ts.map +1 -0
  144. package/dist/v1/types/message.js +10 -0
  145. package/dist/v1/types/message.js.map +1 -0
  146. package/dist/v1/types/thread.d.ts +52 -0
  147. package/dist/v1/types/thread.d.ts.map +1 -0
  148. package/dist/v1/types/thread.js +9 -0
  149. package/dist/v1/types/thread.js.map +1 -0
  150. package/dist/v1/utils/event-accumulator.d.ts +100 -0
  151. package/dist/v1/utils/event-accumulator.d.ts.map +1 -0
  152. package/dist/v1/utils/event-accumulator.js +715 -0
  153. package/dist/v1/utils/event-accumulator.js.map +1 -0
  154. package/dist/v1/utils/event-accumulator.test.d.ts +2 -0
  155. package/dist/v1/utils/event-accumulator.test.d.ts.map +1 -0
  156. package/dist/v1/utils/event-accumulator.test.js +1010 -0
  157. package/dist/v1/utils/event-accumulator.test.js.map +1 -0
  158. package/dist/v1/utils/json-patch.d.ts +18 -0
  159. package/dist/v1/utils/json-patch.d.ts.map +1 -0
  160. package/dist/v1/utils/json-patch.js +35 -0
  161. package/dist/v1/utils/json-patch.js.map +1 -0
  162. package/dist/v1/utils/json-patch.test.d.ts +2 -0
  163. package/dist/v1/utils/json-patch.test.d.ts.map +1 -0
  164. package/dist/v1/utils/json-patch.test.js +28 -0
  165. package/dist/v1/utils/json-patch.test.js.map +1 -0
  166. package/dist/v1/utils/registry-conversion.d.ts +53 -0
  167. package/dist/v1/utils/registry-conversion.d.ts.map +1 -0
  168. package/dist/v1/utils/registry-conversion.js +114 -0
  169. package/dist/v1/utils/registry-conversion.js.map +1 -0
  170. package/dist/v1/utils/registry-conversion.test.d.ts +2 -0
  171. package/dist/v1/utils/registry-conversion.test.d.ts.map +1 -0
  172. package/dist/v1/utils/registry-conversion.test.js +179 -0
  173. package/dist/v1/utils/registry-conversion.test.js.map +1 -0
  174. package/dist/v1/utils/stream-handler.d.ts +45 -0
  175. package/dist/v1/utils/stream-handler.d.ts.map +1 -0
  176. package/dist/v1/utils/stream-handler.js +47 -0
  177. package/dist/v1/utils/stream-handler.js.map +1 -0
  178. package/dist/v1/utils/stream-handler.test.d.ts +2 -0
  179. package/dist/v1/utils/stream-handler.test.d.ts.map +1 -0
  180. package/dist/v1/utils/stream-handler.test.js +74 -0
  181. package/dist/v1/utils/stream-handler.test.js.map +1 -0
  182. package/dist/v1/utils/tool-call-tracker.d.ts +41 -0
  183. package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -0
  184. package/dist/v1/utils/tool-call-tracker.js +90 -0
  185. package/dist/v1/utils/tool-call-tracker.js.map +1 -0
  186. package/dist/v1/utils/tool-executor.d.ts +33 -0
  187. package/dist/v1/utils/tool-executor.d.ts.map +1 -0
  188. package/dist/v1/utils/tool-executor.js +103 -0
  189. package/dist/v1/utils/tool-executor.js.map +1 -0
  190. package/dist/v1/utils/tool-executor.test.d.ts +2 -0
  191. package/dist/v1/utils/tool-executor.test.d.ts.map +1 -0
  192. package/dist/v1/utils/tool-executor.test.js +222 -0
  193. package/dist/v1/utils/tool-executor.test.js.map +1 -0
  194. package/esm/hooks/use-tambo-threads.test.js.map +1 -1
  195. package/esm/index.d.ts +1 -1
  196. package/esm/index.d.ts.map +1 -1
  197. package/esm/index.js.map +1 -1
  198. package/esm/mcp/index.d.ts +4 -5
  199. package/esm/mcp/index.d.ts.map +1 -1
  200. package/esm/mcp/index.js +4 -5
  201. package/esm/mcp/index.js.map +1 -1
  202. package/esm/model/component-metadata.d.ts +88 -241
  203. package/esm/model/component-metadata.d.ts.map +1 -1
  204. package/esm/model/component-metadata.js.map +1 -1
  205. package/esm/model/mcp-server-info.d.ts +3 -3
  206. package/esm/model/mcp-server-info.js.map +1 -1
  207. package/esm/providers/hooks/use-tambo-session-token.test.js.map +1 -1
  208. package/esm/providers/tambo-component-provider.d.ts +2 -2
  209. package/esm/providers/tambo-component-provider.d.ts.map +1 -1
  210. package/esm/providers/tambo-component-provider.js.map +1 -1
  211. package/esm/providers/tambo-interactable-provider.d.ts +1 -1
  212. package/esm/providers/tambo-registry-provider.d.ts +4 -4
  213. package/esm/providers/tambo-registry-provider.d.ts.map +1 -1
  214. package/esm/providers/tambo-registry-provider.js +11 -8
  215. package/esm/providers/tambo-registry-provider.js.map +1 -1
  216. package/esm/providers/tambo-registry-provider.test.js +31 -0
  217. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  218. package/esm/providers/tambo-registry-schema-compat.test.js +42 -52
  219. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
  220. package/esm/providers/tambo-stubs.d.ts +2 -2
  221. package/esm/providers/tambo-stubs.d.ts.map +1 -1
  222. package/esm/providers/tambo-stubs.js.map +1 -1
  223. package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  224. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  225. package/esm/providers/tambo-thread-provider.js +107 -141
  226. package/esm/providers/tambo-thread-provider.js.map +1 -1
  227. package/esm/providers/tambo-thread-provider.test.js +241 -445
  228. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  229. package/esm/schema/index.d.ts +1 -2
  230. package/esm/schema/index.d.ts.map +1 -1
  231. package/esm/schema/index.js +1 -2
  232. package/esm/schema/index.js.map +1 -1
  233. package/esm/schema/schema.d.ts +7 -24
  234. package/esm/schema/schema.d.ts.map +1 -1
  235. package/esm/schema/schema.js +34 -103
  236. package/esm/schema/schema.js.map +1 -1
  237. package/esm/schema/schema.test.js +27 -125
  238. package/esm/schema/schema.test.js.map +1 -1
  239. package/esm/testing/tools.d.ts +2 -12
  240. package/esm/testing/tools.d.ts.map +1 -1
  241. package/esm/testing/tools.js +2 -20
  242. package/esm/testing/tools.js.map +1 -1
  243. package/esm/testing/types.d.ts +2 -2
  244. package/esm/testing/types.d.ts.map +1 -1
  245. package/esm/testing/types.js.map +1 -1
  246. package/esm/util/registry-validators.d.ts +2 -2
  247. package/esm/util/registry-validators.d.ts.map +1 -1
  248. package/esm/util/registry-validators.js +38 -18
  249. package/esm/util/registry-validators.js.map +1 -1
  250. package/esm/util/registry-validators.test.js +64 -25
  251. package/esm/util/registry-validators.test.js.map +1 -1
  252. package/esm/util/registry.d.ts +4 -10
  253. package/esm/util/registry.d.ts.map +1 -1
  254. package/esm/util/registry.js +7 -22
  255. package/esm/util/registry.js.map +1 -1
  256. package/esm/util/registry.test.js +3 -49
  257. package/esm/util/registry.test.js.map +1 -1
  258. package/esm/util/tool-caller.d.ts +2 -2
  259. package/esm/util/tool-caller.d.ts.map +1 -1
  260. package/esm/util/tool-caller.js +5 -12
  261. package/esm/util/tool-caller.js.map +1 -1
  262. package/esm/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  263. package/esm/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  264. package/esm/v1/hooks/use-tambo-v1-messages.js +51 -0
  265. package/esm/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  266. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  267. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  268. package/esm/v1/hooks/use-tambo-v1-messages.test.js +132 -0
  269. package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  270. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  271. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  272. package/esm/v1/hooks/use-tambo-v1-send-message.js +223 -0
  273. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  274. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  275. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  276. package/esm/v1/hooks/use-tambo-v1-send-message.test.js +822 -0
  277. package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  278. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  279. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  280. package/esm/v1/hooks/use-tambo-v1-thread-list.js +53 -0
  281. package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  282. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  283. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  284. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +93 -0
  285. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  286. package/esm/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  287. package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  288. package/esm/v1/hooks/use-tambo-v1-thread.js +46 -0
  289. package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  290. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  291. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  292. package/esm/v1/hooks/use-tambo-v1-thread.test.js +78 -0
  293. package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  294. package/esm/v1/hooks/use-tambo-v1.d.ts +107 -0
  295. package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  296. package/esm/v1/hooks/use-tambo-v1.js +84 -0
  297. package/esm/v1/hooks/use-tambo-v1.js.map +1 -0
  298. package/esm/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  299. package/esm/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  300. package/esm/v1/hooks/use-tambo-v1.test.js +145 -0
  301. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -0
  302. package/esm/v1/index.d.ts +73 -0
  303. package/esm/v1/index.d.ts.map +1 -0
  304. package/esm/v1/index.js +83 -0
  305. package/esm/v1/index.js.map +1 -0
  306. package/esm/v1/providers/tambo-v1-provider.d.ts +91 -0
  307. package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  308. package/esm/v1/providers/tambo-v1-provider.js +74 -0
  309. package/esm/v1/providers/tambo-v1-provider.js.map +1 -0
  310. package/esm/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  311. package/esm/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  312. package/esm/v1/providers/tambo-v1-provider.test.js +118 -0
  313. package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -0
  314. package/esm/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  315. package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  316. package/esm/v1/providers/tambo-v1-stream-context.js +191 -0
  317. package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -0
  318. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  319. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  320. package/esm/v1/providers/tambo-v1-stream-context.test.js +80 -0
  321. package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  322. package/esm/v1/types/component.d.ts +50 -0
  323. package/esm/v1/types/component.d.ts.map +1 -0
  324. package/esm/v1/types/component.js +13 -0
  325. package/esm/v1/types/component.js.map +1 -0
  326. package/esm/v1/types/event.d.ts +72 -0
  327. package/esm/v1/types/event.d.ts.map +1 -0
  328. package/esm/v1/types/event.js +50 -0
  329. package/esm/v1/types/event.js.map +1 -0
  330. package/esm/v1/types/event.test.d.ts +2 -0
  331. package/esm/v1/types/event.test.d.ts.map +1 -0
  332. package/esm/v1/types/event.test.js +68 -0
  333. package/esm/v1/types/event.test.js.map +1 -0
  334. package/esm/v1/types/message.d.ts +35 -0
  335. package/esm/v1/types/message.d.ts.map +1 -0
  336. package/esm/v1/types/message.js +9 -0
  337. package/esm/v1/types/message.js.map +1 -0
  338. package/esm/v1/types/thread.d.ts +52 -0
  339. package/esm/v1/types/thread.d.ts.map +1 -0
  340. package/esm/v1/types/thread.js +8 -0
  341. package/esm/v1/types/thread.js.map +1 -0
  342. package/esm/v1/utils/event-accumulator.d.ts +100 -0
  343. package/esm/v1/utils/event-accumulator.d.ts.map +1 -0
  344. package/esm/v1/utils/event-accumulator.js +708 -0
  345. package/esm/v1/utils/event-accumulator.js.map +1 -0
  346. package/esm/v1/utils/event-accumulator.test.d.ts +2 -0
  347. package/esm/v1/utils/event-accumulator.test.d.ts.map +1 -0
  348. package/esm/v1/utils/event-accumulator.test.js +1008 -0
  349. package/esm/v1/utils/event-accumulator.test.js.map +1 -0
  350. package/esm/v1/utils/json-patch.d.ts +18 -0
  351. package/esm/v1/utils/json-patch.d.ts.map +1 -0
  352. package/esm/v1/utils/json-patch.js +32 -0
  353. package/esm/v1/utils/json-patch.js.map +1 -0
  354. package/esm/v1/utils/json-patch.test.d.ts +2 -0
  355. package/esm/v1/utils/json-patch.test.d.ts.map +1 -0
  356. package/esm/v1/utils/json-patch.test.js +26 -0
  357. package/esm/v1/utils/json-patch.test.js.map +1 -0
  358. package/esm/v1/utils/registry-conversion.d.ts +53 -0
  359. package/esm/v1/utils/registry-conversion.d.ts.map +1 -0
  360. package/esm/v1/utils/registry-conversion.js +108 -0
  361. package/esm/v1/utils/registry-conversion.js.map +1 -0
  362. package/esm/v1/utils/registry-conversion.test.d.ts +2 -0
  363. package/esm/v1/utils/registry-conversion.test.d.ts.map +1 -0
  364. package/esm/v1/utils/registry-conversion.test.js +177 -0
  365. package/esm/v1/utils/registry-conversion.test.js.map +1 -0
  366. package/esm/v1/utils/stream-handler.d.ts +45 -0
  367. package/esm/v1/utils/stream-handler.d.ts.map +1 -0
  368. package/esm/v1/utils/stream-handler.js +44 -0
  369. package/esm/v1/utils/stream-handler.js.map +1 -0
  370. package/esm/v1/utils/stream-handler.test.d.ts +2 -0
  371. package/esm/v1/utils/stream-handler.test.d.ts.map +1 -0
  372. package/esm/v1/utils/stream-handler.test.js +72 -0
  373. package/esm/v1/utils/stream-handler.test.js.map +1 -0
  374. package/esm/v1/utils/tool-call-tracker.d.ts +41 -0
  375. package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -0
  376. package/esm/v1/utils/tool-call-tracker.js +86 -0
  377. package/esm/v1/utils/tool-call-tracker.js.map +1 -0
  378. package/esm/v1/utils/tool-executor.d.ts +33 -0
  379. package/esm/v1/utils/tool-executor.d.ts.map +1 -0
  380. package/esm/v1/utils/tool-executor.js +99 -0
  381. package/esm/v1/utils/tool-executor.js.map +1 -0
  382. package/esm/v1/utils/tool-executor.test.d.ts +2 -0
  383. package/esm/v1/utils/tool-executor.test.d.ts.map +1 -0
  384. package/esm/v1/utils/tool-executor.test.js +220 -0
  385. package/esm/v1/utils/tool-executor.test.js.map +1 -0
  386. package/package.json +20 -9
  387. package/dist/schema/zod.d.ts +0 -57
  388. package/dist/schema/zod.d.ts.map +0 -1
  389. package/dist/schema/zod.js +0 -191
  390. package/dist/schema/zod.js.map +0 -1
  391. package/dist/schema/zod.test.d.ts +0 -2
  392. package/dist/schema/zod.test.d.ts.map +0 -1
  393. package/dist/schema/zod.test.js +0 -663
  394. package/dist/schema/zod.test.js.map +0 -1
  395. package/esm/schema/zod.d.ts +0 -57
  396. package/esm/schema/zod.d.ts.map +0 -1
  397. package/esm/schema/zod.js +0 -180
  398. package/esm/schema/zod.js.map +0 -1
  399. package/esm/schema/zod.test.d.ts +0 -2
  400. package/esm/schema/zod.test.d.ts.map +0 -1
  401. package/esm/schema/zod.test.js +0 -628
  402. package/esm/schema/zod.test.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { advanceStream } from "@tambo-ai/typescript-sdk";
1
+ import TamboAI, { advanceStream } from "@tambo-ai/typescript-sdk";
2
2
  import { act, renderHook } from "@testing-library/react";
3
3
  import React from "react";
4
4
  import { z } from "zod/v4";
@@ -23,9 +23,14 @@ jest.mock("./tambo-client-provider", () => {
23
23
  TamboClientContext: React.createContext(undefined),
24
24
  };
25
25
  });
26
- jest.mock("@tambo-ai/typescript-sdk", () => ({
27
- advanceStream: jest.fn(),
28
- }));
26
+ jest.mock("@tambo-ai/typescript-sdk", () => {
27
+ const actual = jest.requireActual("@tambo-ai/typescript-sdk");
28
+ return {
29
+ __esModule: true,
30
+ ...actual,
31
+ advanceStream: jest.fn(),
32
+ };
33
+ });
29
34
  // Mock the getCustomContext
30
35
  jest.mock("../util/registry", () => ({
31
36
  ...jest.requireActual("../util/registry"),
@@ -68,26 +73,9 @@ const createMockAdvanceResponse = (overrides = {}) => ({
68
73
  });
69
74
  describe("TamboThreadProvider", () => {
70
75
  const mockThread = createMockThread();
71
- const mockThreadsApi = {
72
- messages: {
73
- create: jest.fn(),
74
- },
75
- retrieve: jest.fn(),
76
- advance: jest.fn(),
77
- advanceByID: jest.fn(),
78
- generateName: jest.fn(),
79
- };
80
- const mockProjectsApi = {
81
- getCurrent: jest.fn(),
82
- };
83
- const mockBeta = {
84
- threads: mockThreadsApi,
85
- projects: mockProjectsApi,
86
- };
87
- const mockTamboAI = {
88
- apiKey: "",
89
- beta: mockBeta,
90
- };
76
+ let mockTamboAI;
77
+ let mockThreadsApi;
78
+ let mockProjectsApi;
91
79
  let mockQueryClient;
92
80
  const mockRegistry = [
93
81
  {
@@ -139,8 +127,19 @@ describe("TamboThreadProvider", () => {
139
127
  };
140
128
  // Default wrapper for most tests
141
129
  const Wrapper = createWrapper();
130
+ afterEach(() => {
131
+ jest.restoreAllMocks();
132
+ });
142
133
  beforeEach(() => {
143
134
  jest.clearAllMocks();
135
+ mockTamboAI = new TamboAI({
136
+ apiKey: "",
137
+ fetch: () => {
138
+ throw new Error("Unexpected network call in test");
139
+ },
140
+ });
141
+ mockThreadsApi = mockTamboAI.beta.threads;
142
+ mockProjectsApi = mockTamboAI.beta.projects;
144
143
  // Setup mock query client
145
144
  mockQueryClient = {
146
145
  invalidateQueries: jest.fn().mockResolvedValue(undefined),
@@ -149,21 +148,22 @@ describe("TamboThreadProvider", () => {
149
148
  jest
150
149
  .mocked(useTamboQueryClient)
151
150
  .mockReturnValue(mockQueryClient);
152
- jest.mocked(mockThreadsApi.retrieve).mockResolvedValue(mockThread);
151
+ jest.spyOn(mockThreadsApi, "retrieve").mockResolvedValue(mockThread);
153
152
  jest
154
- .mocked(mockThreadsApi.messages.create)
153
+ .spyOn(mockThreadsApi.messages, "create")
155
154
  .mockResolvedValue(createMockMessage());
156
155
  jest
157
- .mocked(mockThreadsApi.advance)
156
+ .spyOn(mockThreadsApi, "advance")
158
157
  .mockResolvedValue(createMockAdvanceResponse());
159
158
  jest
160
- .mocked(mockThreadsApi.advanceByID)
159
+ .spyOn(mockThreadsApi, "advanceByID")
161
160
  .mockResolvedValue(createMockAdvanceResponse());
162
- jest.mocked(mockThreadsApi.generateName).mockResolvedValue({
161
+ jest.spyOn(mockThreadsApi, "generateName").mockResolvedValue({
163
162
  ...mockThread,
164
163
  name: "Generated Thread Name",
165
164
  });
166
- jest.mocked(mockProjectsApi.getCurrent).mockResolvedValue({
165
+ jest.spyOn(mockThreadsApi, "update").mockResolvedValue({});
166
+ jest.spyOn(mockProjectsApi, "getCurrent").mockResolvedValue({
167
167
  id: "test-project-id",
168
168
  name: "Test Project",
169
169
  isTokenRequired: false,
@@ -231,7 +231,7 @@ describe("TamboThreadProvider", () => {
231
231
  });
232
232
  });
233
233
  it("should send a message and update thread state", async () => {
234
- const mockAdvanceResponse = {
234
+ const mockStreamResponse = {
235
235
  responseMessageDto: {
236
236
  id: "response-1",
237
237
  content: [{ type: "text", text: "Response" }],
@@ -244,14 +244,17 @@ describe("TamboThreadProvider", () => {
244
244
  generationStage: GenerationStage.COMPLETE,
245
245
  mcpAccessToken: "test-mcp-access-token",
246
246
  };
247
- jest
248
- .mocked(mockThreadsApi.advanceByID)
249
- .mockResolvedValue(mockAdvanceResponse);
247
+ const mockAsyncIterator = {
248
+ [Symbol.asyncIterator]: async function* () {
249
+ yield mockStreamResponse;
250
+ },
251
+ };
252
+ jest.mocked(advanceStream).mockResolvedValue(mockAsyncIterator);
250
253
  const { result } = renderHook(() => useTamboThread(), { wrapper: Wrapper });
251
254
  await act(async () => {
252
255
  await result.current.sendThreadMessage("Hello", {
253
256
  threadId: "test-thread-1",
254
- streamResponse: false,
257
+ streamResponse: true,
255
258
  additionalContext: {
256
259
  custom: {
257
260
  message: "additional instructions",
@@ -259,7 +262,7 @@ describe("TamboThreadProvider", () => {
259
262
  },
260
263
  });
261
264
  });
262
- expect(mockThreadsApi.advanceByID).toHaveBeenCalledWith("test-thread-1", {
265
+ expect(advanceStream).toHaveBeenCalledWith(expect.anything(), {
263
266
  messageToAppend: {
264
267
  content: [{ type: "text", text: "Hello" }],
265
268
  role: "user",
@@ -273,7 +276,7 @@ describe("TamboThreadProvider", () => {
273
276
  contextKey: undefined,
274
277
  clientTools: [],
275
278
  toolCallCounts: {},
276
- });
279
+ }, "test-thread-1");
277
280
  expect(result.current.generationStage).toBe(GenerationStage.COMPLETE);
278
281
  });
279
282
  it("should handle streaming responses", async () => {
@@ -308,7 +311,7 @@ describe("TamboThreadProvider", () => {
308
311
  expect(result.current.generationStage).toBe(GenerationStage.COMPLETE);
309
312
  });
310
313
  it("should handle tool calls during message processing.", async () => {
311
- const mockToolCallResponse = {
314
+ const mockToolCallChunk = {
312
315
  responseMessageDto: {
313
316
  id: "tool-call-1",
314
317
  content: [{ type: "text", text: "Tool response" }],
@@ -324,10 +327,7 @@ describe("TamboThreadProvider", () => {
324
327
  generationStage: GenerationStage.COMPLETE,
325
328
  mcpAccessToken: "test-mcp-access-token",
326
329
  };
327
- jest
328
- .mocked(mockThreadsApi.advanceByID)
329
- .mockResolvedValueOnce(mockToolCallResponse)
330
- .mockResolvedValueOnce({
330
+ const mockFinalChunk = {
331
331
  responseMessageDto: {
332
332
  id: "advance-response2",
333
333
  content: [{ type: "text", text: "response 2" }],
@@ -338,12 +338,26 @@ describe("TamboThreadProvider", () => {
338
338
  },
339
339
  generationStage: GenerationStage.COMPLETE,
340
340
  mcpAccessToken: "test-mcp-access-token",
341
- });
341
+ };
342
+ const mockAsyncIterator = {
343
+ [Symbol.asyncIterator]: async function* () {
344
+ yield mockToolCallChunk;
345
+ },
346
+ };
347
+ const mockAsyncIterator2 = {
348
+ [Symbol.asyncIterator]: async function* () {
349
+ yield mockFinalChunk;
350
+ },
351
+ };
352
+ jest
353
+ .mocked(advanceStream)
354
+ .mockResolvedValueOnce(mockAsyncIterator)
355
+ .mockResolvedValueOnce(mockAsyncIterator2);
342
356
  const { result } = renderHook(() => useTamboThread(), { wrapper: Wrapper });
343
357
  await act(async () => {
344
358
  await result.current.sendThreadMessage("Use tool", {
345
359
  threadId: "test-thread-1",
346
- streamResponse: false,
360
+ streamResponse: true,
347
361
  });
348
362
  });
349
363
  expect(result.current.generationStage).toBe(GenerationStage.COMPLETE);
@@ -356,7 +370,7 @@ describe("TamboThreadProvider", () => {
356
370
  const mockOnCallUnregisteredTool = jest
357
371
  .fn()
358
372
  .mockResolvedValue("unregistered-tool-result");
359
- const mockUnregisteredToolCallResponse = {
373
+ const mockUnregisteredToolCallChunk = {
360
374
  responseMessageDto: {
361
375
  id: "unregistered-tool-call-1",
362
376
  content: [{ type: "text", text: "Unregistered tool response" }],
@@ -374,10 +388,7 @@ describe("TamboThreadProvider", () => {
374
388
  generationStage: GenerationStage.COMPLETE,
375
389
  mcpAccessToken: "test-mcp-access-token",
376
390
  };
377
- jest
378
- .mocked(mockThreadsApi.advanceByID)
379
- .mockResolvedValueOnce(mockUnregisteredToolCallResponse)
380
- .mockResolvedValueOnce({
391
+ const mockFinalChunk = {
381
392
  responseMessageDto: {
382
393
  id: "advance-response2",
383
394
  content: [{ type: "text", text: "response 2" }],
@@ -388,7 +399,21 @@ describe("TamboThreadProvider", () => {
388
399
  },
389
400
  generationStage: GenerationStage.COMPLETE,
390
401
  mcpAccessToken: "test-mcp-access-token",
391
- });
402
+ };
403
+ const mockAsyncIterator = {
404
+ [Symbol.asyncIterator]: async function* () {
405
+ yield mockUnregisteredToolCallChunk;
406
+ },
407
+ };
408
+ const mockAsyncIterator2 = {
409
+ [Symbol.asyncIterator]: async function* () {
410
+ yield mockFinalChunk;
411
+ },
412
+ };
413
+ jest
414
+ .mocked(advanceStream)
415
+ .mockResolvedValueOnce(mockAsyncIterator)
416
+ .mockResolvedValueOnce(mockAsyncIterator2);
392
417
  const { result } = renderHook(() => useTamboThread(), {
393
418
  wrapper: createWrapper({
394
419
  onCallUnregisteredTool: mockOnCallUnregisteredTool,
@@ -397,14 +422,14 @@ describe("TamboThreadProvider", () => {
397
422
  await act(async () => {
398
423
  await result.current.sendThreadMessage("Use unregistered tool", {
399
424
  threadId: "test-thread-1",
400
- streamResponse: false,
425
+ streamResponse: true,
401
426
  });
402
427
  });
403
428
  expect(result.current.generationStage).toBe(GenerationStage.COMPLETE);
404
429
  expect(mockOnCallUnregisteredTool).toHaveBeenCalledWith("unregistered-tool", [{ parameterName: "input", parameterValue: "test-input" }]);
405
430
  });
406
431
  it("should handle unregistered tool calls without onCallUnregisteredTool", async () => {
407
- const mockUnregisteredToolCallResponse = {
432
+ const mockUnregisteredToolCallChunk = {
408
433
  responseMessageDto: {
409
434
  id: "unregistered-tool-call-1",
410
435
  content: [{ type: "text", text: "Unregistered tool response" }],
@@ -422,10 +447,7 @@ describe("TamboThreadProvider", () => {
422
447
  generationStage: GenerationStage.COMPLETE,
423
448
  mcpAccessToken: "test-mcp-access-token",
424
449
  };
425
- jest
426
- .mocked(mockThreadsApi.advanceByID)
427
- .mockResolvedValueOnce(mockUnregisteredToolCallResponse)
428
- .mockResolvedValueOnce({
450
+ const mockFinalChunk = {
429
451
  responseMessageDto: {
430
452
  id: "advance-response2",
431
453
  content: [{ type: "text", text: "response 2" }],
@@ -436,12 +458,26 @@ describe("TamboThreadProvider", () => {
436
458
  },
437
459
  generationStage: GenerationStage.COMPLETE,
438
460
  mcpAccessToken: "test-mcp-access-token",
439
- });
461
+ };
462
+ const mockAsyncIterator = {
463
+ [Symbol.asyncIterator]: async function* () {
464
+ yield mockUnregisteredToolCallChunk;
465
+ },
466
+ };
467
+ const mockAsyncIterator2 = {
468
+ [Symbol.asyncIterator]: async function* () {
469
+ yield mockFinalChunk;
470
+ },
471
+ };
472
+ jest
473
+ .mocked(advanceStream)
474
+ .mockResolvedValueOnce(mockAsyncIterator)
475
+ .mockResolvedValueOnce(mockAsyncIterator2);
440
476
  const { result } = renderHook(() => useTamboThread(), { wrapper: Wrapper });
441
477
  await act(async () => {
442
478
  await result.current.sendThreadMessage("Use unregistered tool", {
443
479
  threadId: "test-thread-1",
444
- streamResponse: false,
480
+ streamResponse: true,
445
481
  });
446
482
  });
447
483
  expect(result.current.generationStage).toBe(GenerationStage.COMPLETE);
@@ -502,77 +538,16 @@ describe("TamboThreadProvider", () => {
502
538
  expect(mockThreadsApi.advance).not.toHaveBeenCalled();
503
539
  expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
504
540
  });
505
- it("should call advanceById when streamResponse=false for existing thread", async () => {
506
- // Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
541
+ it("should throw error when streamResponse=false (non-streaming not supported)", async () => {
507
542
  const { result } = renderHook(() => useTamboThread(), {
508
543
  wrapper: createWrapper({ streaming: true }),
509
544
  });
510
545
  await act(async () => {
511
- await result.current.sendThreadMessage("Hello non-streaming", {
546
+ await expect(result.current.sendThreadMessage("Hello non-streaming", {
512
547
  threadId: "test-thread-1",
513
548
  streamResponse: false,
514
- additionalContext: {
515
- custom: {
516
- message: "additional instructions",
517
- },
518
- },
519
- });
520
- });
521
- expect(mockThreadsApi.advanceByID).toHaveBeenCalledWith("test-thread-1", {
522
- messageToAppend: {
523
- content: [{ type: "text", text: "Hello non-streaming" }],
524
- role: "user",
525
- additionalContext: {
526
- custom: {
527
- message: "additional instructions",
528
- },
529
- },
530
- },
531
- availableComponents: serializeRegistry(mockRegistry),
532
- contextKey: undefined,
533
- clientTools: [],
534
- forceToolChoice: undefined,
535
- toolCallCounts: {},
536
- });
537
- // Should not call advance or advanceStream
538
- expect(mockThreadsApi.advance).not.toHaveBeenCalled();
539
- expect(advanceStream).not.toHaveBeenCalled();
540
- });
541
- it("should call advanceById when streamResponse is undefined and provider streaming=false", async () => {
542
- // Use wrapper with streaming=false to test that undefined streamResponse respects provider setting
543
- const { result } = renderHook(() => useTamboThread(), {
544
- wrapper: createWrapper({ streaming: false }),
545
- });
546
- await act(async () => {
547
- await result.current.sendThreadMessage("Hello default", {
548
- threadId: "test-thread-1",
549
- // streamResponse is undefined, should use provider's streaming=false
550
- additionalContext: {
551
- custom: {
552
- message: "additional instructions",
553
- },
554
- },
555
- });
549
+ })).rejects.toThrow();
556
550
  });
557
- expect(mockThreadsApi.advanceByID).toHaveBeenCalledWith("test-thread-1", {
558
- messageToAppend: {
559
- content: [{ type: "text", text: "Hello default" }],
560
- role: "user",
561
- additionalContext: {
562
- custom: {
563
- message: "additional instructions",
564
- },
565
- },
566
- },
567
- availableComponents: serializeRegistry(mockRegistry),
568
- contextKey: undefined,
569
- clientTools: [],
570
- forceToolChoice: undefined,
571
- toolCallCounts: {},
572
- });
573
- // Should not call advance or advanceStream
574
- expect(mockThreadsApi.advance).not.toHaveBeenCalled();
575
- expect(advanceStream).not.toHaveBeenCalled();
576
551
  });
577
552
  it("should call advanceStream when streamResponse is undefined and provider streaming=true (default)", async () => {
578
553
  const mockStreamResponse = {
@@ -628,44 +603,6 @@ describe("TamboThreadProvider", () => {
628
603
  expect(mockThreadsApi.advance).not.toHaveBeenCalled();
629
604
  expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
630
605
  });
631
- it("should call advance when streamResponse=false for placeholder thread", async () => {
632
- // Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
633
- const { result } = renderHook(() => useTamboThread(), {
634
- wrapper: createWrapper({ streaming: true }),
635
- });
636
- // Start with placeholder thread (which is the default state)
637
- expect(result.current.thread.id).toBe("placeholder");
638
- await act(async () => {
639
- await result.current.sendThreadMessage("Hello new thread", {
640
- threadId: "placeholder",
641
- streamResponse: false,
642
- additionalContext: {
643
- custom: {
644
- message: "additional instructions",
645
- },
646
- },
647
- });
648
- });
649
- expect(mockThreadsApi.advance).toHaveBeenCalledWith({
650
- messageToAppend: {
651
- content: [{ type: "text", text: "Hello new thread" }],
652
- role: "user",
653
- additionalContext: {
654
- custom: {
655
- message: "additional instructions",
656
- },
657
- },
658
- },
659
- availableComponents: serializeRegistry(mockRegistry),
660
- contextKey: undefined,
661
- clientTools: [],
662
- forceToolChoice: undefined,
663
- toolCallCounts: {},
664
- });
665
- // Should not call advanceById or advanceStream
666
- expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
667
- expect(advanceStream).not.toHaveBeenCalled();
668
- });
669
606
  it("should call advanceStream when streamResponse=true for placeholder thread", async () => {
670
607
  const mockStreamResponse = {
671
608
  responseMessageDto: {
@@ -812,10 +749,10 @@ describe("TamboThreadProvider", () => {
812
749
  });
813
750
  });
814
751
  describe("error handling", () => {
815
- it("should set generation stage to ERROR when non-streaming sendThreadMessage fails", async () => {
816
- const testError = new Error("API call failed");
817
- // Mock advanceById to throw an error
818
- jest.mocked(mockThreadsApi.advanceByID).mockRejectedValue(testError);
752
+ it("should set generation stage to ERROR when streaming sendThreadMessage fails", async () => {
753
+ const testError = new Error("Streaming API call failed");
754
+ // Mock advanceStream to throw an error
755
+ jest.mocked(advanceStream).mockRejectedValue(testError);
819
756
  const { result } = renderHook(() => useTamboThread(), {
820
757
  wrapper: Wrapper,
821
758
  });
@@ -824,48 +761,91 @@ describe("TamboThreadProvider", () => {
824
761
  await result.current.switchCurrentThread("test-thread-1");
825
762
  await expect(result.current.sendThreadMessage("Hello", {
826
763
  threadId: "test-thread-1",
827
- streamResponse: false,
828
- })).rejects.toThrow("API call failed");
764
+ streamResponse: true,
765
+ })).rejects.toThrow("Streaming API call failed");
829
766
  });
830
767
  // Verify generation stage is set to ERROR
831
768
  expect(result.current.generationStage).toBe(GenerationStage.ERROR);
832
769
  });
833
- it("should set generation stage to ERROR when streaming sendThreadMessage fails", async () => {
834
- const testError = new Error("Streaming API call failed");
835
- // Mock advanceStream to throw an error
770
+ it("should rollback optimistic user message when sendThreadMessage fails", async () => {
771
+ const testError = new Error("API call failed");
836
772
  jest.mocked(advanceStream).mockRejectedValue(testError);
837
773
  const { result } = renderHook(() => useTamboThread(), {
838
774
  wrapper: Wrapper,
839
775
  });
840
- // Expect the error to be thrown
841
776
  await act(async () => {
842
777
  await result.current.switchCurrentThread("test-thread-1");
778
+ });
779
+ const initialMessageCount = result.current.thread.messages.length;
780
+ await act(async () => {
843
781
  await expect(result.current.sendThreadMessage("Hello", {
844
782
  threadId: "test-thread-1",
845
783
  streamResponse: true,
846
- })).rejects.toThrow("Streaming API call failed");
784
+ })).rejects.toThrow("API call failed");
847
785
  });
848
- // Verify generation stage is set to ERROR
849
- expect(result.current.generationStage).toBe(GenerationStage.ERROR);
786
+ // Verify user message was rolled back
787
+ expect(result.current.thread.messages.length).toBe(initialMessageCount);
850
788
  });
851
- it("should set generation stage to ERROR when advance API call fails for placeholder thread", async () => {
852
- const testError = new Error("Advance API call failed");
853
- // Mock advance to throw an error
854
- jest.mocked(mockThreadsApi.advance).mockRejectedValue(testError);
789
+ it("should rollback optimistic message when addThreadMessage fails", async () => {
790
+ const testError = new Error("Create message failed");
791
+ jest.mocked(mockThreadsApi.messages.create).mockRejectedValue(testError);
855
792
  const { result } = renderHook(() => useTamboThread(), {
856
793
  wrapper: Wrapper,
857
794
  });
858
- // Start with placeholder thread (which is the default state)
859
- expect(result.current.thread.id).toBe("placeholder");
860
- // Expect the error to be thrown
861
795
  await act(async () => {
862
- await expect(result.current.sendThreadMessage("Hello", {
863
- threadId: "placeholder",
864
- streamResponse: false,
865
- })).rejects.toThrow("Advance API call failed");
796
+ await result.current.switchCurrentThread("test-thread-1");
866
797
  });
867
- // Verify generation stage is set to ERROR
868
- expect(result.current.generationStage).toBe(GenerationStage.ERROR);
798
+ const initialMessageCount = result.current.thread.messages.length;
799
+ const newMessage = createMockMessage({ threadId: "test-thread-1" });
800
+ await act(async () => {
801
+ await expect(result.current.addThreadMessage(newMessage, true)).rejects.toThrow("Create message failed");
802
+ });
803
+ // Verify message was rolled back
804
+ expect(result.current.thread.messages.length).toBe(initialMessageCount);
805
+ });
806
+ it("should rollback optimistic update when updateThreadMessage fails", async () => {
807
+ const testError = new Error("Update message failed");
808
+ jest.mocked(mockThreadsApi.messages.create).mockRejectedValue(testError);
809
+ const { result } = renderHook(() => useTamboThread(), {
810
+ wrapper: Wrapper,
811
+ });
812
+ await act(async () => {
813
+ await result.current.switchCurrentThread("test-thread-1");
814
+ });
815
+ const existingMessage = createMockMessage({
816
+ id: "existing-msg",
817
+ threadId: "test-thread-1",
818
+ content: [{ type: "text", text: "Old content" }],
819
+ });
820
+ await act(async () => {
821
+ await result.current.addThreadMessage(existingMessage, false);
822
+ });
823
+ const initialMessageCount = result.current.thread.messages.length;
824
+ await act(async () => {
825
+ await expect(result.current.updateThreadMessage("existing-msg", {
826
+ threadId: "test-thread-1",
827
+ content: [{ type: "text", text: "New content" }],
828
+ role: "assistant",
829
+ }, true)).rejects.toThrow("Update message failed");
830
+ });
831
+ // Verify message was rolled back
832
+ expect(result.current.thread.messages.length).toBe(initialMessageCount - 1);
833
+ });
834
+ it("should rollback optimistic name update when updateThreadName fails", async () => {
835
+ const testError = new Error("Update name failed");
836
+ jest.mocked(mockThreadsApi.update).mockRejectedValue(testError);
837
+ const { result } = renderHook(() => useTamboThread(), {
838
+ wrapper: Wrapper,
839
+ });
840
+ await act(async () => {
841
+ await result.current.switchCurrentThread("test-thread-1");
842
+ });
843
+ const initialName = result.current.thread.name;
844
+ await act(async () => {
845
+ await expect(result.current.updateThreadName("New Name", "test-thread-1")).rejects.toThrow("Update name failed");
846
+ });
847
+ // Verify name was rolled back
848
+ expect(result.current.thread.name).toBe(initialName);
869
849
  });
870
850
  });
871
851
  describe("refetch threads list behavior", () => {
@@ -873,8 +853,8 @@ describe("TamboThreadProvider", () => {
873
853
  const { result } = renderHook(() => useTamboThread(), {
874
854
  wrapper: Wrapper,
875
855
  });
876
- // Mock the advance response to return a new thread ID
877
- const mockAdvanceResponse = {
856
+ // Mock the stream response to return a new thread ID
857
+ const mockStreamResponse = {
878
858
  responseMessageDto: {
879
859
  id: "response-1",
880
860
  content: [{ type: "text", text: "Response" }],
@@ -887,16 +867,19 @@ describe("TamboThreadProvider", () => {
887
867
  generationStage: GenerationStage.COMPLETE,
888
868
  mcpAccessToken: "test-mcp-access-token",
889
869
  };
890
- jest
891
- .mocked(mockThreadsApi.advance)
892
- .mockResolvedValue(mockAdvanceResponse);
870
+ const mockAsyncIterator = {
871
+ [Symbol.asyncIterator]: async function* () {
872
+ yield mockStreamResponse;
873
+ },
874
+ };
875
+ jest.mocked(advanceStream).mockResolvedValue(mockAsyncIterator);
893
876
  // Start with placeholder thread
894
877
  expect(result.current.thread.id).toBe("placeholder");
895
878
  // Send a message which will create a new thread with contextKey
896
879
  await act(async () => {
897
880
  await result.current.sendThreadMessage("Hello", {
898
881
  threadId: "placeholder",
899
- streamResponse: false,
882
+ streamResponse: true,
900
883
  contextKey: "test-context-key",
901
884
  });
902
885
  });
@@ -934,93 +917,6 @@ describe("TamboThreadProvider", () => {
934
917
  });
935
918
  });
936
919
  describe("transformToContent", () => {
937
- it("should use custom transformToContent when provided (non-streaming)", async () => {
938
- const mockTransformToContent = jest.fn().mockReturnValue([
939
- { type: "text", text: "Custom transformed content" },
940
- {
941
- type: "image_url",
942
- image_url: { url: "https://example.com/image.png" },
943
- },
944
- ]);
945
- const customToolRegistry = [
946
- {
947
- name: "TestComponent",
948
- component: () => React.createElement("div", null, "Test"),
949
- description: "Test",
950
- propsSchema: z.object({ test: z.string() }),
951
- associatedTools: [
952
- {
953
- name: "custom-tool",
954
- tool: jest.fn().mockResolvedValue({ data: "tool result" }),
955
- description: "Tool with custom transform",
956
- inputSchema: z.object({ input: z.string() }),
957
- outputSchema: z.object({ data: z.string() }),
958
- transformToContent: mockTransformToContent,
959
- },
960
- ],
961
- },
962
- ];
963
- const mockToolCallResponse = {
964
- responseMessageDto: {
965
- id: "tool-call-1",
966
- content: [{ type: "text", text: "Tool response" }],
967
- role: "tool",
968
- threadId: "test-thread-1",
969
- toolCallRequest: {
970
- toolName: "custom-tool",
971
- parameters: [{ parameterName: "input", parameterValue: "test" }],
972
- },
973
- componentState: {},
974
- createdAt: new Date().toISOString(),
975
- },
976
- generationStage: GenerationStage.COMPLETE,
977
- mcpAccessToken: "test-mcp-access-token",
978
- };
979
- jest
980
- .mocked(mockThreadsApi.advanceByID)
981
- .mockResolvedValueOnce(mockToolCallResponse)
982
- .mockResolvedValueOnce({
983
- responseMessageDto: {
984
- id: "final-response",
985
- content: [{ type: "text", text: "Final response" }],
986
- role: "assistant",
987
- threadId: "test-thread-1",
988
- componentState: {},
989
- createdAt: new Date().toISOString(),
990
- },
991
- generationStage: GenerationStage.COMPLETE,
992
- mcpAccessToken: "test-mcp-access-token",
993
- });
994
- const { result } = renderHook(() => useTamboThread(), {
995
- wrapper: createWrapper({ components: customToolRegistry }),
996
- });
997
- await act(async () => {
998
- await result.current.sendThreadMessage("Use custom tool", {
999
- threadId: "test-thread-1",
1000
- streamResponse: false,
1001
- });
1002
- });
1003
- // Verify the tool was called with single object arg (new inputSchema interface)
1004
- expect(customToolRegistry[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
1005
- // Verify transformToContent was called with the tool result
1006
- expect(mockTransformToContent).toHaveBeenCalledWith({
1007
- data: "tool result",
1008
- });
1009
- // Verify the second advance call included the transformed content
1010
- expect(mockThreadsApi.advanceByID).toHaveBeenCalledTimes(2);
1011
- expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
1012
- messageToAppend: expect.objectContaining({
1013
- content: [
1014
- { type: "text", text: "Custom transformed content" },
1015
- {
1016
- type: "image_url",
1017
- image_url: { url: "https://example.com/image.png" },
1018
- },
1019
- ],
1020
- role: "tool",
1021
- }),
1022
- }));
1023
- });
1024
920
  it("should use custom async transformToContent when provided (streaming)", async () => {
1025
921
  const mockTransformToContent = jest
1026
922
  .fn()
@@ -1117,169 +1013,6 @@ describe("TamboThreadProvider", () => {
1117
1013
  }),
1118
1014
  }), "test-thread-1");
1119
1015
  });
1120
- it("should fallback to stringified text when transformToContent is not provided", async () => {
1121
- const toolWithoutTransform = [
1122
- {
1123
- name: "TestComponent",
1124
- component: () => React.createElement("div", null, "Test"),
1125
- description: "Test",
1126
- propsSchema: z.object({ test: z.string() }),
1127
- associatedTools: [
1128
- {
1129
- name: "no-transform-tool",
1130
- tool: jest
1131
- .fn()
1132
- .mockResolvedValue({ complex: "data", nested: { value: 42 } }),
1133
- description: "Tool without custom transform",
1134
- inputSchema: z.object({ input: z.string() }),
1135
- outputSchema: z.object({
1136
- complex: z.string(),
1137
- nested: z.object({ value: z.number() }),
1138
- }),
1139
- // No transformToContent provided
1140
- },
1141
- ],
1142
- },
1143
- ];
1144
- const mockToolCallResponse = {
1145
- responseMessageDto: {
1146
- id: "tool-call-1",
1147
- content: [{ type: "text", text: "Tool call" }],
1148
- role: "tool",
1149
- threadId: "test-thread-1",
1150
- toolCallRequest: {
1151
- toolName: "no-transform-tool",
1152
- parameters: [{ parameterName: "input", parameterValue: "test" }],
1153
- },
1154
- componentState: {},
1155
- createdAt: new Date().toISOString(),
1156
- },
1157
- generationStage: GenerationStage.COMPLETE,
1158
- mcpAccessToken: "test-mcp-access-token",
1159
- };
1160
- jest
1161
- .mocked(mockThreadsApi.advanceByID)
1162
- .mockResolvedValueOnce(mockToolCallResponse)
1163
- .mockResolvedValueOnce({
1164
- responseMessageDto: {
1165
- id: "final-response",
1166
- content: [{ type: "text", text: "Final response" }],
1167
- role: "assistant",
1168
- threadId: "test-thread-1",
1169
- componentState: {},
1170
- createdAt: new Date().toISOString(),
1171
- },
1172
- generationStage: GenerationStage.COMPLETE,
1173
- mcpAccessToken: "test-mcp-access-token",
1174
- });
1175
- const { result } = renderHook(() => useTamboThread(), {
1176
- wrapper: createWrapper({ components: toolWithoutTransform }),
1177
- });
1178
- await act(async () => {
1179
- await result.current.sendThreadMessage("Use tool without transform", {
1180
- threadId: "test-thread-1",
1181
- streamResponse: false,
1182
- });
1183
- });
1184
- // Verify the tool was called with single object arg (new inputSchema interface)
1185
- expect(toolWithoutTransform[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
1186
- // Verify the second advance call used stringified content
1187
- expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
1188
- messageToAppend: expect.objectContaining({
1189
- content: [
1190
- {
1191
- type: "text",
1192
- text: '{"complex":"data","nested":{"value":42}}',
1193
- },
1194
- ],
1195
- role: "tool",
1196
- }),
1197
- }));
1198
- });
1199
- it("should always return text for error responses even with transformToContent", async () => {
1200
- const mockTransformToContent = jest.fn().mockReturnValue([
1201
- {
1202
- type: "image_url",
1203
- image_url: { url: "https://example.com/error.png" },
1204
- },
1205
- ]);
1206
- const toolWithTransform = [
1207
- {
1208
- name: "TestComponent",
1209
- component: () => React.createElement("div", null, "Test"),
1210
- description: "Test",
1211
- propsSchema: z.object({ test: z.string() }),
1212
- associatedTools: [
1213
- {
1214
- name: "error-tool",
1215
- tool: jest
1216
- .fn()
1217
- .mockRejectedValue(new Error("Tool execution failed")),
1218
- description: "Tool that errors",
1219
- inputSchema: z.object({ input: z.string() }),
1220
- outputSchema: z.string(),
1221
- transformToContent: mockTransformToContent,
1222
- },
1223
- ],
1224
- },
1225
- ];
1226
- const mockToolCallResponse = {
1227
- responseMessageDto: {
1228
- id: "tool-call-1",
1229
- content: [{ type: "text", text: "Tool call" }],
1230
- role: "tool",
1231
- threadId: "test-thread-1",
1232
- toolCallRequest: {
1233
- toolName: "error-tool",
1234
- parameters: [{ parameterName: "input", parameterValue: "test" }],
1235
- },
1236
- componentState: {},
1237
- createdAt: new Date().toISOString(),
1238
- },
1239
- generationStage: GenerationStage.COMPLETE,
1240
- mcpAccessToken: "test-mcp-access-token",
1241
- };
1242
- jest
1243
- .mocked(mockThreadsApi.advanceByID)
1244
- .mockResolvedValueOnce(mockToolCallResponse)
1245
- .mockResolvedValueOnce({
1246
- responseMessageDto: {
1247
- id: "final-response",
1248
- content: [{ type: "text", text: "Final response" }],
1249
- role: "assistant",
1250
- threadId: "test-thread-1",
1251
- componentState: {},
1252
- createdAt: new Date().toISOString(),
1253
- },
1254
- generationStage: GenerationStage.COMPLETE,
1255
- mcpAccessToken: "test-mcp-access-token",
1256
- });
1257
- const { result } = renderHook(() => useTamboThread(), {
1258
- wrapper: createWrapper({ components: toolWithTransform }),
1259
- });
1260
- await act(async () => {
1261
- await result.current.sendThreadMessage("Use error tool", {
1262
- threadId: "test-thread-1",
1263
- streamResponse: false,
1264
- });
1265
- });
1266
- // Verify the tool was called with single object arg (new inputSchema interface)
1267
- expect(toolWithTransform[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
1268
- // Verify transformToContent was NOT called for error responses
1269
- expect(mockTransformToContent).not.toHaveBeenCalled();
1270
- // Verify the second advance call used text content with the error message
1271
- expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
1272
- messageToAppend: expect.objectContaining({
1273
- content: [
1274
- expect.objectContaining({
1275
- type: "text",
1276
- // Error message should be in text format
1277
- }),
1278
- ],
1279
- role: "tool",
1280
- }),
1281
- }));
1282
- });
1283
1016
  });
1284
1017
  describe("tamboStreamableHint streaming behavior", () => {
1285
1018
  it("should call streamable tool during streaming when tamboStreamableHint is true", async () => {
@@ -1612,6 +1345,25 @@ describe("TamboThreadProvider", () => {
1612
1345
  });
1613
1346
  describe("auto-generate thread name", () => {
1614
1347
  it("should auto-generate thread name after reaching threshold", async () => {
1348
+ const mockStreamResponse = {
1349
+ responseMessageDto: {
1350
+ id: "response-1",
1351
+ content: [{ type: "text", text: "Response" }],
1352
+ role: "assistant",
1353
+ threadId: "test-thread-1",
1354
+ component: undefined,
1355
+ componentState: {},
1356
+ createdAt: new Date().toISOString(),
1357
+ },
1358
+ generationStage: GenerationStage.COMPLETE,
1359
+ mcpAccessToken: "test-mcp-access-token",
1360
+ };
1361
+ const mockAsyncIterator = {
1362
+ [Symbol.asyncIterator]: async function* () {
1363
+ yield mockStreamResponse;
1364
+ },
1365
+ };
1366
+ jest.mocked(advanceStream).mockResolvedValue(mockAsyncIterator);
1615
1367
  const { result } = renderHook(() => useTamboThread(), {
1616
1368
  wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1617
1369
  });
@@ -1643,13 +1395,34 @@ describe("TamboThreadProvider", () => {
1643
1395
  }), false);
1644
1396
  });
1645
1397
  await act(async () => {
1646
- await result.current.sendThreadMessage("Test message");
1398
+ await result.current.sendThreadMessage("Test message", {
1399
+ streamResponse: true,
1400
+ });
1647
1401
  });
1648
1402
  expect(mockThreadsApi.generateName).toHaveBeenCalledWith("test-thread-1");
1649
1403
  expect(result.current.thread.name).toBe("Generated Thread Name");
1650
1404
  expect(mockQueryClient.setQueryData).toHaveBeenCalledWith(["threads", "test-project-id", undefined], expect.any(Function));
1651
1405
  });
1652
1406
  it("should NOT auto-generate when autoGenerateThreadName is false", async () => {
1407
+ const mockStreamResponse = {
1408
+ responseMessageDto: {
1409
+ id: "response-1",
1410
+ content: [{ type: "text", text: "Response" }],
1411
+ role: "assistant",
1412
+ threadId: "test-thread-1",
1413
+ component: undefined,
1414
+ componentState: {},
1415
+ createdAt: new Date().toISOString(),
1416
+ },
1417
+ generationStage: GenerationStage.COMPLETE,
1418
+ mcpAccessToken: "test-mcp-access-token",
1419
+ };
1420
+ const mockAsyncIterator = {
1421
+ [Symbol.asyncIterator]: async function* () {
1422
+ yield mockStreamResponse;
1423
+ },
1424
+ };
1425
+ jest.mocked(advanceStream).mockResolvedValue(mockAsyncIterator);
1653
1426
  const { result } = renderHook(() => useTamboThread(), {
1654
1427
  wrapper: createWrapper({
1655
1428
  autoGenerateThreadName: false,
@@ -1681,12 +1454,33 @@ describe("TamboThreadProvider", () => {
1681
1454
  }), false);
1682
1455
  });
1683
1456
  await act(async () => {
1684
- await result.current.sendThreadMessage("Test message");
1457
+ await result.current.sendThreadMessage("Test message", {
1458
+ streamResponse: true,
1459
+ });
1685
1460
  });
1686
1461
  // Should NOT generate name because feature is disabled
1687
1462
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
1688
1463
  });
1689
1464
  it("should NOT auto-generate when thread already has a name", async () => {
1465
+ const mockStreamResponse = {
1466
+ responseMessageDto: {
1467
+ id: "response-1",
1468
+ content: [{ type: "text", text: "Response" }],
1469
+ role: "assistant",
1470
+ threadId: "test-thread-1",
1471
+ component: undefined,
1472
+ componentState: {},
1473
+ createdAt: new Date().toISOString(),
1474
+ },
1475
+ generationStage: GenerationStage.COMPLETE,
1476
+ mcpAccessToken: "test-mcp-access-token",
1477
+ };
1478
+ const mockAsyncIterator = {
1479
+ [Symbol.asyncIterator]: async function* () {
1480
+ yield mockStreamResponse;
1481
+ },
1482
+ };
1483
+ jest.mocked(advanceStream).mockResolvedValue(mockAsyncIterator);
1690
1484
  const { result } = renderHook(() => useTamboThread(), {
1691
1485
  wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1692
1486
  });
@@ -1720,7 +1514,9 @@ describe("TamboThreadProvider", () => {
1720
1514
  expect(result.current.thread.messages).toHaveLength(2);
1721
1515
  // Send another message to reach threshold (3 messages total)
1722
1516
  await act(async () => {
1723
- await result.current.sendThreadMessage("Test message");
1517
+ await result.current.sendThreadMessage("Test message", {
1518
+ streamResponse: true,
1519
+ });
1724
1520
  });
1725
1521
  // Should NOT generate name because thread already has one
1726
1522
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();