@tambo-ai/react 0.68.0 → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +1 -1
  2. package/dist/context-helpers/context-helpers.test.js +16 -4
  3. package/dist/context-helpers/context-helpers.test.js.map +1 -1
  4. package/dist/context-helpers/current-interactables-context-helper.d.ts +2 -2
  5. package/dist/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  6. package/dist/context-helpers/current-interactables-context-helper.js +31 -15
  7. package/dist/context-helpers/current-interactables-context-helper.js.map +1 -1
  8. package/dist/context-helpers/registry.d.ts +2 -2
  9. package/dist/context-helpers/registry.d.ts.map +1 -1
  10. package/dist/context-helpers/registry.js.map +1 -1
  11. package/dist/context-helpers/types.d.ts +2 -2
  12. package/dist/context-helpers/types.d.ts.map +1 -1
  13. package/dist/context-helpers/types.js.map +1 -1
  14. package/dist/hooks/use-message-images.test.js +174 -37
  15. package/dist/hooks/use-message-images.test.js.map +1 -1
  16. package/dist/hooks/use-tambo-voice.d.ts +1 -1
  17. package/dist/hooks/use-tambo-voice.js +1 -1
  18. package/dist/hooks/use-tambo-voice.js.map +1 -1
  19. package/dist/hooks/use-tambo-voice.test.d.ts +2 -0
  20. package/dist/hooks/use-tambo-voice.test.d.ts.map +1 -0
  21. package/dist/hooks/use-tambo-voice.test.js +239 -0
  22. package/dist/hooks/use-tambo-voice.test.js.map +1 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/elicitation.d.ts.map +1 -1
  27. package/dist/mcp/elicitation.js +12 -0
  28. package/dist/mcp/elicitation.js.map +1 -1
  29. package/dist/mcp/elicitation.test.js +8 -1
  30. package/dist/mcp/elicitation.test.js.map +1 -1
  31. package/dist/mcp/mcp-client.d.ts +6 -10
  32. package/dist/mcp/mcp-client.d.ts.map +1 -1
  33. package/dist/mcp/mcp-client.js.map +1 -1
  34. package/dist/mcp/mcp-hooks.d.ts +12 -60
  35. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  36. package/dist/mcp/mcp-hooks.js +90 -10
  37. package/dist/mcp/mcp-hooks.js.map +1 -1
  38. package/dist/mcp/mcp-hooks.test.js +423 -0
  39. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  40. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  41. package/dist/mcp/tambo-mcp-provider.js +3 -0
  42. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  43. package/dist/mcp/tambo-mcp-provider.test.js +37 -0
  44. package/dist/mcp/tambo-mcp-provider.test.js.map +1 -1
  45. package/dist/model/component-metadata.d.ts +53 -20
  46. package/dist/model/component-metadata.d.ts.map +1 -1
  47. package/dist/model/component-metadata.js.map +1 -1
  48. package/dist/model/tambo-interactable.d.ts +6 -0
  49. package/dist/model/tambo-interactable.d.ts.map +1 -1
  50. package/dist/model/tambo-interactable.js.map +1 -1
  51. package/dist/providers/index.d.ts +1 -1
  52. package/dist/providers/index.d.ts.map +1 -1
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/providers/tambo-client-provider.d.ts +8 -0
  55. package/dist/providers/tambo-client-provider.d.ts.map +1 -1
  56. package/dist/providers/tambo-client-provider.js +10 -11
  57. package/dist/providers/tambo-client-provider.js.map +1 -1
  58. package/dist/providers/tambo-client-provider.test.d.ts +2 -0
  59. package/dist/providers/tambo-client-provider.test.d.ts.map +1 -0
  60. package/dist/providers/tambo-client-provider.test.js +208 -0
  61. package/dist/providers/tambo-client-provider.test.js.map +1 -0
  62. package/dist/providers/tambo-context-attachment-provider.d.ts +34 -92
  63. package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  64. package/dist/providers/tambo-context-attachment-provider.js +62 -105
  65. package/dist/providers/tambo-context-attachment-provider.js.map +1 -1
  66. package/dist/providers/tambo-context-attachment-provider.test.js +229 -463
  67. package/dist/providers/tambo-context-attachment-provider.test.js.map +1 -1
  68. package/dist/providers/tambo-interactable-provider.d.ts +2 -0
  69. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  70. package/dist/providers/tambo-interactable-provider.js +29 -4
  71. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  72. package/dist/providers/tambo-interactable-provider.test.js +1 -1
  73. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  74. package/dist/providers/tambo-interactables-additional-context.test.js +2 -5
  75. package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
  76. package/dist/providers/tambo-provider.d.ts +2 -3
  77. package/dist/providers/tambo-provider.d.ts.map +1 -1
  78. package/dist/providers/tambo-provider.js +5 -6
  79. package/dist/providers/tambo-provider.js.map +1 -1
  80. package/dist/providers/tambo-registry-provider.test.js +16 -0
  81. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  82. package/dist/providers/tambo-registry-schema-compat.test.js +31 -0
  83. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
  84. package/dist/providers/tambo-thread-input-provider.d.ts +1 -1
  85. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  86. package/dist/providers/tambo-thread-input-provider.js +5 -1
  87. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  88. package/dist/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  89. package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  90. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  91. package/dist/providers/tambo-thread-provider.js +56 -43
  92. package/dist/providers/tambo-thread-provider.js.map +1 -1
  93. package/dist/providers/tambo-thread-provider.test.js +456 -262
  94. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  95. package/dist/schema/json-schema.js +29 -29
  96. package/dist/schema/json-schema.js.map +1 -1
  97. package/dist/schema/schema.test.js +237 -0
  98. package/dist/schema/schema.test.js.map +1 -1
  99. package/dist/schema/standard-schema.d.ts +1 -0
  100. package/dist/schema/standard-schema.d.ts.map +1 -1
  101. package/dist/schema/standard-schema.js +18 -13
  102. package/dist/schema/standard-schema.js.map +1 -1
  103. package/dist/schema/standard-schema.test.d.ts +2 -0
  104. package/dist/schema/standard-schema.test.d.ts.map +1 -0
  105. package/dist/schema/standard-schema.test.js +165 -0
  106. package/dist/schema/standard-schema.test.js.map +1 -0
  107. package/dist/schema/validate.test.js +149 -0
  108. package/dist/schema/validate.test.js.map +1 -1
  109. package/dist/schema/zod.d.ts +7 -4
  110. package/dist/schema/zod.d.ts.map +1 -1
  111. package/dist/schema/zod.js +65 -22
  112. package/dist/schema/zod.js.map +1 -1
  113. package/dist/schema/zod.test.js +112 -0
  114. package/dist/schema/zod.test.js.map +1 -1
  115. package/dist/testing/tools.d.ts +4 -1
  116. package/dist/testing/tools.d.ts.map +1 -1
  117. package/dist/testing/tools.js +6 -1
  118. package/dist/testing/tools.js.map +1 -1
  119. package/dist/util/generate-component.d.ts.map +1 -1
  120. package/dist/util/generate-component.js +18 -3
  121. package/dist/util/generate-component.js.map +1 -1
  122. package/dist/util/generate-component.test.d.ts +2 -0
  123. package/dist/util/generate-component.test.d.ts.map +1 -0
  124. package/dist/util/generate-component.test.js +340 -0
  125. package/dist/util/generate-component.test.js.map +1 -0
  126. package/dist/util/is-promise.d.ts +9 -0
  127. package/dist/util/is-promise.d.ts.map +1 -0
  128. package/dist/util/is-promise.js +20 -0
  129. package/dist/util/is-promise.js.map +1 -0
  130. package/dist/util/is-promise.test.d.ts +2 -0
  131. package/dist/util/is-promise.test.d.ts.map +1 -0
  132. package/dist/util/is-promise.test.js +48 -0
  133. package/dist/util/is-promise.test.js.map +1 -0
  134. package/dist/util/query-utils.test.d.ts +2 -0
  135. package/dist/util/query-utils.test.d.ts.map +1 -0
  136. package/dist/util/query-utils.test.js +382 -0
  137. package/dist/util/query-utils.test.js.map +1 -0
  138. package/dist/util/registry-validators.d.ts.map +1 -1
  139. package/dist/util/registry-validators.js +7 -0
  140. package/dist/util/registry-validators.js.map +1 -1
  141. package/dist/util/registry-validators.test.js +57 -0
  142. package/dist/util/registry-validators.test.js.map +1 -1
  143. package/dist/util/registry.d.ts.map +1 -1
  144. package/dist/util/registry.js +9 -0
  145. package/dist/util/registry.js.map +1 -1
  146. package/dist/util/registry.test.js +323 -1
  147. package/dist/util/registry.test.js.map +1 -1
  148. package/dist/util/resource-validators.test.d.ts +2 -0
  149. package/dist/util/resource-validators.test.d.ts.map +1 -0
  150. package/dist/util/resource-validators.test.js +90 -0
  151. package/dist/util/resource-validators.test.js.map +1 -0
  152. package/dist/util/tool-caller.d.ts +2 -2
  153. package/dist/util/tool-caller.d.ts.map +1 -1
  154. package/dist/util/tool-caller.js +8 -8
  155. package/dist/util/tool-caller.js.map +1 -1
  156. package/dist/util/validate-component-name.test.d.ts +2 -0
  157. package/dist/util/validate-component-name.test.d.ts.map +1 -0
  158. package/dist/util/validate-component-name.test.js +35 -0
  159. package/dist/util/validate-component-name.test.js.map +1 -0
  160. package/esm/context-helpers/context-helpers.test.js +16 -4
  161. package/esm/context-helpers/context-helpers.test.js.map +1 -1
  162. package/esm/context-helpers/current-interactables-context-helper.d.ts +2 -2
  163. package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  164. package/esm/context-helpers/current-interactables-context-helper.js +31 -15
  165. package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
  166. package/esm/context-helpers/registry.d.ts +2 -2
  167. package/esm/context-helpers/registry.d.ts.map +1 -1
  168. package/esm/context-helpers/registry.js.map +1 -1
  169. package/esm/context-helpers/types.d.ts +2 -2
  170. package/esm/context-helpers/types.d.ts.map +1 -1
  171. package/esm/context-helpers/types.js.map +1 -1
  172. package/esm/hooks/use-message-images.test.js +174 -37
  173. package/esm/hooks/use-message-images.test.js.map +1 -1
  174. package/esm/hooks/use-tambo-voice.d.ts +1 -1
  175. package/esm/hooks/use-tambo-voice.js +1 -1
  176. package/esm/hooks/use-tambo-voice.js.map +1 -1
  177. package/esm/hooks/use-tambo-voice.test.d.ts +2 -0
  178. package/esm/hooks/use-tambo-voice.test.d.ts.map +1 -0
  179. package/esm/hooks/use-tambo-voice.test.js +234 -0
  180. package/esm/hooks/use-tambo-voice.test.js.map +1 -0
  181. package/esm/index.d.ts +2 -2
  182. package/esm/index.d.ts.map +1 -1
  183. package/esm/index.js.map +1 -1
  184. package/esm/mcp/elicitation.d.ts.map +1 -1
  185. package/esm/mcp/elicitation.js +12 -0
  186. package/esm/mcp/elicitation.js.map +1 -1
  187. package/esm/mcp/elicitation.test.js +8 -1
  188. package/esm/mcp/elicitation.test.js.map +1 -1
  189. package/esm/mcp/mcp-client.d.ts +6 -10
  190. package/esm/mcp/mcp-client.d.ts.map +1 -1
  191. package/esm/mcp/mcp-client.js.map +1 -1
  192. package/esm/mcp/mcp-hooks.d.ts +12 -60
  193. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  194. package/esm/mcp/mcp-hooks.js +57 -10
  195. package/esm/mcp/mcp-hooks.js.map +1 -1
  196. package/esm/mcp/mcp-hooks.test.js +423 -0
  197. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  198. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  199. package/esm/mcp/tambo-mcp-provider.js +3 -0
  200. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  201. package/esm/mcp/tambo-mcp-provider.test.js +37 -0
  202. package/esm/mcp/tambo-mcp-provider.test.js.map +1 -1
  203. package/esm/model/component-metadata.d.ts +53 -20
  204. package/esm/model/component-metadata.d.ts.map +1 -1
  205. package/esm/model/component-metadata.js.map +1 -1
  206. package/esm/model/tambo-interactable.d.ts +6 -0
  207. package/esm/model/tambo-interactable.d.ts.map +1 -1
  208. package/esm/model/tambo-interactable.js.map +1 -1
  209. package/esm/providers/index.d.ts +1 -1
  210. package/esm/providers/index.d.ts.map +1 -1
  211. package/esm/providers/index.js.map +1 -1
  212. package/esm/providers/tambo-client-provider.d.ts +8 -0
  213. package/esm/providers/tambo-client-provider.d.ts.map +1 -1
  214. package/esm/providers/tambo-client-provider.js +11 -12
  215. package/esm/providers/tambo-client-provider.js.map +1 -1
  216. package/esm/providers/tambo-client-provider.test.d.ts +2 -0
  217. package/esm/providers/tambo-client-provider.test.d.ts.map +1 -0
  218. package/esm/providers/tambo-client-provider.test.js +203 -0
  219. package/esm/providers/tambo-client-provider.test.js.map +1 -0
  220. package/esm/providers/tambo-context-attachment-provider.d.ts +34 -92
  221. package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  222. package/esm/providers/tambo-context-attachment-provider.js +63 -106
  223. package/esm/providers/tambo-context-attachment-provider.js.map +1 -1
  224. package/esm/providers/tambo-context-attachment-provider.test.js +230 -464
  225. package/esm/providers/tambo-context-attachment-provider.test.js.map +1 -1
  226. package/esm/providers/tambo-interactable-provider.d.ts +2 -0
  227. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  228. package/esm/providers/tambo-interactable-provider.js +29 -4
  229. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  230. package/esm/providers/tambo-interactable-provider.test.js +1 -1
  231. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  232. package/esm/providers/tambo-interactables-additional-context.test.js +2 -5
  233. package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
  234. package/esm/providers/tambo-provider.d.ts +2 -3
  235. package/esm/providers/tambo-provider.d.ts.map +1 -1
  236. package/esm/providers/tambo-provider.js +5 -6
  237. package/esm/providers/tambo-provider.js.map +1 -1
  238. package/esm/providers/tambo-registry-provider.test.js +16 -0
  239. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  240. package/esm/providers/tambo-registry-schema-compat.test.js +31 -0
  241. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
  242. package/esm/providers/tambo-thread-input-provider.d.ts +1 -1
  243. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  244. package/esm/providers/tambo-thread-input-provider.js +5 -1
  245. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  246. package/esm/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  247. package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  248. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  249. package/esm/providers/tambo-thread-provider.js +56 -43
  250. package/esm/providers/tambo-thread-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider.test.js +456 -262
  252. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  253. package/esm/schema/json-schema.js +1 -1
  254. package/esm/schema/json-schema.js.map +1 -1
  255. package/esm/schema/schema.test.js +238 -1
  256. package/esm/schema/schema.test.js.map +1 -1
  257. package/esm/schema/standard-schema.d.ts +1 -0
  258. package/esm/schema/standard-schema.d.ts.map +1 -1
  259. package/esm/schema/standard-schema.js +18 -13
  260. package/esm/schema/standard-schema.js.map +1 -1
  261. package/esm/schema/standard-schema.test.d.ts +2 -0
  262. package/esm/schema/standard-schema.test.d.ts.map +1 -0
  263. package/esm/schema/standard-schema.test.js +130 -0
  264. package/esm/schema/standard-schema.test.js.map +1 -0
  265. package/esm/schema/validate.test.js +149 -0
  266. package/esm/schema/validate.test.js.map +1 -1
  267. package/esm/schema/zod.d.ts +7 -4
  268. package/esm/schema/zod.d.ts.map +1 -1
  269. package/esm/schema/zod.js +65 -22
  270. package/esm/schema/zod.js.map +1 -1
  271. package/esm/schema/zod.test.js +113 -1
  272. package/esm/schema/zod.test.js.map +1 -1
  273. package/esm/testing/tools.d.ts +4 -1
  274. package/esm/testing/tools.d.ts.map +1 -1
  275. package/esm/testing/tools.js +6 -1
  276. package/esm/testing/tools.js.map +1 -1
  277. package/esm/util/generate-component.d.ts.map +1 -1
  278. package/esm/util/generate-component.js +18 -3
  279. package/esm/util/generate-component.js.map +1 -1
  280. package/esm/util/generate-component.test.d.ts +2 -0
  281. package/esm/util/generate-component.test.d.ts.map +1 -0
  282. package/esm/util/generate-component.test.js +302 -0
  283. package/esm/util/generate-component.test.js.map +1 -0
  284. package/esm/util/is-promise.d.ts +9 -0
  285. package/esm/util/is-promise.d.ts.map +1 -0
  286. package/esm/util/is-promise.js +17 -0
  287. package/esm/util/is-promise.js.map +1 -0
  288. package/esm/util/is-promise.test.d.ts +2 -0
  289. package/esm/util/is-promise.test.d.ts.map +1 -0
  290. package/esm/util/is-promise.test.js +46 -0
  291. package/esm/util/is-promise.test.js.map +1 -0
  292. package/esm/util/query-utils.test.d.ts +2 -0
  293. package/esm/util/query-utils.test.d.ts.map +1 -0
  294. package/esm/util/query-utils.test.js +380 -0
  295. package/esm/util/query-utils.test.js.map +1 -0
  296. package/esm/util/registry-validators.d.ts.map +1 -1
  297. package/esm/util/registry-validators.js +7 -0
  298. package/esm/util/registry-validators.js.map +1 -1
  299. package/esm/util/registry-validators.test.js +57 -0
  300. package/esm/util/registry-validators.test.js.map +1 -1
  301. package/esm/util/registry.d.ts.map +1 -1
  302. package/esm/util/registry.js +9 -0
  303. package/esm/util/registry.js.map +1 -1
  304. package/esm/util/registry.test.js +324 -2
  305. package/esm/util/registry.test.js.map +1 -1
  306. package/esm/util/resource-validators.test.d.ts +2 -0
  307. package/esm/util/resource-validators.test.d.ts.map +1 -0
  308. package/esm/util/resource-validators.test.js +88 -0
  309. package/esm/util/resource-validators.test.js.map +1 -0
  310. package/esm/util/tool-caller.d.ts +2 -2
  311. package/esm/util/tool-caller.d.ts.map +1 -1
  312. package/esm/util/tool-caller.js +8 -8
  313. package/esm/util/tool-caller.js.map +1 -1
  314. package/esm/util/validate-component-name.test.d.ts +2 -0
  315. package/esm/util/validate-component-name.test.d.ts.map +1 -0
  316. package/esm/util/validate-component-name.test.js +33 -0
  317. package/esm/util/validate-component-name.test.js.map +1 -0
  318. package/package.json +15 -23
  319. package/dist/schema/alias.d.ts +0 -3
  320. package/dist/schema/alias.d.ts.map +0 -1
  321. package/dist/schema/alias.js +0 -6
  322. package/dist/schema/alias.js.map +0 -1
  323. package/esm/schema/alias.d.ts +0 -3
  324. package/esm/schema/alias.d.ts.map +0 -1
  325. package/esm/schema/alias.js +0 -13
  326. package/esm/schema/alias.js.map +0 -1
