@tambo-ai/react 0.73.0 → 0.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +12 -12
  2. package/dist/hooks/use-component-state.d.ts +1 -1
  3. package/dist/hooks/use-component-state.js.map +1 -1
  4. package/dist/hooks/use-streaming-props.d.ts +1 -1
  5. package/dist/hooks/use-streaming-props.js +1 -1
  6. package/dist/hooks/use-streaming-props.js.map +1 -1
  7. package/dist/hooks/use-tambo-stream-status.d.ts +1 -1
  8. package/dist/hooks/use-tambo-stream-status.js +1 -1
  9. package/dist/hooks/use-tambo-stream-status.js.map +1 -1
  10. package/dist/mcp/mcp-hooks.d.ts +4 -0
  11. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  12. package/dist/mcp/mcp-hooks.js +4 -0
  13. package/dist/mcp/mcp-hooks.js.map +1 -1
  14. package/dist/providers/tambo-interactable-provider-partial-updates.test.js +3 -3
  15. package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  16. package/dist/providers/tambo-interactable-provider.js +2 -2
  17. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  18. package/dist/providers/tambo-interactable-provider.test.js +3 -3
  19. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  20. package/dist/providers/tambo-provider.d.ts +3 -0
  21. package/dist/providers/tambo-provider.d.ts.map +1 -1
  22. package/dist/providers/tambo-provider.js +3 -0
  23. package/dist/providers/tambo-provider.js.map +1 -1
  24. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  25. package/dist/providers/tambo-thread-input-provider.js +1 -0
  26. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  27. package/dist/util/resource-content-resolver.d.ts.map +1 -1
  28. package/dist/util/resource-content-resolver.js +2 -0
  29. package/dist/util/resource-content-resolver.js.map +1 -1
  30. package/dist/v1/__tests__/v1-interactables.test.d.ts +2 -0
  31. package/dist/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
  32. package/dist/v1/__tests__/v1-interactables.test.js +135 -0
  33. package/dist/v1/__tests__/v1-interactables.test.js.map +1 -0
  34. package/dist/v1/components/v1-component-renderer.d.ts +48 -0
  35. package/dist/v1/components/v1-component-renderer.d.ts.map +1 -0
  36. package/dist/v1/components/v1-component-renderer.js +137 -0
  37. package/dist/v1/components/v1-component-renderer.js.map +1 -0
  38. package/dist/v1/components/v1-component-renderer.test.d.ts +2 -0
  39. package/dist/v1/components/v1-component-renderer.test.d.ts.map +1 -0
  40. package/dist/v1/components/v1-component-renderer.test.js +270 -0
  41. package/dist/v1/components/v1-component-renderer.test.js.map +1 -0
  42. package/dist/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
  43. package/dist/v1/hooks/use-tambo-v1-component-state.js +2 -25
  44. package/dist/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
  45. package/dist/v1/hooks/use-tambo-v1-component-state.test.js +2 -1
  46. package/dist/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
  47. package/dist/v1/hooks/use-tambo-v1-messages.test.js +25 -1
  48. package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
  49. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +18 -0
  50. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  51. package/dist/v1/hooks/use-tambo-v1-send-message.js +204 -17
  52. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  53. package/dist/v1/hooks/use-tambo-v1-send-message.test.js +261 -7
  54. package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
  55. package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
  56. package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
  57. package/dist/v1/hooks/use-tambo-v1-stream-status.js +179 -0
  58. package/dist/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
  59. package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
  60. package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
  61. package/dist/v1/hooks/use-tambo-v1-stream-status.test.js +371 -0
  62. package/dist/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
  63. package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
  64. package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
  65. package/dist/v1/hooks/use-tambo-v1-suggestions.js +153 -87
  66. package/dist/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
  67. package/dist/v1/hooks/use-tambo-v1-suggestions.test.js +213 -134
  68. package/dist/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
  69. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js +148 -13
  70. package/dist/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
  71. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
  72. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
  73. package/dist/v1/hooks/use-tambo-v1-thread-list.js +11 -10
  74. package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
  75. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +37 -2
  76. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
  77. package/dist/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
  78. package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
  79. package/dist/v1/hooks/use-tambo-v1-thread.js +2 -7
  80. package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -1
  81. package/dist/v1/hooks/use-tambo-v1-thread.test.js +2 -0
  82. package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
  83. package/dist/v1/hooks/use-tambo-v1.d.ts +12 -28
  84. package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -1
  85. package/dist/v1/hooks/use-tambo-v1.js +164 -31
  86. package/dist/v1/hooks/use-tambo-v1.js.map +1 -1
  87. package/dist/v1/hooks/use-tambo-v1.test.js +891 -18
  88. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -1
  89. package/dist/v1/index.d.ts +7 -1
  90. package/dist/v1/index.d.ts.map +1 -1
  91. package/dist/v1/index.js +18 -1
  92. package/dist/v1/index.js.map +1 -1
  93. package/dist/v1/providers/tambo-v1-provider.d.ts +16 -6
  94. package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -1
  95. package/dist/v1/providers/tambo-v1-provider.js +14 -19
  96. package/dist/v1/providers/tambo-v1-provider.js.map +1 -1
  97. package/dist/v1/providers/tambo-v1-provider.test.js +34 -20
  98. package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -1
  99. package/dist/v1/providers/tambo-v1-stream-context.d.ts +3 -3
  100. package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
  101. package/dist/v1/providers/tambo-v1-stream-context.js +60 -12
  102. package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -1
  103. package/dist/v1/providers/tambo-v1-stream-context.test.js +49 -20
  104. package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
  105. package/dist/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
  106. package/dist/v1/providers/tambo-v1-stub-provider.js +2 -0
  107. package/dist/v1/providers/tambo-v1-stub-provider.js.map +1 -1
  108. package/dist/v1/providers/tambo-v1-stub-provider.test.js +7 -6
  109. package/dist/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
  110. package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts +1 -6
  111. package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
  112. package/dist/v1/providers/tambo-v1-thread-input-provider.js +14 -12
  113. package/dist/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
  114. package/dist/v1/types/event.d.ts +9 -1
  115. package/dist/v1/types/event.d.ts.map +1 -1
  116. package/dist/v1/types/event.js.map +1 -1
  117. package/dist/v1/types/event.test.js +5 -1
  118. package/dist/v1/types/event.test.js.map +1 -1
  119. package/dist/v1/types/message.d.ts +65 -7
  120. package/dist/v1/types/message.d.ts.map +1 -1
  121. package/dist/v1/types/message.js.map +1 -1
  122. package/dist/v1/types/thread.d.ts +4 -0
  123. package/dist/v1/types/thread.d.ts.map +1 -1
  124. package/dist/v1/types/thread.js.map +1 -1
  125. package/dist/v1/utils/event-accumulator.d.ts +40 -4
  126. package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
  127. package/dist/v1/utils/event-accumulator.js +444 -35
  128. package/dist/v1/utils/event-accumulator.js.map +1 -1
  129. package/dist/v1/utils/event-accumulator.test.js +1041 -28
  130. package/dist/v1/utils/event-accumulator.test.js.map +1 -1
  131. package/dist/v1/utils/registry-conversion.d.ts +9 -9
  132. package/dist/v1/utils/registry-conversion.d.ts.map +1 -1
  133. package/dist/v1/utils/registry-conversion.js +10 -11
  134. package/dist/v1/utils/registry-conversion.js.map +1 -1
  135. package/dist/v1/utils/registry-conversion.test.js +39 -11
  136. package/dist/v1/utils/registry-conversion.test.js.map +1 -1
  137. package/dist/v1/utils/thread-utils.d.ts +16 -0
  138. package/dist/v1/utils/thread-utils.d.ts.map +1 -0
  139. package/dist/v1/utils/thread-utils.js +34 -0
  140. package/dist/v1/utils/thread-utils.js.map +1 -0
  141. package/dist/v1/utils/tool-executor.d.ts.map +1 -1
  142. package/dist/v1/utils/tool-executor.js +2 -0
  143. package/dist/v1/utils/tool-executor.js.map +1 -1
  144. package/dist/v1/utils/tool-executor.test.js +5 -0
  145. package/dist/v1/utils/tool-executor.test.js.map +1 -1
  146. package/esm/context-helpers/context-helpers-provider.test.js +2 -2
  147. package/esm/context-helpers/context-helpers.test.js +1 -1
  148. package/esm/context-helpers/current-interactables-context-helper.d.ts +1 -1
  149. package/esm/context-helpers/current-page-context-helper.d.ts +1 -1
  150. package/esm/context-helpers/current-time-context-helper.d.ts +1 -1
  151. package/esm/context-helpers/index.d.ts +4 -4
  152. package/esm/context-helpers/index.js +4 -4
  153. package/esm/hoc/with-tambo-interactable.d.ts +1 -1
  154. package/esm/hoc/with-tambo-interactable.js +2 -2
  155. package/esm/hoc/with-tambo-interactable.test.js +3 -3
  156. package/esm/hooks/index.d.ts +8 -8
  157. package/esm/hooks/index.js +8 -8
  158. package/esm/hooks/react-query-hooks.js +1 -1
  159. package/esm/hooks/use-component-state.d.ts +1 -1
  160. package/esm/hooks/use-component-state.js +3 -3
  161. package/esm/hooks/use-component-state.js.map +1 -1
  162. package/esm/hooks/use-component-state.test.js +5 -5
  163. package/esm/hooks/use-current-message.d.ts +1 -1
  164. package/esm/hooks/use-current-message.test.js +1 -1
  165. package/esm/hooks/use-message-images.test.js +1 -1
  166. package/esm/hooks/use-streaming-props.d.ts +1 -1
  167. package/esm/hooks/use-streaming-props.js +1 -1
  168. package/esm/hooks/use-streaming-props.js.map +1 -1
  169. package/esm/hooks/use-suggestions.d.ts +2 -2
  170. package/esm/hooks/use-suggestions.js +10 -10
  171. package/esm/hooks/use-suggestions.test.js +7 -7
  172. package/esm/hooks/use-tambo-stream-status.d.ts +1 -1
  173. package/esm/hooks/use-tambo-stream-status.js +4 -4
  174. package/esm/hooks/use-tambo-stream-status.js.map +1 -1
  175. package/esm/hooks/use-tambo-stream-status.test.js +4 -4
  176. package/esm/hooks/use-tambo-threads.js +3 -3
  177. package/esm/hooks/use-tambo-threads.test.js +3 -3
  178. package/esm/hooks/use-tambo-voice.js +2 -2
  179. package/esm/hooks/use-tambo-voice.test.js +3 -3
  180. package/esm/index.d.ts +22 -22
  181. package/esm/index.js +15 -15
  182. package/esm/mcp/elicitation.d.ts +1 -1
  183. package/esm/mcp/elicitation.test.js +1 -1
  184. package/esm/mcp/index.d.ts +7 -7
  185. package/esm/mcp/index.js +3 -3
  186. package/esm/mcp/mcp-client.d.ts +1 -1
  187. package/esm/mcp/mcp-client.js +1 -1
  188. package/esm/mcp/mcp-client.test.js +1 -1
  189. package/esm/mcp/mcp-hooks.d.ts +5 -1
  190. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  191. package/esm/mcp/mcp-hooks.js +8 -4
  192. package/esm/mcp/mcp-hooks.js.map +1 -1
  193. package/esm/mcp/mcp-hooks.test.js +6 -6
  194. package/esm/mcp/tambo-mcp-provider.d.ts +4 -4
  195. package/esm/mcp/tambo-mcp-provider.js +7 -7
  196. package/esm/mcp/tambo-mcp-provider.test.js +5 -5
  197. package/esm/mcp/use-mcp-servers.test.js +4 -4
  198. package/esm/model/generate-component-response.d.ts +1 -1
  199. package/esm/model/tambo-interactable.d.ts +1 -1
  200. package/esm/model/tambo-thread.d.ts +1 -1
  201. package/esm/providers/__tests__/thread-input-resource-resolution.test.js +3 -3
  202. package/esm/providers/hooks/use-tambo-session-token.test.js +1 -1
  203. package/esm/providers/index.d.ts +12 -12
  204. package/esm/providers/index.js +10 -10
  205. package/esm/providers/tambo-client-provider.js +1 -1
  206. package/esm/providers/tambo-client-provider.test.js +2 -2
  207. package/esm/providers/tambo-component-provider.d.ts +1 -1
  208. package/esm/providers/tambo-component-provider.js +2 -2
  209. package/esm/providers/tambo-context-attachment-provider.js +1 -1
  210. package/esm/providers/tambo-context-attachment-provider.test.js +2 -2
  211. package/esm/providers/tambo-context-helpers-provider.d.ts +1 -1
  212. package/esm/providers/tambo-context-helpers-provider.js +1 -1
  213. package/esm/providers/tambo-context-helpers-provider.test.js +2 -2
  214. package/esm/providers/tambo-interactable-provider-partial-updates.test.js +4 -4
  215. package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  216. package/esm/providers/tambo-interactable-provider.d.ts +5 -5
  217. package/esm/providers/tambo-interactable-provider.js +6 -6
  218. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  219. package/esm/providers/tambo-interactable-provider.test.js +4 -4
  220. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  221. package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js +4 -4
  222. package/esm/providers/tambo-interactables-additional-context.test.js +4 -4
  223. package/esm/providers/tambo-mcp-token-provider.js +2 -2
  224. package/esm/providers/tambo-prop-stream-provider/index.d.ts +8 -8
  225. package/esm/providers/tambo-prop-stream-provider/index.js +9 -9
  226. package/esm/providers/tambo-prop-stream-provider/pending.d.ts +1 -1
  227. package/esm/providers/tambo-prop-stream-provider/pending.js +2 -2
  228. package/esm/providers/tambo-prop-stream-provider/provider.d.ts +1 -1
  229. package/esm/providers/tambo-prop-stream-provider/provider.js +2 -2
  230. package/esm/providers/tambo-prop-stream-provider/streaming.d.ts +1 -1
  231. package/esm/providers/tambo-prop-stream-provider/streaming.js +2 -2
  232. package/esm/providers/tambo-prop-stream-provider/success.d.ts +1 -1
  233. package/esm/providers/tambo-prop-stream-provider/success.js +2 -2
  234. package/esm/providers/tambo-prop-stream-provider/types.d.ts +1 -1
  235. package/esm/providers/tambo-prop-stream-provider.test.js +4 -4
  236. package/esm/providers/tambo-provider.d.ts +10 -7
  237. package/esm/providers/tambo-provider.d.ts.map +1 -1
  238. package/esm/providers/tambo-provider.js +13 -10
  239. package/esm/providers/tambo-provider.js.map +1 -1
  240. package/esm/providers/tambo-registry-provider.d.ts +3 -3
  241. package/esm/providers/tambo-registry-provider.js +3 -3
  242. package/esm/providers/tambo-registry-provider.test.js +2 -2
  243. package/esm/providers/tambo-registry-schema-compat.test.js +2 -2
  244. package/esm/providers/tambo-stubs.d.ts +4 -4
  245. package/esm/providers/tambo-stubs.js +9 -9
  246. package/esm/providers/tambo-stubs.test.js +2 -2
  247. package/esm/providers/tambo-thread-input-provider.d.ts +2 -2
  248. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  249. package/esm/providers/tambo-thread-input-provider.js +11 -10
  250. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider-initial-messages.test.js +6 -6
  252. package/esm/providers/tambo-thread-provider.d.ts +2 -2
  253. package/esm/providers/tambo-thread-provider.js +8 -8
  254. package/esm/providers/tambo-thread-provider.test.js +7 -7
  255. package/esm/schema/index.d.ts +4 -4
  256. package/esm/schema/index.js +4 -4
  257. package/esm/schema/json-schema.test.js +1 -1
  258. package/esm/schema/schema.d.ts +1 -1
  259. package/esm/schema/schema.js +2 -2
  260. package/esm/schema/schema.test.js +3 -3
  261. package/esm/schema/standard-schema.test.js +1 -1
  262. package/esm/schema/validate.js +2 -2
  263. package/esm/schema/validate.test.js +1 -1
  264. package/esm/testing/tools.d.ts +3 -3
  265. package/esm/testing/tools.js +2 -2
  266. package/esm/util/content-parts.test.js +1 -1
  267. package/esm/util/generate-component.d.ts +2 -2
  268. package/esm/util/generate-component.js +4 -4
  269. package/esm/util/generate-component.test.js +2 -2
  270. package/esm/util/is-promise.test.js +1 -1
  271. package/esm/util/mcp-server-utils.d.ts +1 -1
  272. package/esm/util/mcp-server-utils.js +1 -1
  273. package/esm/util/mcp-server-utils.test.js +2 -2
  274. package/esm/util/message-builder.d.ts +1 -1
  275. package/esm/util/message-builder.test.js +1 -1
  276. package/esm/util/query-utils.test.js +1 -1
  277. package/esm/util/registry-validators.d.ts +1 -1
  278. package/esm/util/registry-validators.js +2 -2
  279. package/esm/util/registry-validators.test.js +1 -1
  280. package/esm/util/registry.d.ts +1 -1
  281. package/esm/util/registry.js +1 -1
  282. package/esm/util/registry.test.js +2 -2
  283. package/esm/util/resource-content-resolver.d.ts +2 -2
  284. package/esm/util/resource-content-resolver.d.ts.map +1 -1
  285. package/esm/util/resource-content-resolver.js +3 -1
  286. package/esm/util/resource-content-resolver.js.map +1 -1
  287. package/esm/util/resource-content-resolver.test.js +3 -3
  288. package/esm/util/resource-validators.d.ts +1 -1
  289. package/esm/util/resource-validators.test.js +1 -1
  290. package/esm/util/tool-caller.d.ts +1 -1
  291. package/esm/util/tool-caller.js +1 -1
  292. package/esm/util/validate-component-name.test.js +1 -1
  293. package/esm/v1/__tests__/v1-interactables.test.d.ts +2 -0
  294. package/esm/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
  295. package/esm/v1/__tests__/v1-interactables.test.js +130 -0
  296. package/esm/v1/__tests__/v1-interactables.test.js.map +1 -0
  297. package/esm/v1/components/v1-component-renderer.d.ts +48 -0
  298. package/esm/v1/components/v1-component-renderer.d.ts.map +1 -0
  299. package/esm/v1/components/v1-component-renderer.js +100 -0
  300. package/esm/v1/components/v1-component-renderer.js.map +1 -0
  301. package/esm/v1/components/v1-component-renderer.test.d.ts +2 -0
  302. package/esm/v1/components/v1-component-renderer.test.d.ts.map +1 -0
  303. package/esm/v1/components/v1-component-renderer.test.js +265 -0
  304. package/esm/v1/components/v1-component-renderer.test.js.map +1 -0
  305. package/esm/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
  306. package/esm/v1/hooks/use-tambo-v1-component-state.js +4 -27
  307. package/esm/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
  308. package/esm/v1/hooks/use-tambo-v1-component-state.test.js +6 -5
  309. package/esm/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
  310. package/esm/v1/hooks/use-tambo-v1-messages.d.ts +1 -1
  311. package/esm/v1/hooks/use-tambo-v1-messages.js +1 -1
  312. package/esm/v1/hooks/use-tambo-v1-messages.test.js +27 -3
  313. package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
  314. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +20 -2
  315. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  316. package/esm/v1/hooks/use-tambo-v1-send-message.js +213 -26
  317. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  318. package/esm/v1/hooks/use-tambo-v1-send-message.test.js +266 -12
  319. package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
  320. package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
  321. package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
  322. package/esm/v1/hooks/use-tambo-v1-stream-status.js +176 -0
  323. package/esm/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
  324. package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
  325. package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
  326. package/esm/v1/hooks/use-tambo-v1-stream-status.test.js +369 -0
  327. package/esm/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
  328. package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
  329. package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
  330. package/esm/v1/hooks/use-tambo-v1-suggestions.js +157 -91
  331. package/esm/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
  332. package/esm/v1/hooks/use-tambo-v1-suggestions.test.js +218 -139
  333. package/esm/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
  334. package/esm/v1/hooks/use-tambo-v1-thread-input.d.ts +1 -1
  335. package/esm/v1/hooks/use-tambo-v1-thread-input.js +1 -1
  336. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js +151 -16
  337. package/esm/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
  338. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
  339. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
  340. package/esm/v1/hooks/use-tambo-v1-thread-list.js +12 -11
  341. package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
  342. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +39 -4
  343. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
  344. package/esm/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
  345. package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
  346. package/esm/v1/hooks/use-tambo-v1-thread.js +3 -8
  347. package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -1
  348. package/esm/v1/hooks/use-tambo-v1-thread.test.js +4 -2
  349. package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
  350. package/esm/v1/hooks/use-tambo-v1.d.ts +15 -31
  351. package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -1
  352. package/esm/v1/hooks/use-tambo-v1.js +134 -34
  353. package/esm/v1/hooks/use-tambo-v1.js.map +1 -1
  354. package/esm/v1/hooks/use-tambo-v1.test.js +862 -19
  355. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -1
  356. package/esm/v1/index.d.ts +28 -22
  357. package/esm/v1/index.d.ts.map +1 -1
  358. package/esm/v1/index.js +30 -18
  359. package/esm/v1/index.js.map +1 -1
  360. package/esm/v1/providers/tambo-v1-provider.d.ts +21 -11
  361. package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -1
  362. package/esm/v1/providers/tambo-v1-provider.js +20 -25
  363. package/esm/v1/providers/tambo-v1-provider.js.map +1 -1
  364. package/esm/v1/providers/tambo-v1-provider.test.js +40 -26
  365. package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -1
  366. package/esm/v1/providers/tambo-v1-stream-context.d.ts +4 -4
  367. package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
  368. package/esm/v1/providers/tambo-v1-stream-context.js +62 -14
  369. package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -1
  370. package/esm/v1/providers/tambo-v1-stream-context.test.js +50 -21
  371. package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
  372. package/esm/v1/providers/tambo-v1-stub-provider.d.ts +3 -3
  373. package/esm/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
  374. package/esm/v1/providers/tambo-v1-stub-provider.js +7 -5
  375. package/esm/v1/providers/tambo-v1-stub-provider.js.map +1 -1
  376. package/esm/v1/providers/tambo-v1-stub-provider.test.js +12 -11
  377. package/esm/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
  378. package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts +3 -8
  379. package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
  380. package/esm/v1/providers/tambo-v1-thread-input-provider.js +18 -16
  381. package/esm/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
  382. package/esm/v1/types/event.d.ts +9 -1
  383. package/esm/v1/types/event.d.ts.map +1 -1
  384. package/esm/v1/types/event.js.map +1 -1
  385. package/esm/v1/types/event.test.js +6 -2
  386. package/esm/v1/types/event.test.js.map +1 -1
  387. package/esm/v1/types/message.d.ts +65 -7
  388. package/esm/v1/types/message.d.ts.map +1 -1
  389. package/esm/v1/types/message.js.map +1 -1
  390. package/esm/v1/types/thread.d.ts +5 -1
  391. package/esm/v1/types/thread.d.ts.map +1 -1
  392. package/esm/v1/types/thread.js.map +1 -1
  393. package/esm/v1/utils/component-renderer.test.js +1 -1
  394. package/esm/v1/utils/event-accumulator.d.ts +41 -5
  395. package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
  396. package/esm/v1/utils/event-accumulator.js +444 -36
  397. package/esm/v1/utils/event-accumulator.js.map +1 -1
  398. package/esm/v1/utils/event-accumulator.test.js +1042 -29
  399. package/esm/v1/utils/event-accumulator.test.js.map +1 -1
  400. package/esm/v1/utils/json-patch.test.js +1 -1
  401. package/esm/v1/utils/registry-conversion.d.ts +9 -9
  402. package/esm/v1/utils/registry-conversion.d.ts.map +1 -1
  403. package/esm/v1/utils/registry-conversion.js +11 -12
  404. package/esm/v1/utils/registry-conversion.js.map +1 -1
  405. package/esm/v1/utils/registry-conversion.test.js +40 -12
  406. package/esm/v1/utils/registry-conversion.test.js.map +1 -1
  407. package/esm/v1/utils/stream-handler.test.js +1 -1
  408. package/esm/v1/utils/thread-utils.d.ts +16 -0
  409. package/esm/v1/utils/thread-utils.d.ts.map +1 -0
  410. package/esm/v1/utils/thread-utils.js +31 -0
  411. package/esm/v1/utils/thread-utils.js.map +1 -0
  412. package/esm/v1/utils/tool-call-tracker.d.ts +1 -1
  413. package/esm/v1/utils/tool-executor.d.ts +1 -1
  414. package/esm/v1/utils/tool-executor.d.ts.map +1 -1
  415. package/esm/v1/utils/tool-executor.js +2 -0
  416. package/esm/v1/utils/tool-executor.js.map +1 -1
  417. package/esm/v1/utils/tool-executor.test.js +6 -1
  418. package/esm/v1/utils/tool-executor.test.js.map +1 -1
  419. package/package.json +11 -10
