@tambo-ai/react 0.68.0 → 0.69.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 (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 +53 -42
  92. package/dist/providers/tambo-thread-provider.js.map +1 -1
  93. package/dist/providers/tambo-thread-provider.test.js +368 -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 +53 -42
  250. package/esm/providers/tambo-thread-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider.test.js +368 -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");
@@ -978,22 +877,6 @@ describe("TamboThreadProvider", () => {
978
877
  ],
979
878
  },
980
879
  ];
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
880
  const mockToolCallResponse = {
998
881
  responseMessageDto: {
999
882
  id: "tool-call-1",
@@ -1026,7 +909,7 @@ describe("TamboThreadProvider", () => {
1026
909
  mcpAccessToken: "test-mcp-access-token",
1027
910
  });
1028
911
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1029
- wrapper: WrapperWithCustomTool,
912
+ wrapper: createWrapper({ components: customToolRegistry }),
1030
913
  });
1031
914
  await (0, react_1.act)(async () => {
1032
915
  await result.current.sendThreadMessage("Use custom tool", {
@@ -1079,22 +962,6 @@ describe("TamboThreadProvider", () => {
1079
962
  ],
1080
963
  },
1081
964
  ];
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
965
  const mockToolCallChunk = {
1099
966
  responseMessageDto: {
1100
967
  id: "tool-call-chunk",
@@ -1140,7 +1007,10 @@ describe("TamboThreadProvider", () => {
1140
1007
  },
1141
1008
  });
1142
1009
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1143
- wrapper: WrapperWithAsyncTool,
1010
+ wrapper: createWrapper({
1011
+ components: customToolRegistry,
1012
+ streaming: true,
1013
+ }),
1144
1014
  });
1145
1015
  await (0, react_1.act)(async () => {
1146
1016
  await result.current.sendThreadMessage("Use async tool", {
@@ -1188,22 +1058,6 @@ describe("TamboThreadProvider", () => {
1188
1058
  ],
1189
1059
  },
1190
1060
  ];
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
1061
  const mockToolCallResponse = {
1208
1062
  responseMessageDto: {
1209
1063
  id: "tool-call-1",
@@ -1236,7 +1090,7 @@ describe("TamboThreadProvider", () => {
1236
1090
  mcpAccessToken: "test-mcp-access-token",
1237
1091
  });
1238
1092
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1239
- wrapper: WrapperWithoutTransform,
1093
+ wrapper: createWrapper({ components: toolWithoutTransform }),
1240
1094
  });
1241
1095
  await (0, react_1.act)(async () => {
1242
1096
  await result.current.sendThreadMessage("Use tool without transform", {
@@ -1286,22 +1140,6 @@ describe("TamboThreadProvider", () => {
1286
1140
  ],
1287
1141
  },
1288
1142
  ];
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
1143
  const mockToolCallResponse = {
1306
1144
  responseMessageDto: {
1307
1145
  id: "tool-call-1",
@@ -1334,7 +1172,7 @@ describe("TamboThreadProvider", () => {
1334
1172
  mcpAccessToken: "test-mcp-access-token",
1335
1173
  });
1336
1174
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1337
- wrapper: WrapperWithErrorTool,
1175
+ wrapper: createWrapper({ components: toolWithTransform }),
1338
1176
  });
1339
1177
  await (0, react_1.act)(async () => {
1340
1178
  await result.current.sendThreadMessage("Use error tool", {
@@ -1360,26 +1198,339 @@ describe("TamboThreadProvider", () => {
1360
1198
  }));
1361
1199
  });
1362
1200
  });
1201
+ describe("tamboStreamableHint streaming behavior", () => {
1202
+ it("should call streamable tool during streaming when tamboStreamableHint is true", async () => {
1203
+ const streamableToolFn = jest
1204
+ .fn()
1205
+ .mockResolvedValue({ data: "streamed" });
1206
+ const customToolRegistry = [
1207
+ {
1208
+ name: "TestComponent",
1209
+ component: () => react_2.default.createElement("div", null, "Test"),
1210
+ description: "Test",
1211
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1212
+ associatedTools: [
1213
+ {
1214
+ name: "streamable-tool",
1215
+ tool: streamableToolFn,
1216
+ description: "Tool safe for streaming",
1217
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1218
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1219
+ annotations: { tamboStreamableHint: true },
1220
+ },
1221
+ ],
1222
+ },
1223
+ ];
1224
+ // First chunk initializes finalMessage
1225
+ const mockInitialChunk = {
1226
+ responseMessageDto: {
1227
+ id: "initial-chunk",
1228
+ content: [{ type: "text", text: "Starting..." }],
1229
+ role: "assistant",
1230
+ threadId: "test-thread-1",
1231
+ componentState: {},
1232
+ createdAt: new Date().toISOString(),
1233
+ },
1234
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1235
+ mcpAccessToken: "test-mcp-access-token",
1236
+ };
1237
+ // Second chunk has the tool call - this triggers streaming tool handling
1238
+ const mockToolCallChunk = {
1239
+ responseMessageDto: {
1240
+ id: "initial-chunk", // Same ID as initial - it's an update
1241
+ content: [{ type: "text", text: "Streaming..." }],
1242
+ role: "assistant",
1243
+ threadId: "test-thread-1",
1244
+ component: {
1245
+ componentName: "",
1246
+ componentState: {},
1247
+ message: "",
1248
+ props: {},
1249
+ toolCallRequest: {
1250
+ toolName: "streamable-tool",
1251
+ parameters: [
1252
+ { parameterName: "input", parameterValue: "stream-test" },
1253
+ ],
1254
+ },
1255
+ },
1256
+ componentState: {},
1257
+ createdAt: new Date().toISOString(),
1258
+ },
1259
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1260
+ mcpAccessToken: "test-mcp-access-token",
1261
+ };
1262
+ const mockFinalChunk = {
1263
+ responseMessageDto: {
1264
+ id: "initial-chunk",
1265
+ content: [{ type: "text", text: "Complete" }],
1266
+ role: "assistant",
1267
+ threadId: "test-thread-1",
1268
+ componentState: {},
1269
+ createdAt: new Date().toISOString(),
1270
+ },
1271
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1272
+ mcpAccessToken: "test-mcp-access-token",
1273
+ };
1274
+ const mockAsyncIterator = {
1275
+ [Symbol.asyncIterator]: async function* () {
1276
+ yield mockInitialChunk;
1277
+ yield mockToolCallChunk;
1278
+ yield mockFinalChunk;
1279
+ },
1280
+ };
1281
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1282
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1283
+ wrapper: createWrapper({
1284
+ components: customToolRegistry,
1285
+ streaming: true,
1286
+ }),
1287
+ });
1288
+ await (0, react_1.act)(async () => {
1289
+ await result.current.sendThreadMessage("Test streamable tool", {
1290
+ threadId: "test-thread-1",
1291
+ streamResponse: true,
1292
+ });
1293
+ });
1294
+ // Streamable tool should be called during streaming
1295
+ expect(streamableToolFn).toHaveBeenCalledWith({ input: "stream-test" });
1296
+ });
1297
+ it("should NOT call non-streamable tool during streaming", async () => {
1298
+ const nonStreamableToolFn = jest
1299
+ .fn()
1300
+ .mockResolvedValue({ data: "result" });
1301
+ const customToolRegistry = [
1302
+ {
1303
+ name: "TestComponent",
1304
+ component: () => react_2.default.createElement("div", null, "Test"),
1305
+ description: "Test",
1306
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1307
+ associatedTools: [
1308
+ {
1309
+ name: "non-streamable-tool",
1310
+ tool: nonStreamableToolFn,
1311
+ description: "Tool not safe for streaming",
1312
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1313
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1314
+ // No tamboStreamableHint - defaults to false
1315
+ },
1316
+ ],
1317
+ },
1318
+ ];
1319
+ // First chunk initializes finalMessage
1320
+ const mockInitialChunk = {
1321
+ responseMessageDto: {
1322
+ id: "streaming-chunk",
1323
+ content: [{ type: "text", text: "Starting..." }],
1324
+ role: "assistant",
1325
+ threadId: "test-thread-1",
1326
+ componentState: {},
1327
+ createdAt: new Date().toISOString(),
1328
+ },
1329
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1330
+ mcpAccessToken: "test-mcp-access-token",
1331
+ };
1332
+ // Second chunk has the tool call - but tool is NOT streamable
1333
+ const mockToolCallChunk = {
1334
+ responseMessageDto: {
1335
+ id: "streaming-chunk",
1336
+ content: [{ type: "text", text: "Streaming..." }],
1337
+ role: "assistant",
1338
+ threadId: "test-thread-1",
1339
+ component: {
1340
+ componentName: "",
1341
+ componentState: {},
1342
+ message: "",
1343
+ props: {},
1344
+ toolCallRequest: {
1345
+ toolName: "non-streamable-tool",
1346
+ parameters: [{ parameterName: "input", parameterValue: "test" }],
1347
+ },
1348
+ },
1349
+ componentState: {},
1350
+ createdAt: new Date().toISOString(),
1351
+ },
1352
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1353
+ mcpAccessToken: "test-mcp-access-token",
1354
+ };
1355
+ const mockFinalChunk = {
1356
+ responseMessageDto: {
1357
+ id: "streaming-chunk",
1358
+ content: [{ type: "text", text: "Complete" }],
1359
+ role: "assistant",
1360
+ threadId: "test-thread-1",
1361
+ componentState: {},
1362
+ createdAt: new Date().toISOString(),
1363
+ },
1364
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1365
+ mcpAccessToken: "test-mcp-access-token",
1366
+ };
1367
+ const mockAsyncIterator = {
1368
+ [Symbol.asyncIterator]: async function* () {
1369
+ yield mockInitialChunk;
1370
+ yield mockToolCallChunk;
1371
+ yield mockFinalChunk;
1372
+ },
1373
+ };
1374
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1375
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1376
+ wrapper: createWrapper({
1377
+ components: customToolRegistry,
1378
+ streaming: true,
1379
+ }),
1380
+ });
1381
+ await (0, react_1.act)(async () => {
1382
+ await result.current.sendThreadMessage("Test non-streamable tool", {
1383
+ threadId: "test-thread-1",
1384
+ streamResponse: true,
1385
+ });
1386
+ });
1387
+ // Non-streamable tool should NOT be called during the streaming chunk phase
1388
+ // (it would only be called when generationStage is COMPLETE with a toolCallRequest)
1389
+ expect(nonStreamableToolFn).not.toHaveBeenCalled();
1390
+ });
1391
+ it("should only call streamable tools during streaming when mixed", async () => {
1392
+ const streamableToolFn = jest
1393
+ .fn()
1394
+ .mockResolvedValue({ data: "streamed" });
1395
+ const nonStreamableToolFn = jest
1396
+ .fn()
1397
+ .mockResolvedValue({ data: "not-streamed" });
1398
+ const customToolRegistry = [
1399
+ {
1400
+ name: "TestComponent",
1401
+ component: () => react_2.default.createElement("div", null, "Test"),
1402
+ description: "Test",
1403
+ propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
1404
+ associatedTools: [
1405
+ {
1406
+ name: "streamable-tool",
1407
+ tool: streamableToolFn,
1408
+ description: "Tool safe for streaming",
1409
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1410
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1411
+ annotations: { tamboStreamableHint: true },
1412
+ },
1413
+ {
1414
+ name: "non-streamable-tool",
1415
+ tool: nonStreamableToolFn,
1416
+ description: "Tool not safe for streaming",
1417
+ inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
1418
+ outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
1419
+ annotations: { tamboStreamableHint: false },
1420
+ },
1421
+ ],
1422
+ },
1423
+ ];
1424
+ // First chunk initializes finalMessage
1425
+ const mockInitialChunk = {
1426
+ responseMessageDto: {
1427
+ id: "streaming-chunk",
1428
+ content: [{ type: "text", text: "Starting..." }],
1429
+ role: "assistant",
1430
+ threadId: "test-thread-1",
1431
+ componentState: {},
1432
+ createdAt: new Date().toISOString(),
1433
+ },
1434
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1435
+ mcpAccessToken: "test-mcp-access-token",
1436
+ };
1437
+ // Second chunk calls the streamable tool
1438
+ const mockStreamableToolChunk = {
1439
+ responseMessageDto: {
1440
+ id: "streaming-chunk",
1441
+ content: [{ type: "text", text: "Calling streamable..." }],
1442
+ role: "assistant",
1443
+ threadId: "test-thread-1",
1444
+ component: {
1445
+ componentName: "",
1446
+ componentState: {},
1447
+ message: "",
1448
+ props: {},
1449
+ toolCallRequest: {
1450
+ toolName: "streamable-tool",
1451
+ parameters: [
1452
+ { parameterName: "input", parameterValue: "streamed-input" },
1453
+ ],
1454
+ },
1455
+ },
1456
+ componentState: {},
1457
+ createdAt: new Date().toISOString(),
1458
+ },
1459
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1460
+ mcpAccessToken: "test-mcp-access-token",
1461
+ };
1462
+ // Third chunk calls the non-streamable tool
1463
+ const mockNonStreamableToolChunk = {
1464
+ responseMessageDto: {
1465
+ id: "streaming-chunk",
1466
+ content: [{ type: "text", text: "Calling non-streamable..." }],
1467
+ role: "assistant",
1468
+ threadId: "test-thread-1",
1469
+ component: {
1470
+ componentName: "",
1471
+ componentState: {},
1472
+ message: "",
1473
+ props: {},
1474
+ toolCallRequest: {
1475
+ toolName: "non-streamable-tool",
1476
+ parameters: [
1477
+ {
1478
+ parameterName: "input",
1479
+ parameterValue: "non-streamed-input",
1480
+ },
1481
+ ],
1482
+ },
1483
+ },
1484
+ componentState: {},
1485
+ createdAt: new Date().toISOString(),
1486
+ },
1487
+ generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
1488
+ mcpAccessToken: "test-mcp-access-token",
1489
+ };
1490
+ const mockFinalChunk = {
1491
+ responseMessageDto: {
1492
+ id: "streaming-chunk",
1493
+ content: [{ type: "text", text: "Complete" }],
1494
+ role: "assistant",
1495
+ threadId: "test-thread-1",
1496
+ componentState: {},
1497
+ createdAt: new Date().toISOString(),
1498
+ },
1499
+ generationStage: generate_component_response_1.GenerationStage.COMPLETE,
1500
+ mcpAccessToken: "test-mcp-access-token",
1501
+ };
1502
+ const mockAsyncIterator = {
1503
+ [Symbol.asyncIterator]: async function* () {
1504
+ yield mockInitialChunk;
1505
+ yield mockStreamableToolChunk;
1506
+ yield mockNonStreamableToolChunk;
1507
+ yield mockFinalChunk;
1508
+ },
1509
+ };
1510
+ jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValueOnce(mockAsyncIterator);
1511
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1512
+ wrapper: createWrapper({
1513
+ components: customToolRegistry,
1514
+ streaming: true,
1515
+ }),
1516
+ });
1517
+ await (0, react_1.act)(async () => {
1518
+ await result.current.sendThreadMessage("Test mixed tools", {
1519
+ threadId: "test-thread-1",
1520
+ streamResponse: true,
1521
+ });
1522
+ });
1523
+ // Only the streamable tool should be called during streaming
1524
+ expect(streamableToolFn).toHaveBeenCalledWith({
1525
+ input: "streamed-input",
1526
+ });
1527
+ expect(nonStreamableToolFn).not.toHaveBeenCalled();
1528
+ });
1529
+ });
1363
1530
  describe("auto-generate thread name", () => {
1364
1531
  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
1532
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1382
- wrapper: WrapperWithAutoGenerate,
1533
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1383
1534
  });
1384
1535
  const existingThread = createMockThread({
1385
1536
  id: "test-thread-1",
@@ -1416,24 +1567,11 @@ describe("TamboThreadProvider", () => {
1416
1567
  expect(mockQueryClient.setQueryData).toHaveBeenCalledWith(["threads", "test-project-id", undefined], expect.any(Function));
1417
1568
  });
1418
1569
  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
1570
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1436
- wrapper: WrapperWithDisabled,
1571
+ wrapper: createWrapper({
1572
+ autoGenerateThreadName: false,
1573
+ autoGenerateNameThreshold: 2,
1574
+ }),
1437
1575
  });
1438
1576
  const existingThread = createMockThread({
1439
1577
  id: "test-thread-1",
@@ -1466,24 +1604,8 @@ describe("TamboThreadProvider", () => {
1466
1604
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
1467
1605
  });
1468
1606
  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
1607
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1486
- wrapper: WrapperWithAutoGenerate,
1608
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1487
1609
  });
1488
1610
  const threadWithName = createMockThread({
1489
1611
  id: "test-thread-1",
@@ -1521,24 +1643,8 @@ describe("TamboThreadProvider", () => {
1521
1643
  expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
1522
1644
  });
1523
1645
  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
1646
  const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
1541
- wrapper: WrapperWithAutoGenerate,
1647
+ wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
1542
1648
  });
1543
1649
  // Stay on placeholder thread
1544
1650
  expect(result.current.thread.id).toBe("placeholder");