@@ -115,8 +115,18 @@ describe("TamboThreadProvider", () => {
115
115
  ],
116
116
  },
117
117
  ];
118
- // Use helpers that explicitly return null so they don't appear in additionalContext
119
- const Wrapper = ({ children }) => {
118
+ /**
119
+ * Creates a test wrapper component with configurable options.
120
+ * Reduces duplication across tests by centralizing provider setup.
121
+ * @param options - Configuration options for the wrapper
122
+ * @param options.components - The Tambo components to register
123
+ * @param options.streaming - Whether to enable streaming responses
124
+ * @param options.onCallUnregisteredTool - Handler for unregistered tool calls
125
+ * @param options.autoGenerateThreadName - Whether to auto-generate thread names
126
+ * @param options.autoGenerateNameThreshold - Token threshold for auto-generating names
127
+ * @returns A React component that wraps children with the necessary providers
128
+ */
129
+ const createWrapper = ({ components = mockRegistry, streaming = false, onCallUnregisteredTool, autoGenerateThreadName, autoGenerateNameThreshold, } = {}) => function TestWrapper({ children }) {
120
130
  const client = (0, tambo_client_provider_1.useTamboClient)();
121
131
  const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
122
132
  return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
@@ -124,14 +134,16 @@ describe("TamboThreadProvider", () => {
124
134
  queryClient,
125
135
  isUpdatingToken: false,
126
136
  } },
127
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
137
+ react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: components, onCallUnregisteredTool: onCallUnregisteredTool },
128
138
  react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
129
139
  currentTimeContextHelper: () => null,
130
140
  currentPageContextHelper: () => null,
131
141
  } },