@@ -0,0 +1,48 @@
1
+ import React, { type FC } from "react";
2
+ import type { V1ComponentContent } from "../types/message.js";
3
+ export interface V1ComponentRendererProps {
4
+ /**
5
+ * The component content block from a v1 message
6
+ */
7
+ content: V1ComponentContent;
8
+ /**
9
+ * The thread ID the component belongs to
10
+ */
11
+ threadId: string;
12
+ /**
13
+ * The message ID the component belongs to
14
+ */
15
+ messageId: string;
16
+ /**
17
+ * Optional fallback to render if component is not found in registry
18
+ */
19
+ fallback?: React.ReactNode;
20
+ }
21
+ /**
22
+ * Renders a component from the registry based on component content block data.
23
+ *
24
+ * Use this component in your message renderer to display AI-generated components.
25
+ * The component instance is preserved across re-renders as long as React's
26
+ * reconciliation keeps this wrapper mounted (use content.id as key).
27
+ *
28
+ * Wraps the rendered component with V1ComponentContentProvider so that hooks
29
+ * like useTamboV1ComponentState can access component context.
30
+ * @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found
31
+ * @example
32
+ * ```tsx
33
+ * function MessageContent({ content }: { content: Content }) {
34
+ * if (content.type === 'component') {
35
+ * return (
36
+ * <V1ComponentRenderer
37
+ * key={content.id}
38
+ * content={content}
39
+ * fallback={<div>Unknown component: {content.name}</div>}
40
+ * />
41
+ * );
42
+ * }
43
+ * // ... handle other content types
44
+ * }
45
+ * ```
46
+ */
47
+ export declare const V1ComponentRenderer: FC<V1ComponentRendererProps>;
48
+ //# sourceMappingURL=v1-component-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1-component-renderer.d.ts","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,EAAuB,MAAM,OAAO,CAAC;AAK5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAAC;IAE5B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAoF5D,CAAC"}
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ /**
3
+ * V1 Component Renderer
4
+ *
5
+ * A wrapper component that renders a component from the registry based on
6
+ * component content block data. Uses React's normal reconciliation to maintain
7
+ * component identity - as long as the key stays stable, the component instance
8
+ * is preserved.
9
+ *
10
+ * Wraps the component with V1ComponentContentProvider so that hooks like
11
+ * useTamboV1ComponentState can access component context.
12
+ */
13
+ import { parse } from "partial-json";
14
+ import React, { useMemo, useContext } from "react";
15
+ import { TamboRegistryContext } from "../../providers/tambo-registry-provider.js";
16
+ import { isStandardSchema } from "../../schema/index.js";
17
+ import { isPromise } from "../../util/is-promise.js";
18
+ import { getComponentFromRegistry } from "../../util/registry.js";
19
+ import { V1ComponentContentProvider } from "../utils/component-renderer.js";
20
+ /**
21
+ * Renders a component from the registry based on component content block data.
22
+ *
23
+ * Use this component in your message renderer to display AI-generated components.
24
+ * The component instance is preserved across re-renders as long as React's
25
+ * reconciliation keeps this wrapper mounted (use content.id as key).
26
+ *
27
+ * Wraps the rendered component with V1ComponentContentProvider so that hooks
28
+ * like useTamboV1ComponentState can access component context.
29
+ * @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found
30
+ * @example
31
+ * ```tsx
32
+ * function MessageContent({ content }: { content: Content }) {
33
+ * if (content.type === 'component') {
34
+ * return (
35
+ * <V1ComponentRenderer
36
+ * key={content.id}
37
+ * content={content}
38
+ * fallback={<div>Unknown component: {content.name}</div>}
39
+ * />
40
+ * );
41
+ * }
42
+ * // ... handle other content types
43
+ * }
44
+ * ```
45
+ */
46
+ export const V1ComponentRenderer = ({ content, threadId, messageId, fallback = null, }) => {
47
+ const registry = useContext(TamboRegistryContext);
48
+ // Memoize the rendered element - only recreates when props change
49
+ const element = useMemo(() => {
50
+ try {
51
+ const registeredComponent = getComponentFromRegistry(content.name, registry.componentList);
52
+ // Parse props (handles partial JSON during streaming)
53
+ const propsJson = JSON.stringify(content.props ?? {});
54
+ const parsedProps = parse(propsJson);
55
+ let validatedProps = parsedProps;
56
+ // Validate props if schema is present
57
+ if (isStandardSchema(registeredComponent.props)) {
58
+ const result = registeredComponent.props["~standard"].validate(parsedProps);
59
+ if (isPromise(result)) {
60
+ // Async validation not supported - skip validation
61
+ console.warn(`Async schema validation not supported for component ${content.name}`);
62
+ }
63
+ else if ("value" in result) {
64
+ validatedProps = result.value;
65
+ }
66
+ else {
67
+ // Validation failed - log warning but still render with raw props
68
+ console.warn(`Props validation failed for component ${content.name}:`, result.issues?.[0]?.message);
69
+ }
70
+ }
71
+ return React.createElement(registeredComponent.component, validatedProps);
72
+ }
73
+ catch (error) {
74
+ console.error("[V1ComponentRenderer] Failed to render component", {
75
+ threadId,
76
+ messageId,
77
+ componentId: content.id,
78
+ componentName: content.name,
79
+ streamingState: content.streamingState,
80
+ props: content.props,
81
+ error,
82
+ });
83
+ return null;
84
+ }
85
+ }, [
86
+ content.id,
87
+ content.name,
88
+ content.props,
89
+ content.streamingState,
90
+ messageId,
91
+ threadId,
92
+ registry.componentList,
93
+ ]);
94
+ if (element === null) {
95
+ return React.createElement(React.Fragment, null, fallback);
96
+ }
97
+ // Wrap with provider so hooks like useTamboV1ComponentState work
98
+ return (React.createElement(V1ComponentContentProvider, { componentId: content.id, threadId: threadId, messageId: messageId, componentName: content.name }, element));
99
+ };
100
+ //# sourceMappingURL=v1-component-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1-component-renderer.js","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * V1 Component Renderer\n *\n * A wrapper component that renders a component from the registry based on\n * component content block data. Uses React's normal reconciliation to maintain\n * component identity - as long as the key stays stable, the component instance\n * is preserved.\n *\n * Wraps the component with V1ComponentContentProvider so that hooks like\n * useTamboV1ComponentState can access component context.\n */\n\nimport { parse } from \"partial-json\";\nimport React, { type FC, useMemo, useContext } from \"react\";\nimport { TamboRegistryContext } from \"../../providers/tambo-registry-provider\";\nimport { isStandardSchema } from \"../../schema\";\nimport { isPromise } from \"../../util/is-promise\";\nimport { getComponentFromRegistry } from \"../../util/registry\";\nimport type { V1ComponentContent } from \"../types/message\";\nimport { V1ComponentContentProvider } from \"../utils/component-renderer\";\n\nexport interface V1ComponentRendererProps {\n /**\n * The component content block from a v1 message\n */\n content: V1ComponentContent;\n\n /**\n * The thread ID the component belongs to\n */\n threadId: string;\n\n /**\n * The message ID the component belongs to\n */\n messageId: string;\n\n /**\n * Optional fallback to render if component is not found in registry\n */\n fallback?: React.ReactNode;\n}\n\n/**\n * Renders a component from the registry based on component content block data.\n *\n * Use this component in your message renderer to display AI-generated components.\n * The component instance is preserved across re-renders as long as React's\n * reconciliation keeps this wrapper mounted (use content.id as key).\n *\n * Wraps the rendered component with V1ComponentContentProvider so that hooks\n * like useTamboV1ComponentState can access component context.\n * @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found\n * @example\n * ```tsx\n * function MessageContent({ content }: { content: Content }) {\n * if (content.type === 'component') {\n * return (\n * <V1ComponentRenderer\n * key={content.id}\n * content={content}\n * fallback={<div>Unknown component: {content.name}</div>}\n * />\n * );\n * }\n * // ... handle other content types\n * }\n * ```\n */\nexport const V1ComponentRenderer: FC<V1ComponentRendererProps> = ({\n content,\n threadId,\n messageId,\n fallback = null,\n}) => {\n const registry = useContext(TamboRegistryContext);\n\n // Memoize the rendered element - only recreates when props change\n const element = useMemo(() => {\n try {\n const registeredComponent = getComponentFromRegistry(\n content.name,\n registry.componentList,\n );\n\n // Parse props (handles partial JSON during streaming)\n const propsJson = JSON.stringify(content.props ?? {});\n const parsedProps = parse(propsJson);\n\n let validatedProps: Record<string, unknown> = parsedProps as Record<\n string,\n unknown\n >;\n\n // Validate props if schema is present\n if (isStandardSchema(registeredComponent.props)) {\n const result =\n registeredComponent.props[\"~standard\"].validate(parsedProps);\n\n if (isPromise(result)) {\n // Async validation not supported - skip validation\n console.warn(\n `Async schema validation not supported for component ${content.name}`,\n );\n } else if (\"value\" in result) {\n validatedProps = result.value as Record<string, unknown>;\n } else {\n // Validation failed - log warning but still render with raw props\n console.warn(\n `Props validation failed for component ${content.name}:`,\n result.issues?.[0]?.message,\n );\n }\n }\n\n return React.createElement(registeredComponent.component, validatedProps);\n } catch (error) {\n console.error(\"[V1ComponentRenderer] Failed to render component\", {\n threadId,\n messageId,\n componentId: content.id,\n componentName: content.name,\n streamingState: content.streamingState,\n props: content.props,\n error,\n });\n return null;\n }\n }, [\n content.id,\n content.name,\n content.props,\n content.streamingState,\n messageId,\n threadId,\n registry.componentList,\n ]);\n\n if (element === null) {\n return <>{fallback}</>;\n }\n\n // Wrap with provider so hooks like useTamboV1ComponentState work\n return (\n <V1ComponentContentProvider\n componentId={content.id}\n threadId={threadId}\n messageId={messageId}\n componentName={content.name}\n >\n {element}\n </V1ComponentContentProvider>\n );\n};\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=v1-component-renderer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1-component-renderer.test.d.ts","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,265 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { z } from "zod";
4
+ import { V1ComponentRenderer } from "./v1-component-renderer.js";
5
+ import { TamboRegistryContext } from "../../providers/tambo-registry-provider.js";
6
+ // Simple test component
7
+ const TestComponent = ({ title, count, }) => (React.createElement("div", { "data-testid": "test-component" },
8
+ React.createElement("span", { "data-testid": "title" }, title),
9
+ count !== undefined && React.createElement("span", { "data-testid": "count" }, count)));
10
+ // Component with Zod schema for validation
11
+ const ValidatedComponent = ({ name, age, }) => (React.createElement("div", { "data-testid": "validated-component" },
12
+ React.createElement("span", { "data-testid": "name" }, name),
13
+ React.createElement("span", { "data-testid": "age" }, age)));
14
+ const validatedComponentSchema = z.object({
15
+ name: z.string(),
16
+ age: z.number(),
17
+ });
18
+ // Create a mock registry
19
+ function createMockRegistry(componentList = {}) {
20
+ return {
21
+ componentList,
22
+ toolRegistry: {},
23
+ componentToolAssociations: {},
24
+ mcpServerInfos: [],
25
+ resources: [],
26
+ resourceSource: null,
27
+ registerComponent: jest.fn(),
28
+ registerTool: jest.fn(),
29
+ registerTools: jest.fn(),
30
+ addToolAssociation: jest.fn(),
31
+ registerMcpServer: jest.fn(),
32
+ registerMcpServers: jest.fn(),
33
+ registerResource: jest.fn(),
34
+ registerResources: jest.fn(),
35
+ registerResourceSource: jest.fn(),
36
+ };
37
+ }
38
+ describe("V1ComponentRenderer", () => {
39
+ function withMockedConsoleError(fn) {
40
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
41
+ try {
42
+ return fn(consoleErrorSpy);
43
+ }
44
+ finally {
45
+ consoleErrorSpy.mockRestore();
46
+ }
47
+ }
48
+ const baseContent = {
49
+ type: "component",
50
+ id: "comp_123",
51
+ name: "TestComponent",
52
+ props: { title: "Hello World" },
53
+ streamingState: "done",
54
+ };
55
+ it("renders component from registry with props", () => {
56
+ const registry = createMockRegistry({
57
+ TestComponent: {
58
+ name: "TestComponent",
59
+ description: "A test component",
60
+ component: TestComponent,
61
+ props: { type: "object" },
62
+ contextTools: [],
63
+ },
64
+ });
65
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
66
+ React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
67
+ expect(screen.getByTestId("test-component")).toBeInTheDocument();
68
+ expect(screen.getByTestId("title")).toHaveTextContent("Hello World");
69
+ });
70
+ it("renders fallback when component not found in registry", () => {
71
+ const registry = createMockRegistry({});
72
+ withMockedConsoleError((consoleErrorSpy) => {
73
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
74
+ React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456", fallback: React.createElement("div", { "data-testid": "fallback" }, "Not found") })));
75
+ expect(screen.getByTestId("fallback")).toBeInTheDocument();
76
+ expect(screen.queryByTestId("test-component")).not.toBeInTheDocument();
77
+ expect(consoleErrorSpy).toHaveBeenCalledWith("[V1ComponentRenderer] Failed to render component", expect.objectContaining({
78
+ componentId: baseContent.id,
79
+ componentName: baseContent.name,
80
+ }));
81
+ });
82
+ });
83
+ it("renders nothing (null fallback) when component not found and no fallback provided", () => {
84
+ const registry = createMockRegistry({});
85
+ withMockedConsoleError((consoleErrorSpy) => {
86
+ const { container } = render(React.createElement(TamboRegistryContext.Provider, { value: registry },
87
+ React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
88
+ expect(container.firstChild).toBeNull();
89
+ expect(consoleErrorSpy).toHaveBeenCalledWith("[V1ComponentRenderer] Failed to render component", expect.objectContaining({
90
+ componentId: baseContent.id,
91
+ componentName: baseContent.name,
92
+ }));
93
+ });
94
+ });
95
+ it("handles props with undefined values", () => {
96
+ const registry = createMockRegistry({
97
+ TestComponent: {
98
+ name: "TestComponent",
99
+ description: "A test component",
100
+ component: TestComponent,
101
+ props: { type: "object" },
102
+ contextTools: [],
103
+ },
104
+ });
105
+ const content = {
106
+ type: "component",
107
+ id: "comp_123",
108
+ name: "TestComponent",
109
+ props: { title: "Test", count: undefined },
110
+ streamingState: "done",
111
+ };
112
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
113
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
114
+ expect(screen.getByTestId("title")).toHaveTextContent("Test");
115
+ expect(screen.queryByTestId("count")).not.toBeInTheDocument();
116
+ });
117
+ it("handles null props", () => {
118
+ const registry = createMockRegistry({
119
+ TestComponent: {
120
+ name: "TestComponent",
121
+ description: "A test component",
122
+ component: TestComponent,
123
+ props: { type: "object" },
124
+ contextTools: [],
125
+ },
126
+ });
127
+ const content = {
128
+ type: "component",
129
+ id: "comp_123",
130
+ name: "TestComponent",
131
+ props: null,
132
+ streamingState: "done",
133
+ };
134
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
135
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
136
+ // Component should render with empty props
137
+ expect(screen.getByTestId("test-component")).toBeInTheDocument();
138
+ });
139
+ it("validates props with StandardSchema and uses validated values", () => {
140
+ const registry = createMockRegistry({
141
+ ValidatedComponent: {
142
+ name: "ValidatedComponent",
143
+ description: "A validated component",
144
+ component: ValidatedComponent,
145
+ // Cast as unknown to satisfy TypeScript while still providing a Zod schema
146
+ props: validatedComponentSchema,
147
+ contextTools: [],
148
+ },
149
+ });
150
+ const content = {
151
+ type: "component",
152
+ id: "comp_123",
153
+ name: "ValidatedComponent",
154
+ props: { name: "Alice", age: 30 },
155
+ streamingState: "done",
156
+ };
157
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
158
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
159
+ expect(screen.getByTestId("name")).toHaveTextContent("Alice");
160
+ expect(screen.getByTestId("age")).toHaveTextContent("30");
161
+ });
162
+ it("logs warning and renders with raw props when schema validation fails", () => {
163
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
164
+ const registry = createMockRegistry({
165
+ ValidatedComponent: {
166
+ name: "ValidatedComponent",
167
+ description: "A validated component",
168
+ component: ValidatedComponent,
169
+ // Cast as unknown to satisfy TypeScript while still providing a Zod schema
170
+ props: validatedComponentSchema,
171
+ contextTools: [],
172
+ },
173
+ });
174
+ const content = {
175
+ type: "component",
176
+ id: "comp_123",
177
+ name: "ValidatedComponent",
178
+ props: { name: "Bob", age: "not a number" }, // Invalid: age should be number
179
+ streamingState: "done",
180
+ };
181
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
182
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
183
+ // Should still render with raw props
184
+ expect(screen.getByTestId("name")).toHaveTextContent("Bob");
185
+ expect(screen.getByTestId("age")).toHaveTextContent("not a number");
186
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Props validation failed"), expect.any(String));
187
+ consoleSpy.mockRestore();
188
+ });
189
+ it("logs warning for async schema validation", () => {
190
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
191
+ // Create a mock async schema
192
+ const asyncSchema = {
193
+ "~standard": {
194
+ version: 1,
195
+ vendor: "test",
196
+ validate: async () => {
197
+ return await Promise.resolve({ value: {} });
198
+ },
199
+ },
200
+ };
201
+ const registry = createMockRegistry({
202
+ TestComponent: {
203
+ name: "TestComponent",
204
+ description: "A test component",
205
+ component: TestComponent,
206
+ props: asyncSchema,
207
+ contextTools: [],
208
+ },
209
+ });
210
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
211
+ React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
212
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Async schema validation not supported"));
213
+ consoleSpy.mockRestore();
214
+ });
215
+ it("handles partial JSON during streaming", () => {
216
+ const registry = createMockRegistry({
217
+ TestComponent: {
218
+ name: "TestComponent",
219
+ description: "A test component",
220
+ component: TestComponent,
221
+ props: { type: "object" },
222
+ contextTools: [],
223
+ },
224
+ });
225
+ // partial-json library handles incomplete JSON gracefully
226
+ const content = {
227
+ type: "component",
228
+ id: "comp_123",
229
+ name: "TestComponent",
230
+ props: { title: "Partial" },
231
+ streamingState: "streaming",
232
+ };
233
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
234
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
235
+ expect(screen.getByTestId("title")).toHaveTextContent("Partial");
236
+ });
237
+ it("provides component context to rendered components via V1ComponentContentProvider", () => {
238
+ // Create a component that uses the context
239
+ const ContextAwareComponent = () => {
240
+ // We can't directly test the context without importing useV1ComponentContent
241
+ // but we can verify the component renders which means the provider works
242
+ return React.createElement("div", { "data-testid": "context-aware" }, "Rendered");
243
+ };
244
+ const registry = createMockRegistry({
245
+ ContextAwareComponent: {
246
+ name: "ContextAwareComponent",
247
+ description: "A context aware component",
248
+ component: ContextAwareComponent,
249
+ props: { type: "object" },
250
+ contextTools: [],
251
+ },
252
+ });
253
+ const content = {
254
+ type: "component",
255
+ id: "comp_789",
256
+ name: "ContextAwareComponent",
257
+ props: {},
258
+ streamingState: "done",
259
+ };
260
+ render(React.createElement(TamboRegistryContext.Provider, { value: registry },
261
+ React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_abc", messageId: "msg_def" })));
262
+ expect(screen.getByTestId("context-aware")).toBeInTheDocument();
263
+ });
264
+ });
265
+ //# sourceMappingURL=v1-component-renderer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1-component-renderer.test.js","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from \"react\";\nimport { render, screen } from \"@testing-library/react\";\nimport { z } from \"zod\";\nimport { V1ComponentRenderer } from \"./v1-component-renderer\";\nimport { TamboRegistryContext } from \"../../providers/tambo-registry-provider\";\nimport type { TamboRegistryContext as TamboRegistryContextType } from \"../../providers/tambo-registry-provider\";\nimport type { V1ComponentContent } from \"../types/message\";\n\n// Simple test component\nconst TestComponent: React.FC<{ title: string; count?: number }> = ({\n title,\n count,\n}) => (\n <div data-testid=\"test-component\">\n <span data-testid=\"title\">{title}</span>\n {count !== undefined && <span data-testid=\"count\">{count}</span>}\n </div>\n);\n\n// Component with Zod schema for validation\nconst ValidatedComponent: React.FC<{ name: string; age: number }> = ({\n name,\n age,\n}) => (\n <div data-testid=\"validated-component\">\n <span data-testid=\"name\">{name}</span>\n <span data-testid=\"age\">{age}</span>\n </div>\n);\n\nconst validatedComponentSchema = z.object({\n name: z.string(),\n age: z.number(),\n});\n\n// Create a mock registry\nfunction createMockRegistry(\n componentList: TamboRegistryContextType[\"componentList\"] = {},\n): TamboRegistryContextType {\n return {\n componentList,\n toolRegistry: {},\n componentToolAssociations: {},\n mcpServerInfos: [],\n resources: [],\n resourceSource: null,\n registerComponent: jest.fn(),\n registerTool: jest.fn(),\n registerTools: jest.fn(),\n addToolAssociation: jest.fn(),\n registerMcpServer: jest.fn(),\n registerMcpServers: jest.fn(),\n registerResource: jest.fn(),\n registerResources: jest.fn(),\n registerResourceSource: jest.fn(),\n };\n}\n\ndescribe(\"V1ComponentRenderer\", () => {\n function withMockedConsoleError<T>(\n fn: (consoleErrorSpy: jest.SpyInstance) => T,\n ): T {\n const consoleErrorSpy = jest.spyOn(console, \"error\").mockImplementation();\n try {\n return fn(consoleErrorSpy);\n } finally {\n consoleErrorSpy.mockRestore();\n }\n }\n\n const baseContent: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"TestComponent\",\n props: { title: \"Hello World\" },\n streamingState: \"done\",\n };\n\n it(\"renders component from registry with props\", () => {\n const registry = createMockRegistry({\n TestComponent: {\n name: \"TestComponent\",\n description: \"A test component\",\n component: TestComponent,\n props: { type: \"object\" },\n contextTools: [],\n },\n });\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={baseContent}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"test-component\")).toBeInTheDocument();\n expect(screen.getByTestId(\"title\")).toHaveTextContent(\"Hello World\");\n });\n\n it(\"renders fallback when component not found in registry\", () => {\n const registry = createMockRegistry({});\n\n withMockedConsoleError((consoleErrorSpy) => {\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={baseContent}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n fallback={<div data-testid=\"fallback\">Not found</div>}\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"fallback\")).toBeInTheDocument();\n expect(screen.queryByTestId(\"test-component\")).not.toBeInTheDocument();\n\n expect(consoleErrorSpy).toHaveBeenCalledWith(\n \"[V1ComponentRenderer] Failed to render component\",\n expect.objectContaining({\n componentId: baseContent.id,\n componentName: baseContent.name,\n }),\n );\n });\n });\n\n it(\"renders nothing (null fallback) when component not found and no fallback provided\", () => {\n const registry = createMockRegistry({});\n\n withMockedConsoleError((consoleErrorSpy) => {\n const { container } = render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={baseContent}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(container.firstChild).toBeNull();\n\n expect(consoleErrorSpy).toHaveBeenCalledWith(\n \"[V1ComponentRenderer] Failed to render component\",\n expect.objectContaining({\n componentId: baseContent.id,\n componentName: baseContent.name,\n }),\n );\n });\n });\n\n it(\"handles props with undefined values\", () => {\n const registry = createMockRegistry({\n TestComponent: {\n name: \"TestComponent\",\n description: \"A test component\",\n component: TestComponent,\n props: { type: \"object\" },\n contextTools: [],\n },\n });\n\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"TestComponent\",\n props: { title: \"Test\", count: undefined },\n streamingState: \"done\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"title\")).toHaveTextContent(\"Test\");\n expect(screen.queryByTestId(\"count\")).not.toBeInTheDocument();\n });\n\n it(\"handles null props\", () => {\n const registry = createMockRegistry({\n TestComponent: {\n name: \"TestComponent\",\n description: \"A test component\",\n component: TestComponent,\n props: { type: \"object\" },\n contextTools: [],\n },\n });\n\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"TestComponent\",\n props: null,\n streamingState: \"done\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n // Component should render with empty props\n expect(screen.getByTestId(\"test-component\")).toBeInTheDocument();\n });\n\n it(\"validates props with StandardSchema and uses validated values\", () => {\n const registry = createMockRegistry({\n ValidatedComponent: {\n name: \"ValidatedComponent\",\n description: \"A validated component\",\n component: ValidatedComponent,\n // Cast as unknown to satisfy TypeScript while still providing a Zod schema\n props: validatedComponentSchema as unknown as Record<string, unknown>,\n contextTools: [],\n },\n });\n\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"ValidatedComponent\",\n props: { name: \"Alice\", age: 30 },\n streamingState: \"done\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"name\")).toHaveTextContent(\"Alice\");\n expect(screen.getByTestId(\"age\")).toHaveTextContent(\"30\");\n });\n\n it(\"logs warning and renders with raw props when schema validation fails\", () => {\n const consoleSpy = jest.spyOn(console, \"warn\").mockImplementation();\n\n const registry = createMockRegistry({\n ValidatedComponent: {\n name: \"ValidatedComponent\",\n description: \"A validated component\",\n component: ValidatedComponent,\n // Cast as unknown to satisfy TypeScript while still providing a Zod schema\n props: validatedComponentSchema as unknown as Record<string, unknown>,\n contextTools: [],\n },\n });\n\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"ValidatedComponent\",\n props: { name: \"Bob\", age: \"not a number\" }, // Invalid: age should be number\n streamingState: \"done\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n // Should still render with raw props\n expect(screen.getByTestId(\"name\")).toHaveTextContent(\"Bob\");\n expect(screen.getByTestId(\"age\")).toHaveTextContent(\"not a number\");\n\n expect(consoleSpy).toHaveBeenCalledWith(\n expect.stringContaining(\"Props validation failed\"),\n expect.any(String),\n );\n\n consoleSpy.mockRestore();\n });\n\n it(\"logs warning for async schema validation\", () => {\n const consoleSpy = jest.spyOn(console, \"warn\").mockImplementation();\n\n // Create a mock async schema\n const asyncSchema = {\n \"~standard\": {\n version: 1,\n vendor: \"test\",\n validate: async () => {\n return await Promise.resolve({ value: {} });\n },\n },\n };\n\n const registry = createMockRegistry({\n TestComponent: {\n name: \"TestComponent\",\n description: \"A test component\",\n component: TestComponent,\n props: asyncSchema,\n contextTools: [],\n },\n });\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={baseContent}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(consoleSpy).toHaveBeenCalledWith(\n expect.stringContaining(\"Async schema validation not supported\"),\n );\n\n consoleSpy.mockRestore();\n });\n\n it(\"handles partial JSON during streaming\", () => {\n const registry = createMockRegistry({\n TestComponent: {\n name: \"TestComponent\",\n description: \"A test component\",\n component: TestComponent,\n props: { type: \"object\" },\n contextTools: [],\n },\n });\n\n // partial-json library handles incomplete JSON gracefully\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_123\",\n name: \"TestComponent\",\n props: { title: \"Partial\" },\n streamingState: \"streaming\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_123\"\n messageId=\"msg_456\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"title\")).toHaveTextContent(\"Partial\");\n });\n\n it(\"provides component context to rendered components via V1ComponentContentProvider\", () => {\n // Create a component that uses the context\n const ContextAwareComponent: React.FC = () => {\n // We can't directly test the context without importing useV1ComponentContent\n // but we can verify the component renders which means the provider works\n return <div data-testid=\"context-aware\">Rendered</div>;\n };\n\n const registry = createMockRegistry({\n ContextAwareComponent: {\n name: \"ContextAwareComponent\",\n description: \"A context aware component\",\n component: ContextAwareComponent,\n props: { type: \"object\" },\n contextTools: [],\n },\n });\n\n const content: V1ComponentContent = {\n type: \"component\",\n id: \"comp_789\",\n name: \"ContextAwareComponent\",\n props: {},\n streamingState: \"done\",\n };\n\n render(\n <TamboRegistryContext.Provider value={registry}>\n <V1ComponentRenderer\n content={content}\n threadId=\"thread_abc\"\n messageId=\"msg_def\"\n />\n </TamboRegistryContext.Provider>,\n );\n\n expect(screen.getByTestId(\"context-aware\")).toBeInTheDocument();\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-v1-component-state.d.ts","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-component-state.ts"],"names":[],"mappings":"AAoBA;;;GAGG;AACH,MAAM,MAAM,8BAA8B,CAAC,CAAC,IAAI;IAC9C,YAAY,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI;IAClD,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,KAAK,EAAE,MAAM,IAAI,CAAC;KACnB;CACF,CAAC;AAgCF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,SAAS,EACpD,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,CAAC,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,8BAA8B,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;AACjD,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,CAAC,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,8BAA8B,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"use-tambo-v1-component-state.d.ts","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-component-state.ts"],"names":[],"mappings":"AAoBA;;;GAGG;AACH,MAAM,MAAM,8BAA8B,CAAC,CAAC,IAAI;IAC9C,YAAY,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI;IAClD,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,KAAK,EAAE,MAAM,IAAI,CAAC;KACnB;CACF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,SAAS,EACpD,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,CAAC,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,8BAA8B,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;AACjD,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,CAAC,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,8BAA8B,CAAC,CAAC,CAAC,CAAC"}
@@ -11,33 +11,10 @@
11
11
  import { useCallback, useEffect, useState, useRef } from "react";
12
12
  import { useDebouncedCallback } from "use-debounce";
13
13
  import { deepEqual } from "fast-equals";
14
- import { useTamboClient } from "../../providers/tambo-client-provider";
15
- import { useV1ComponentContent } from "../utils/component-renderer";
16
- import { useStreamState } from "../providers/tambo-v1-stream-context";
17
- /**
18
- * Find a component content block by ID in a specific thread.
19
- * Only searches the specified thread to prevent cross-thread data access
20
- * and improve performance (O(m*k) instead of O(n*m*k)).
21
- * @param streamState - The current stream state
22
- * @param threadId - The thread ID to search in
23
- * @param componentId - The component ID to find
24
- * @returns The component content block, or undefined if not found
25
- */
26
- function findComponentContent(streamState, threadId, componentId) {
27
- // Only search the specified thread (not all threads)
28
- const threadState = streamState.threadMap[threadId];
29
- if (!threadState) {
30
- return undefined;
31
- }
32
- for (const message of threadState.thread.messages) {
33
- for (const content of message.content) {
34
- if (content.type === "component" && content.id === componentId) {
35
- return content;
36
- }
37
- }
38
- }
39
- return undefined;
40
- }
14
+ import { useTamboClient } from "../../providers/tambo-client-provider.js";
15
+ import { useV1ComponentContent } from "../utils/component-renderer.js";
16
+ import { useStreamState } from "../providers/tambo-v1-stream-context.js";
17
+ import { findComponentContent } from "../utils/thread-utils.js";
41
18
  export function useTamboV1ComponentState(keyName, initialValue, debounceTime = 500) {
42
19
  const client = useTamboClient();
43
20
  const { componentId, threadId } = useV1ComponentContent();
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-v1-component-state.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-component-state.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * useTamboV1ComponentState - Component State Hook for v1 API\n *\n * Provides bidirectional state synchronization between React components\n * and the Tambo backend. State changes are debounced before syncing to\n * the server, and server state updates are reflected in the component.\n *\n * Must be used within a component rendered via the component renderer.\n */\n\nimport { useCallback, useEffect, useState, useRef } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { deepEqual } from \"fast-equals\";\nimport { useTamboClient } from \"../../providers/tambo-client-provider\";\nimport { useV1ComponentContent } from \"../utils/component-renderer\";\nimport { useStreamState } from \"../providers/tambo-v1-stream-context\";\nimport type { V1ComponentContent } from \"../types/message\";\n\n/**\n * Return type for useTamboV1ComponentState hook.\n * Similar to useState but with additional metadata.\n */\nexport type UseTamboV1ComponentStateReturn<S> = [\n currentState: S,\n setState: (newState: S | ((prev: S) => S)) => void,\n meta: {\n isPending: boolean;\n error: Error | null;\n flush: () => void;\n },\n];\n\n/**\n * Find a component content block by ID in a specific thread.\n * Only searches the specified thread to prevent cross-thread data access\n * and improve performance (O(m*k) instead of O(n*m*k)).\n * @param streamState - The current stream state\n * @param threadId - The thread ID to search in\n * @param componentId - The component ID to find\n * @returns The component content block, or undefined if not found\n */\nfunction findComponentContent(\n streamState: ReturnType<typeof useStreamState>,\n threadId: string,\n componentId: string,\n): V1ComponentContent | undefined {\n // Only search the specified thread (not all threads)\n const threadState = streamState.threadMap[threadId];\n if (!threadState) {\n return undefined;\n }\n\n for (const message of threadState.thread.messages) {\n for (const content of message.content) {\n if (content.type === \"component\" && content.id === componentId) {\n return content;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Hook for managing component state with bidirectional server sync.\n *\n * This hook acts like useState but automatically syncs state changes\n * to the Tambo backend. Server-side state updates are also reflected\n * in the component.\n *\n * Must be used within a component rendered via the component renderer.\n * @param keyName - The unique key to identify this state value within the component's state\n * @param initialValue - Initial value for the state (used if no server state exists)\n * @param debounceTime - Debounce time in milliseconds (default: 500ms)\n * @returns Tuple of [currentState, setState, meta]\n * @example\n * ```tsx\n * function Counter() {\n * const [count, setCount, { isPending }] = useTamboV1ComponentState('count', 0);\n *\n * return (\n * <div>\n * <span>{count}</span>\n * <button onClick={() => setCount(c => c + 1)} disabled={isPending}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useTamboV1ComponentState<S = undefined>(\n keyName: string,\n initialValue?: S,\n debounceTime?: number,\n): UseTamboV1ComponentStateReturn<S | undefined>;\nexport function useTamboV1ComponentState<S>(\n keyName: string,\n initialValue: S,\n debounceTime?: number,\n): UseTamboV1ComponentStateReturn<S>;\nexport function useTamboV1ComponentState<S>(\n keyName: string,\n initialValue?: S,\n debounceTime = 500,\n): UseTamboV1ComponentStateReturn<S> {\n const client = useTamboClient();\n const { componentId, threadId } = useV1ComponentContent();\n const streamState = useStreamState();\n\n // Find the component content to get server state (only search current thread)\n const componentContent = findComponentContent(\n streamState,\n threadId,\n componentId,\n );\n const serverState = componentContent?.state as\n | Record<string, unknown>\n | undefined;\n const serverValue = serverState?.[keyName] as S | undefined;\n\n // Local state - initialized from server state or initial value\n const [localState, setLocalState] = useState<S>(\n () => serverValue ?? (initialValue as S),\n );\n\n // Track pending state and errors\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Track the last value we sent to avoid overwriting with stale server state\n const lastSentValueRef = useRef<S | undefined>(undefined);\n\n // Track whether there's a pending local change that hasn't synced yet\n const hasPendingLocalChangeRef = useRef(false);\n\n // Track in-flight sync requests to avoid stale completions clearing pending state\n const syncSeqRef = useRef(0);\n\n // Debounced function to sync state to server\n const syncToServer = useDebouncedCallback(async (newState: S) => {\n const seq = ++syncSeqRef.current;\n setIsPending(true);\n setError(null);\n lastSentValueRef.current = newState;\n\n try {\n await client.threads.state.updateState(componentId, {\n threadId,\n state: { [keyName]: newState },\n });\n // Clear pending flag after successful sync\n hasPendingLocalChangeRef.current = false;\n } catch (err) {\n // Clear pending flag on error to allow server reconciliation\n hasPendingLocalChangeRef.current = false;\n const syncError = err instanceof Error ? err : new Error(String(err));\n setError(syncError);\n console.error(\n `[useTamboV1ComponentState] Failed to sync state for ${componentId}:`,\n syncError,\n );\n } finally {\n // Only clear isPending if this is the most recent request\n if (seq === syncSeqRef.current) {\n setIsPending(false);\n }\n }\n }, debounceTime);\n\n // setState function that updates local state and triggers debounced sync\n const setState = useCallback(\n (newState: S | ((prev: S) => S)) => {\n setLocalState((prev) => {\n const nextState =\n typeof newState === \"function\"\n ? (newState as (prev: S) => S)(prev)\n : newState;\n\n // Mark that we have a pending local change\n hasPendingLocalChangeRef.current = true;\n\n // Trigger debounced sync to server\n void syncToServer(nextState);\n\n return nextState;\n });\n },\n [syncToServer],\n );\n\n // Sync from server state when it changes (e.g., from streaming events)\n useEffect(() => {\n if (serverValue === undefined) {\n return;\n }\n\n // Don't overwrite local changes that haven't synced yet\n if (hasPendingLocalChangeRef.current) {\n return;\n }\n\n // Only sync if the server value is different from what we last sent\n // This prevents overwriting local state with stale server values\n if (\n lastSentValueRef.current !== undefined &&\n deepEqual(serverValue, lastSentValueRef.current)\n ) {\n return;\n }\n\n // Use functional update to avoid localState in deps\n setLocalState((prev) =>\n deepEqual(serverValue, prev) ? prev : serverValue,\n );\n }, [serverValue]);\n\n // Flush pending updates on unmount\n useEffect(() => {\n return () => {\n void syncToServer.flush();\n };\n }, [syncToServer]);\n\n // Flush function for immediate sync\n const flush = useCallback(() => {\n void syncToServer.flush();\n }, [syncToServer]);\n\n return [localState, setState, { isPending, error, flush }];\n}\n"]}
1
+ {"version":3,"file":"use-tambo-v1-component-state.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-component-state.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * useTamboV1ComponentState - Component State Hook for v1 API\n *\n * Provides bidirectional state synchronization between React components\n * and the Tambo backend. State changes are debounced before syncing to\n * the server, and server state updates are reflected in the component.\n *\n * Must be used within a component rendered via the component renderer.\n */\n\nimport { useCallback, useEffect, useState, useRef } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { deepEqual } from \"fast-equals\";\nimport { useTamboClient } from \"../../providers/tambo-client-provider\";\nimport { useV1ComponentContent } from \"../utils/component-renderer\";\nimport { useStreamState } from \"../providers/tambo-v1-stream-context\";\nimport { findComponentContent } from \"../utils/thread-utils\";\n\n/**\n * Return type for useTamboV1ComponentState hook.\n * Similar to useState but with additional metadata.\n */\nexport type UseTamboV1ComponentStateReturn<S> = [\n currentState: S,\n setState: (newState: S | ((prev: S) => S)) => void,\n meta: {\n isPending: boolean;\n error: Error | null;\n flush: () => void;\n },\n];\n\n/**\n * Hook for managing component state with bidirectional server sync.\n *\n * This hook acts like useState but automatically syncs state changes\n * to the Tambo backend. Server-side state updates are also reflected\n * in the component.\n *\n * Must be used within a component rendered via the component renderer.\n * @param keyName - The unique key to identify this state value within the component's state\n * @param initialValue - Initial value for the state (used if no server state exists)\n * @param debounceTime - Debounce time in milliseconds (default: 500ms)\n * @returns Tuple of [currentState, setState, meta]\n * @example\n * ```tsx\n * function Counter() {\n * const [count, setCount, { isPending }] = useTamboV1ComponentState('count', 0);\n *\n * return (\n * <div>\n * <span>{count}</span>\n * <button onClick={() => setCount(c => c + 1)} disabled={isPending}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useTamboV1ComponentState<S = undefined>(\n keyName: string,\n initialValue?: S,\n debounceTime?: number,\n): UseTamboV1ComponentStateReturn<S | undefined>;\nexport function useTamboV1ComponentState<S>(\n keyName: string,\n initialValue: S,\n debounceTime?: number,\n): UseTamboV1ComponentStateReturn<S>;\nexport function useTamboV1ComponentState<S>(\n keyName: string,\n initialValue?: S,\n debounceTime = 500,\n): UseTamboV1ComponentStateReturn<S> {\n const client = useTamboClient();\n const { componentId, threadId } = useV1ComponentContent();\n const streamState = useStreamState();\n\n // Find the component content to get server state (only search current thread)\n const componentContent = findComponentContent(\n streamState,\n threadId,\n componentId,\n );\n const serverState = componentContent?.state as\n | Record<string, unknown>\n | undefined;\n const serverValue = serverState?.[keyName] as S | undefined;\n\n // Local state - initialized from server state or initial value\n const [localState, setLocalState] = useState<S>(\n () => serverValue ?? (initialValue as S),\n );\n\n // Track pending state and errors\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Track the last value we sent to avoid overwriting with stale server state\n const lastSentValueRef = useRef<S | undefined>(undefined);\n\n // Track whether there's a pending local change that hasn't synced yet\n const hasPendingLocalChangeRef = useRef(false);\n\n // Track in-flight sync requests to avoid stale completions clearing pending state\n const syncSeqRef = useRef(0);\n\n // Debounced function to sync state to server\n const syncToServer = useDebouncedCallback(async (newState: S) => {\n const seq = ++syncSeqRef.current;\n setIsPending(true);\n setError(null);\n lastSentValueRef.current = newState;\n\n try {\n await client.threads.state.updateState(componentId, {\n threadId,\n state: { [keyName]: newState },\n });\n // Clear pending flag after successful sync\n hasPendingLocalChangeRef.current = false;\n } catch (err) {\n // Clear pending flag on error to allow server reconciliation\n hasPendingLocalChangeRef.current = false;\n const syncError = err instanceof Error ? err : new Error(String(err));\n setError(syncError);\n console.error(\n `[useTamboV1ComponentState] Failed to sync state for ${componentId}:`,\n syncError,\n );\n } finally {\n // Only clear isPending if this is the most recent request\n if (seq === syncSeqRef.current) {\n setIsPending(false);\n }\n }\n }, debounceTime);\n\n // setState function that updates local state and triggers debounced sync\n const setState = useCallback(\n (newState: S | ((prev: S) => S)) => {\n setLocalState((prev) => {\n const nextState =\n typeof newState === \"function\"\n ? (newState as (prev: S) => S)(prev)\n : newState;\n\n // Mark that we have a pending local change\n hasPendingLocalChangeRef.current = true;\n\n // Trigger debounced sync to server\n void syncToServer(nextState);\n\n return nextState;\n });\n },\n [syncToServer],\n );\n\n // Sync from server state when it changes (e.g., from streaming events)\n useEffect(() => {\n if (serverValue === undefined) {\n return;\n }\n\n // Don't overwrite local changes that haven't synced yet\n if (hasPendingLocalChangeRef.current) {\n return;\n }\n\n // Only sync if the server value is different from what we last sent\n // This prevents overwriting local state with stale server values\n if (\n lastSentValueRef.current !== undefined &&\n deepEqual(serverValue, lastSentValueRef.current)\n ) {\n return;\n }\n\n // Use functional update to avoid localState in deps\n setLocalState((prev) =>\n deepEqual(serverValue, prev) ? prev : serverValue,\n );\n }, [serverValue]);\n\n // Flush pending updates on unmount\n useEffect(() => {\n return () => {\n void syncToServer.flush();\n };\n }, [syncToServer]);\n\n // Flush function for immediate sync\n const flush = useCallback(() => {\n void syncToServer.flush();\n }, [syncToServer]);\n\n return [localState, setState, { isPending, error, flush }];\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { act, renderHook } from "@testing-library/react";
2
- import { useTamboV1ComponentState } from "./use-tambo-v1-component-state";
2
+ import { useTamboV1ComponentState } from "./use-tambo-v1-component-state.js";
3
3
  // Mock the required modules
4
4
  jest.mock("../../providers/tambo-client-provider", () => ({
5
5
  useTamboClient: jest.fn(),
@@ -23,9 +23,9 @@ jest.mock("use-debounce", () => ({
23
23
  useDebouncedCallback: jest.fn(),
24
24
  }));
25
25
  // Import the mocked modules
26
- import { useTamboClient } from "../../providers/tambo-client-provider";
27
- import { useStreamState } from "../providers/tambo-v1-stream-context";
28
- import { useV1ComponentContent } from "../utils/component-renderer";
26
+ import { useTamboClient } from "../../providers/tambo-client-provider.js";
27
+ import { useStreamState } from "../providers/tambo-v1-stream-context.js";
28
+ import { useV1ComponentContent } from "../utils/component-renderer.js";
29
29
  import { useDebouncedCallback } from "use-debounce";
30
30
  describe("useTamboV1ComponentState", () => {
31
31
  const mockUpdateState = jest.fn();
@@ -58,6 +58,7 @@ describe("useTamboV1ComponentState", () => {
58
58
  status: "idle",
59
59
  createdAt: new Date().toISOString(),
60
60
  updatedAt: new Date().toISOString(),
61
+ lastRunCancelled: false,
61
62
  },
62
63
  streaming: { status: "idle" },
63
64
  accumulatingToolArgs: new Map(),
@@ -245,7 +246,7 @@ describe("useTamboV1ComponentState", () => {
245
246
  // Empty stream state (no matching component)
246
247
  jest.mocked(useStreamState).mockReturnValue({
247
248
  threadMap: {},
248
- currentThreadId: null,
249
+ currentThreadId: "placeholder",
249
250
  });
250
251
  const { result } = renderHook(() => useTamboV1ComponentState("testKey", "default"));
251
252
  expect(result.current[0]).toBe("default");