132
142
  react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
133
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
143
+ react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: streaming, autoGenerateThreadName: autoGenerateThreadName, autoGenerateNameThreshold: autoGenerateNameThreshold }, children))))));
134
144
  };
145
+ // Default wrapper for most tests
146
+ const Wrapper = createWrapper();
135
147
  beforeEach(() => {
136
148
  jest.clearAllMocks();
137
149
  // Setup mock query client
@@ -349,22 +361,6 @@ describe("TamboThreadProvider", () => {
349
361
  const mockOnCallUnregisteredTool = jest
350
362
  .fn()
351
363
  .mockResolvedValue("unregistered-tool-result");
352
- const WrapperWithUnregisteredTool = ({ children, }) => {
353
- const client = (0, tambo_client_provider_1.useTamboClient)();
354
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
355
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
356
- client,
357
- queryClient,
358
- isUpdatingToken: false,
359
- } },
360
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry, onCallUnregisteredTool: mockOnCallUnregisteredTool },
361
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
362
- currentTimeContextHelper: () => null,
363
- currentPageContextHelper: () => null,
364
- } },
365
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
366
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
367
- };
368
364
  const mockUnregisteredToolCallResponse = {
369
365
  responseMessageDto: {
370
366
  id: "unregistered-tool-call-1",
@@ -399,7 +395,9 @@ describe("TamboThreadProvider", () => {
399
395
  mcpAccessToken: "test-mcp-access-token",
400
396
  });
401
397
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
402
- wrapper: WrapperWithUnregisteredTool,
398
+ wrapper: createWrapper({
399
+ onCallUnregisteredTool: mockOnCallUnregisteredTool,
400
+ }),
403
401
  });
404
402
  await (0, react_1.act)(async () => {
405
403
  await result.current.sendThreadMessage("Use unregistered tool", {
@@ -456,23 +454,6 @@ describe("TamboThreadProvider", () => {
456
454
  });
457
455
  describe("streaming behavior", () => {
458
456
  it("should call advanceStream when streamResponse=true", async () => {
459
- // Use wrapper with streaming=true to show that explicit streamResponse=true works
460
- const WrapperWithStreaming = ({ children, }) => {
461
- const client = (0, tambo_client_provider_1.useTamboClient)();
462
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
463
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
464
- client,
465
- queryClient,
466
- isUpdatingToken: false,
467
- } },
468
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
469
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
470
- currentTimeContextHelper: () => null,
471
- currentPageContextHelper: () => null,
472
- } },
473
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
474
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: true }, children))))));
475
- };
476
457
  const mockStreamResponse = {
477
458
  responseMessageDto: {
478
459
  id: "stream-response",
@@ -493,7 +474,7 @@ describe("TamboThreadProvider", () => {
493
474
  };
494
475
  jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
495
476
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
496
- wrapper: WrapperWithStreaming,
477
+ wrapper: createWrapper({ streaming: true }),
497
478
  });
498
479
  await (0, react_1.act)(async () => {
499
480
  await result.current.sendThreadMessage("Hello streaming", {
@@ -528,24 +509,8 @@ describe("TamboThreadProvider", () => {
528
509
  });
529
510
  it("should call advanceById when streamResponse=false for existing thread", async () => {
530
511
  // Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
531
- const WrapperWithStreaming = ({ children, }) => {
532
- const client = (0, tambo_client_provider_1.useTamboClient)();
533
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
534
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
535
- client,
536
- queryClient,
537
- isUpdatingToken: false,
538
- } },
539
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
540
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
541
- currentTimeContextHelper: () => null,
542
- currentPageContextHelper: () => null,
543
- } },
544
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
545
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: true }, children))))));
546
- };
547
512
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
548
- wrapper: WrapperWithStreaming,
513
+ wrapper: createWrapper({ streaming: true }),
549
514
  });
550
515
  await (0, react_1.act)(async () => {
551
516
  await result.current.sendThreadMessage("Hello non-streaming", {
@@ -580,24 +545,8 @@ describe("TamboThreadProvider", () => {
580
545
  });
581
546
  it("should call advanceById when streamResponse is undefined and provider streaming=false", async () => {
582
547
  // Use wrapper with streaming=false to test that undefined streamResponse respects provider setting
583
- const WrapperWithoutStreaming = ({ children, }) => {
584
- const client = (0, tambo_client_provider_1.useTamboClient)();
585
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
586
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
587
- client,
588
- queryClient,
589
- isUpdatingToken: false,
590
- } },
591
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
592
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
593
- currentTimeContextHelper: () => null,
594
- currentPageContextHelper: () => null,
595
- } },
596
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
597
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
598
- };
599
548
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
600
- wrapper: WrapperWithoutStreaming,
549
+ wrapper: createWrapper({ streaming: false }),
601
550
  });
602
551
  await (0, react_1.act)(async () => {
603
552
  await result.current.sendThreadMessage("Hello default", {
@@ -631,23 +580,6 @@ describe("TamboThreadProvider", () => {
631
580
  expect(typescript_sdk_1.advanceStream).not.toHaveBeenCalled();
632
581
  });
633
582
  it("should call advanceStream when streamResponse is undefined and provider streaming=true (default)", async () => {
634
- // Use wrapper with streaming=true (default) to test that undefined streamResponse respects provider setting
635
- const WrapperWithDefaultStreaming = ({ children, }) => {
636
- const client = (0, tambo_client_provider_1.useTamboClient)();
637
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
638
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
639
- client,
640
- queryClient,
641
- isUpdatingToken: false,
642
- } },
643
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
644
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
645
- currentTimeContextHelper: () => null,
646
- currentPageContextHelper: () => null,
647
- } },
648
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
649
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, null, children))))));
650
- };
651
583
  const mockStreamResponse = {
652
584
  responseMessageDto: {
653
585
  id: "stream-response",
@@ -668,7 +600,7 @@ describe("TamboThreadProvider", () => {
668
600
  };
669
601
  jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
670
602
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
671
- wrapper: WrapperWithDefaultStreaming,
603
+ wrapper: createWrapper({ streaming: true }),
672
604
  });
673
605
  await (0, react_1.act)(async () => {
674
606
  await result.current.sendThreadMessage("Hello default streaming", {
@@ -703,24 +635,8 @@ describe("TamboThreadProvider", () => {
703
635
  });
704
636
  it("should call advance when streamResponse=false for placeholder thread", async () => {
705
637
  // Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
706
- const WrapperWithStreaming = ({ children, }) => {
707
- const client = (0, tambo_client_provider_1.useTamboClient)();
708
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
709
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
710
- client,
711
- queryClient,
712
- isUpdatingToken: false,
713
- } },
714
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
715
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
716
- currentTimeContextHelper: () => null,
717
- currentPageContextHelper: () => null,
718
- } },
719
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
720
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: true }, children))))));
721
- };
722
638
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
723
- wrapper: WrapperWithStreaming,
639
+ wrapper: createWrapper({ streaming: true }),
724
640
  });
725
641
  // Start with placeholder thread (which is the default state)
726
642
  expect(result.current.thread.id).toBe("placeholder");
@@ -756,23 +672,6 @@ describe("TamboThreadProvider", () => {
756
672
  expect(typescript_sdk_1.advanceStream).not.toHaveBeenCalled();
757
673
  });
758
674
  it("should call advanceStream when streamResponse=true for placeholder thread", async () => {
759
- // Use wrapper with streaming=false to show that explicit streamResponse=true overrides provider setting
760
- const WrapperWithoutStreaming = ({ children, }) => {
761
- const client = (0, tambo_client_provider_1.useTamboClient)();
762
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
763
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
764
- client,
765
- queryClient,
766
- isUpdatingToken: false,
767
- } },
768
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
769
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
770
- currentTimeContextHelper: () => null,
771
- currentPageContextHelper: () => null,
772
- } },
773
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
774
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
775
- };
776
675
  const mockStreamResponse = {
777
676
  responseMessageDto: {
778
677
  id: "stream-response",
@@ -793,7 +692,7 @@ describe("TamboThreadProvider", () => {
793
692
  };
794
693
  jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
795
694
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
796
- wrapper: WrapperWithoutStreaming,
695
+ wrapper: createWrapper({ streaming: false }),
797
696
  });
798
697
  // Start with placeholder thread (which is the default state)
799
698
  expect(result.current.thread.id).toBe("placeholder");
@@ -828,6 +727,94 @@ describe("TamboThreadProvider", () => {
828
727
  expect(mockThreadsApi.advance).not.toHaveBeenCalled();
829
728
  expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
830
729
  });
730
+ it("should handle multiple sequential messages during streaming (server tool scenario)", async () => {
731
+ // This test verifies the fix for the bug where the second message doesn't render
732
+ // during server tool response streaming. The scenario:
733
+ // 1. First message: "I will call the tool..." with statusMessage
734
+ // 2. Second message: The tool result response streaming in
735
+ // First message - tool announcement (server tools don't have componentName set during streaming)
736
+ const mockFirstMessage = {
737
+ responseMessageDto: {
738
+ id: "msg-first",
739
+ content: [{ type: "text", text: "I will search the docs..." }],
740
+ role: "assistant",
741
+ threadId: "test-thread-1",
742
+ component: {
743
+ componentName: "",
744
+ componentState: {},
745
+ message: "",
746
+ props: {},
747
+ statusMessage: "searching the Tambo docs...",
748
+ },
749
+ componentState: {},
750
+ createdAt: new Date().toISOString(),
751
+ },
752
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
753
+ mcpAccessToken: "test-mcp-access-token",
754
+ };
755
+ // Second message - tool result (different ID!)
756
+ const mockSecondMessageChunk1 = {
757
+ responseMessageDto: {
758
+ id: "msg-second",
759
+ content: [{ type: "text", text: "Here's what I found..." }],
760
+ role: "assistant",
761
+ threadId: "test-thread-1",
762
+ componentState: {},
763
+ createdAt: new Date().toISOString(),
764
+ },
765
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
766
+ mcpAccessToken: "test-mcp-access-token",
767
+ };
768
+ const mockSecondMessageChunk2 = {
769
+ responseMessageDto: {
770
+ id: "msg-second",
771
+ content: [
772
+ {
773
+ type: "text",
774
+ text: "Here's what I found in the documentation about that topic.",
775
+ },
776
+ ],
777
+ role: "assistant",
778
+ threadId: "test-thread-1",
779
+ componentState: {},
780
+ createdAt: new Date().toISOString(),
781
+ },
782
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
783
+ mcpAccessToken: "test-mcp-access-token",
784
+ };
785
+ const mockAsyncIterator = {
786
+ [Symbol.asyncIterator]: async function* () {
787
+ yield mockFirstMessage;
788
+ yield mockSecondMessageChunk1;
789
+ yield mockSecondMessageChunk2;
790
+ },
791
+ };
792
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
793
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
794
+ wrapper: createWrapper({ streaming: true }),
795
+ });
796
+ await (0, react_1.act)(async () => {
797
+ await result.current.sendThreadMessage("Search the docs", {
798
+ threadId: "test-thread-1",
799
+ streamResponse: true,
800
+ });
801
+ });
802
+ // Thread should have 3 messages: user message + 2 assistant messages
803
+ expect(result.current.thread.messages).toHaveLength(3);
804
+ // Filter to assistant messages only
805
+ const assistantMessages = result.current.thread.messages.filter((m) => m.role === "assistant");
806
+ expect(assistantMessages).toHaveLength(2);
807
+ // First assistant message should have the tool status
808
+ const firstMsg = result.current.thread.messages.find((m) => m.id === "msg-first");
809
+ expect(firstMsg).toBeDefined();
810
+ expect(firstMsg?.content[0]?.text).toContain("search the docs");
811
+ // Second assistant message should have the final content
812
+ const secondMsg = result.current.thread.messages.find((m) => m.id === "msg-second");
813
+ expect(secondMsg).toBeDefined();
814
+ expect(secondMsg?.content[0]?.text).toContain("what I found in the documentation");
815
+ // Generation should be complete
816
+ expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
817
+ });
831
818
  });
832
819
  describe("error handling", () => {
833
820
  it("should set generation stage to ERROR when non-streaming sendThreadMessage fails", async () => {
@@ -978,22 +965,6 @@ describe("TamboThreadProvider", () => {
978
965
  ],
979
966
  },
980
967
  ];
981
- const WrapperWithCustomTool = ({ children, }) => {
982
- const client = (0, tambo_client_provider_1.useTamboClient)();
983
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
984
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
985
- client,
986
- queryClient,
987
- isUpdatingToken: false,
988
- } },
989
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: customToolRegistry },
990
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
991
- currentTimeContextHelper: () => null,
992
- currentPageContextHelper: () => null,
993
- } },
994
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
995
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
996
- };
997
968
  const mockToolCallResponse = {
998
969
  responseMessageDto: {
999
970
  id: "tool-call-1",
@@ -1026,7 +997,7 @@ describe("TamboThreadProvider", () => {
1026
997
  mcpAccessToken: "test-mcp-access-token",
1027
998
  });
1028
999
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1029
- wrapper: WrapperWithCustomTool,
1000
+ wrapper: createWrapper({ components: customToolRegistry }),
1030
1001
  });
1031
1002
  await (0, react_1.act)(async () => {
1032
1003
  await result.current.sendThreadMessage("Use custom tool", {
@@ -1079,22 +1050,6 @@ describe("TamboThreadProvider", () => {
1079
1050
  ],
1080
1051
  },
1081
1052
  ];
1082
- const WrapperWithAsyncTool = ({ children, }) => {
1083
- const client = (0, tambo_client_provider_1.useTamboClient)();
1084
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1085
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1086
- client,
1087
- queryClient,
1088
- isUpdatingToken: false,
1089
- } },
1090
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: customToolRegistry },
1091
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1092
- currentTimeContextHelper: () => null,
1093
- currentPageContextHelper: () => null,
1094
- } },
1095
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1096
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: true }, children))))));
1097
- };
1098
1053
  const mockToolCallChunk = {
1099
1054
  responseMessageDto: {
1100
1055
  id: "tool-call-chunk",
@@ -1140,7 +1095,10 @@ describe("TamboThreadProvider", () => {
1140
1095
  },
1141
1096
  });
1142
1097
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1143
- wrapper: WrapperWithAsyncTool,
1098
+ wrapper: createWrapper({
1099
+ components: customToolRegistry,
1100
+ streaming: true,
1101
+ }),
1144
1102
  });
1145
1103
  await (0, react_1.act)(async () => {
1146
1104
  await result.current.sendThreadMessage("Use async tool", {
@@ -1188,22 +1146,6 @@ describe("TamboThreadProvider", () => {
1188
1146
  ],
1189
1147
  },
1190
1148
  ];
1191
- const WrapperWithoutTransform = ({ children, }) => {
1192
- const client = (0, tambo_client_provider_1.useTamboClient)();
1193
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1194
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1195
- client,
1196
- queryClient,
1197
- isUpdatingToken: false,
1198
- } },
1199
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: toolWithoutTransform },
1200
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1201
- currentTimeContextHelper: () => null,
1202
- currentPageContextHelper: () => null,
1203
- } },
1204
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1205
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
1206
- };
1207
1149
  const mockToolCallResponse = {
1208
1150
  responseMessageDto: {
1209
1151
  id: "tool-call-1",
@@ -1236,7 +1178,7 @@ describe("TamboThreadProvider", () => {
1236
1178
  mcpAccessToken: "test-mcp-access-token",
1237
1179
  });
1238
1180
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1239
- wrapper: WrapperWithoutTransform,
1181
+ wrapper: createWrapper({ components: toolWithoutTransform }),
1240
1182
  });
1241
1183
  await (0, react_1.act)(async () => {
1242
1184
  await result.current.sendThreadMessage("Use tool without transform", {
@@ -1286,22 +1228,6 @@ describe("TamboThreadProvider", () => {
1286
1228
  ],
1287
1229
  },
1288
1230
  ];
1289
- const WrapperWithErrorTool = ({ children, }) => {
1290
- const client = (0, tambo_client_provider_1.useTamboClient)();
1291
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1292
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1293
- client,
1294
- queryClient,
1295
- isUpdatingToken: false,
1296
- } },
1297
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: toolWithTransform },
1298
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1299
- currentTimeContextHelper: () => null,
1300
- currentPageContextHelper: () => null,
1301
- } },
1302
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1303
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false }, children))))));
1304
- };
1305
1231
  const mockToolCallResponse = {
1306
1232
  responseMessageDto: {
1307
1233
  id: "tool-call-1",
@@ -1334,7 +1260,7 @@ describe("TamboThreadProvider", () => {
1334
1260
  mcpAccessToken: "test-mcp-access-token",
1335
1261
  });
1336
1262
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1337
- wrapper: WrapperWithErrorTool,
1263
+ wrapper: createWrapper({ components: toolWithTransform }),
1338
1264
  });
1339
1265
  await (0, react_1.act)(async () => {
1340
1266
  await result.current.sendThreadMessage("Use error tool", {
@@ -1360,26 +1286,339 @@ describe("TamboThreadProvider", () => {
1360
1286
  }));
1361
1287
  });
1362
1288
  });
1289
+ describe("tamboStreamableHint streaming behavior", () => {
1290
+ it("should call streamable tool during streaming when tamboStreamableHint is true", async () => {
1291
+ const streamableToolFn = jest
1292
+ .fn()
1293
+ .mockResolvedValue({ data: "streamed" });
1294
+ const customToolRegistry = [
1295
+ {
1296
+ name: "TestComponent",
1297
+ component: () => react_2.default.createElement("div", null, "Test"),
1298
+ description: "Test",
1299
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1300
+ associatedTools: [
1301
+ {
1302
+ name: "streamable-tool",
1303
+ tool: streamableToolFn,
1304
+ description: "Tool safe for streaming",
1305
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1306
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1307
+ annotations: { tamboStreamableHint: true },
1308
+ },
1309
+ ],
1310
+ },
1311
+ ];
1312
+ // First chunk initializes finalMessage
1313
+ const mockInitialChunk = {
1314
+ responseMessageDto: {
1315
+ id: "initial-chunk",
1316
+ content: [{ type: "text", text: "Starting..." }],
1317
+ role: "assistant",
1318
+ threadId: "test-thread-1",
1319
+ componentState: {},
1320
+ createdAt: new Date().toISOString(),
1321
+ },
1322
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1323
+ mcpAccessToken: "test-mcp-access-token",
1324
+ };
1325
+ // Second chunk has the tool call - this triggers streaming tool handling
1326
+ const mockToolCallChunk = {
1327
+ responseMessageDto: {
1328
+ id: "initial-chunk", // Same ID as initial - it's an update
1329
+ content: [{ type: "text", text: "Streaming..." }],
1330
+ role: "assistant",
1331
+ threadId: "test-thread-1",
1332
+ component: {
1333
+ componentName: "",
1334
+ componentState: {},
1335
+ message: "",
1336
+ props: {},
1337
+ toolCallRequest: {
1338
+ toolName: "streamable-tool",
1339
+ parameters: [
1340
+ { parameterName: "input", parameterValue: "stream-test" },
1341
+ ],
1342
+ },
1343
+ },
1344
+ componentState: {},
1345
+ createdAt: new Date().toISOString(),
1346
+ },
1347
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1348
+ mcpAccessToken: "test-mcp-access-token",
1349
+ };
1350
+ const mockFinalChunk = {
1351
+ responseMessageDto: {
1352
+ id: "initial-chunk",
1353
+ content: [{ type: "text", text: "Complete" }],
1354
+ role: "assistant",
1355
+ threadId: "test-thread-1",
1356
+ componentState: {},
1357
+ createdAt: new Date().toISOString(),
1358
+ },
1359
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1360
+ mcpAccessToken: "test-mcp-access-token",
1361
+ };
1362
+ const mockAsyncIterator = {
1363
+ [Symbol.asyncIterator]: async function* () {
1364
+ yield mockInitialChunk;
1365
+ yield mockToolCallChunk;
1366
+ yield mockFinalChunk;
1367
+ },
1368
+ };
1369
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1370
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1371
+ wrapper: createWrapper({
1372
+ components: customToolRegistry,
1373
+ streaming: true,
1374
+ }),
1375
+ });
1376
+ await (0, react_1.act)(async () => {
1377
+ await result.current.sendThreadMessage("Test streamable tool", {
1378
+ threadId: "test-thread-1",
1379
+ streamResponse: true,
1380
+ });
1381
+ });
1382
+ // Streamable tool should be called during streaming
1383
+ expect(streamableToolFn).toHaveBeenCalledWith({ input: "stream-test" });
1384
+ });
1385
+ it("should NOT call non-streamable tool during streaming", async () => {
1386
+ const nonStreamableToolFn = jest
1387
+ .fn()
1388
+ .mockResolvedValue({ data: "result" });
1389
+ const customToolRegistry = [
1390
+ {
1391
+ name: "TestComponent",
1392
+ component: () => react_2.default.createElement("div", null, "Test"),
1393
+ description: "Test",
1394
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1395
+ associatedTools: [
1396
+ {
1397
+ name: "non-streamable-tool",
1398
+ tool: nonStreamableToolFn,
1399
+ description: "Tool not safe for streaming",
1400
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1401
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1402
+ // No tamboStreamableHint - defaults to false
1403
+ },
1404
+ ],
1405
+ },
1406
+ ];
1407
+ // First chunk initializes finalMessage
1408
+ const mockInitialChunk = {
1409
+ responseMessageDto: {
1410
+ id: "streaming-chunk",
1411
+ content: [{ type: "text", text: "Starting..." }],
1412
+ role: "assistant",
1413
+ threadId: "test-thread-1",
1414
+ componentState: {},
1415
+ createdAt: new Date().toISOString(),
1416
+ },
1417
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1418
+ mcpAccessToken: "test-mcp-access-token",
1419
+ };
1420
+ // Second chunk has the tool call - but tool is NOT streamable
1421
+ const mockToolCallChunk = {
1422
+ responseMessageDto: {
1423
+ id: "streaming-chunk",
1424
+ content: [{ type: "text", text: "Streaming..." }],
1425
+ role: "assistant",
1426
+ threadId: "test-thread-1",
1427
+ component: {
1428
+ componentName: "",
1429
+ componentState: {},
1430
+ message: "",
1431
+ props: {},
1432
+ toolCallRequest: {
1433
+ toolName: "non-streamable-tool",
1434
+ parameters: [{ parameterName: "input", parameterValue: "test" }],
1435
+ },
1436
+ },
1437
+ componentState: {},
1438
+ createdAt: new Date().toISOString(),
1439
+ },
1440
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1441
+ mcpAccessToken: "test-mcp-access-token",
1442
+ };
1443
+ const mockFinalChunk = {
1444
+ responseMessageDto: {
1445
+ id: "streaming-chunk",
1446
+ content: [{ type: "text", text: "Complete" }],
1447
+ role: "assistant",
1448
+ threadId: "test-thread-1",
1449
+ componentState: {},
1450
+ createdAt: new Date().toISOString(),
1451
+ },
1452
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1453
+ mcpAccessToken: "test-mcp-access-token",
1454
+ };
1455
+ const mockAsyncIterator = {
1456
+ [Symbol.asyncIterator]: async function* () {
1457
+ yield mockInitialChunk;
1458
+ yield mockToolCallChunk;
1459
+ yield mockFinalChunk;
1460
+ },
1461
+ };
1462
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1463
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1464
+ wrapper: createWrapper({
1465
+ components: customToolRegistry,
1466
+ streaming: true,
1467
+ }),
1468
+ });
1469
+ await (0, react_1.act)(async () => {
1470
+ await result.current.sendThreadMessage("Test non-streamable tool", {
1471
+ threadId: "test-thread-1",
1472
+ streamResponse: true,
1473
+ });
1474
+ });
1475
+ // Non-streamable tool should NOT be called during the streaming chunk phase
1476
+ // (it would only be called when generationStage is COMPLETE with a toolCallRequest)
1477
+ expect(nonStreamableToolFn).not.toHaveBeenCalled();
1478
+ });
1479
+ it("should only call streamable tools during streaming when mixed", async () => {
1480
+ const streamableToolFn = jest
1481
+ .fn()
1482
+ .mockResolvedValue({ data: "streamed" });
1483
+ const nonStreamableToolFn = jest
1484
+ .fn()
1485
+ .mockResolvedValue({ data: "not-streamed" });
1486
+ const customToolRegistry = [
1487
+ {
1488
+ name: "TestComponent",
1489
+ component: () => react_2.default.createElement("div", null, "Test"),
1490
+ description: "Test",
1491
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1492
+ associatedTools: [
1493
+ {
1494
+ name: "streamable-tool",
1495
+ tool: streamableToolFn,
1496
+ description: "Tool safe for streaming",
1497
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1498
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1499
+ annotations: { tamboStreamableHint: true },
1500
+ },
1501
+ {
1502
+ name: "non-streamable-tool",
1503
+ tool: nonStreamableToolFn,
1504
+ description: "Tool not safe for streaming",
1505
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1506
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1507
+ annotations: { tamboStreamableHint: false },
1508
+ },
1509
+ ],
1510
+ },
1511
+ ];
1512
+ // First chunk initializes finalMessage
1513
+ const mockInitialChunk = {
1514
+ responseMessageDto: {
1515
+ id: "streaming-chunk",
1516
+ content: [{ type: "text", text: "Starting..." }],
1517
+ role: "assistant",
1518
+ threadId: "test-thread-1",
1519
+ componentState: {},
1520
+ createdAt: new Date().toISOString(),
1521
+ },
1522
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1523
+ mcpAccessToken: "test-mcp-access-token",
1524
+ };
1525
+ // Second chunk calls the streamable tool
1526
+ const mockStreamableToolChunk = {
1527
+ responseMessageDto: {
1528
+ id: "streaming-chunk",
1529
+ content: [{ type: "text", text: "Calling streamable..." }],
1530
+ role: "assistant",
1531
+ threadId: "test-thread-1",
1532
+ component: {
1533
+ componentName: "",
1534
+ componentState: {},
1535
+ message: "",
1536
+ props: {},
1537
+ toolCallRequest: {
1538
+ toolName: "streamable-tool",
1539
+ parameters: [
1540
+ { parameterName: "input", parameterValue: "streamed-input" },
1541
+ ],
1542
+ },
1543
+ },
1544
+ componentState: {},
1545
+ createdAt: new Date().toISOString(),
1546
+ },
1547
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1548
+ mcpAccessToken: "test-mcp-access-token",
1549
+ };
1550
+ // Third chunk calls the non-streamable tool
1551
+ const mockNonStreamableToolChunk = {
1552
+ responseMessageDto: {
1553
+ id: "streaming-chunk",
1554
+ content: [{ type: "text", text: "Calling non-streamable..." }],
1555
+ role: "assistant",
1556
+ threadId: "test-thread-1",
1557
+ component: {
1558
+ componentName: "",
1559
+ componentState: {},
1560
+ message: "",
1561
+ props: {},
1562
+ toolCallRequest: {
1563
+ toolName: "non-streamable-tool",
1564
+ parameters: [
1565
+ {
1566
+ parameterName: "input",
1567
+ parameterValue: "non-streamed-input",
1568
+ },
1569
+ ],
1570
+ },
1571
+ },
1572
+ componentState: {},
1573
+ createdAt: new Date().toISOString(),
1574
+ },
1575
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1576
+ mcpAccessToken: "test-mcp-access-token",
1577
+ };
1578
+ const mockFinalChunk = {
1579
+ responseMessageDto: {
1580
+ id: "streaming-chunk",
1581
+ content: [{ type: "text", text: "Complete" }],
1582
+ role: "assistant",
1583
+ threadId: "test-thread-1",
1584
+ componentState: {},
1585
+ createdAt: new Date().toISOString(),
1586
+ },
1587
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1588
+ mcpAccessToken: "test-mcp-access-token",
1589
+ };
1590
+ const mockAsyncIterator = {
1591
+ [Symbol.asyncIterator]: async function* () {
1592
+ yield mockInitialChunk;
1593
+ yield mockStreamableToolChunk;
1594
+ yield mockNonStreamableToolChunk;
1595
+ yield mockFinalChunk;
1596
+ },
1597
+ };
1598
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1599
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1600
+ wrapper: createWrapper({
1601
+ components: customToolRegistry,
1602
+ streaming: true,
1603
+ }),
1604
+ });
1605
+ await (0, react_1.act)(async () => {
1606
+ await result.current.sendThreadMessage("Test mixed tools", {
1607
+ threadId: "test-thread-1",
1608
+ streamResponse: true,
1609
+ });
1610
+ });
1611
+ // Only the streamable tool should be called during streaming
1612
+ expect(streamableToolFn).toHaveBeenCalledWith({
1613
+ input: "streamed-input",
1614
+ });
1615
+ expect(nonStreamableToolFn).not.toHaveBeenCalled();
1616
+ });
1617
+ });
1363
1618
  describe("auto-generate thread name", () => {
1364
1619
  it("should auto-generate thread name after reaching threshold", async () => {
1365
- const WrapperWithAutoGenerate = ({ children, }) => {
1366
- const client = (0, tambo_client_provider_1.useTamboClient)();
1367
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1368
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1369
- client,
1370
- queryClient,
1371
- isUpdatingToken: false,
1372
- } },
1373
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
1374
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1375
- currentTimeContextHelper: () => null,
1376
- currentPageContextHelper: () => null,
1377
- } },
1378
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1379
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false, autoGenerateNameThreshold: 2 }, children))))));
1380
- };
1381
1620
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1382
- wrapper: WrapperWithAutoGenerate,
1621
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1383
1622
  });
1384
1623
  const existingThread = createMockThread({
1385
1624
  id: "test-thread-1",
@@ -1416,24 +1655,11 @@ describe("TamboThreadProvider", () => {
1416
1655
  expect(mockQueryClient.setQueryData).toHaveBeenCalledWith(["threads", "test-project-id", undefined], expect.any(Function));
1417
1656
  });
1418
1657
  it("should NOT auto-generate when autoGenerateThreadName is false", async () => {
1419
- const WrapperWithDisabled = ({ children, }) => {
1420
- const client = (0, tambo_client_provider_1.useTamboClient)();
1421
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1422
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1423
- client,
1424
- queryClient,
1425
- isUpdatingToken: false,
1426
- } },
1427
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
1428
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1429
- currentTimeContextHelper: () => null,
1430
- currentPageContextHelper: () => null,
1431
- } },
1432
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1433
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false, autoGenerateThreadName: false, autoGenerateNameThreshold: 2 }, children))))));
1434
- };
1435
1658
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1436
- wrapper: WrapperWithDisabled,
1659
+ wrapper: createWrapper({
1660
+ autoGenerateThreadName: false,
1661
+ autoGenerateNameThreshold: 2,
1662
+ }),
1437
1663
  });
1438
1664
  const existingThread = createMockThread({
1439
1665
  id: "test-thread-1",
@@ -1466,24 +1692,8 @@ describe("TamboThreadProvider", () => {
1466
1692
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
1467
1693
  });
1468
1694
  it("should NOT auto-generate when thread already has a name", async () => {
1469
- const WrapperWithAutoGenerate = ({ children, }) => {
1470
- const client = (0, tambo_client_provider_1.useTamboClient)();
1471
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1472
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1473
- client,
1474
- queryClient,
1475
- isUpdatingToken: false,
1476
- } },
1477
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
1478
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1479
- currentTimeContextHelper: () => null,
1480
- currentPageContextHelper: () => null,
1481
- } },
1482
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1483
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false, autoGenerateNameThreshold: 2 }, children))))));
1484
- };
1485
1695
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1486
- wrapper: WrapperWithAutoGenerate,
1696
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1487
1697
  });
1488
1698
  const threadWithName = createMockThread({
1489
1699
  id: "test-thread-1",
@@ -1521,24 +1731,8 @@ describe("TamboThreadProvider", () => {
1521
1731
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
1522
1732
  });
1523
1733
  it("should NOT auto-generate for placeholder thread", async () => {
1524
- const WrapperWithAutoGenerate = ({ children, }) => {
1525
- const client = (0, tambo_client_provider_1.useTamboClient)();
1526
- const queryClient = (0, tambo_client_provider_1.useTamboQueryClient)();
1527
- return (react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
1528
- client,
1529
- queryClient,
1530
- isUpdatingToken: false,
1531
- } },
1532
- react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, { components: mockRegistry },
1533
- react_2.default.createElement(tambo_context_helpers_provider_1.TamboContextHelpersProvider, { contextHelpers: {
1534
- currentTimeContextHelper: () => null,
1535
- currentPageContextHelper: () => null,
1536
- } },
1537
- react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
1538
- react_2.default.createElement(tambo_thread_provider_1.TamboThreadProvider, { streaming: false, autoGenerateNameThreshold: 2 }, children))))));
1539
- };
1540
1734
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1541
- wrapper: WrapperWithAutoGenerate,
1735
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1542
1736
  });
1543
1737
  // Stay on placeholder thread
1544
1738
  expect(result.current.thread.id).toBe("placeholder");