@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
package/README.md CHANGED
@@ -246,7 +246,7 @@ import { MCPTransport } from "@tambo-ai/react/mcp";
246
246
  const mcpServers = [
247
247
  {
248
248
  name: "filesystem",
249
- url: "http://localhost:3001/mcp",
249
+ url: "http://localhost:8261/mcp",
250
250
  transport: MCPTransport.HTTP,
251
251
  },
252
252
  ];
@@ -13,11 +13,17 @@ describe("Context Helpers (prebuilt functions)", () => {
13
13
  const context = (0, index_1.currentTimeContextHelper)();
14
14
  // Should not be null (error case)
15
15
  expect(context).not.toBeNull();
16
+ // Type guard: ensure context is an object
17
+ expect(typeof context).toBe("object");
18
+ if (typeof context !== "object" || context === null) {
19
+ throw new Error("Expected context to be a non-null object");
20
+ }
16
21
  // Shape: { timestamp: string }
17
22
  expect(context).toHaveProperty("timestamp");
18
- expect(typeof context.timestamp).toBe("string");
23
+ const contextObj = context;
24
+ expect(typeof contextObj.timestamp).toBe("string");
19
25
  // Verify timestamp string parses
20
- expect(() => new Date(context.timestamp)).not.toThrow();
26
+ expect(() => new Date(contextObj.timestamp)).not.toThrow();
21
27
  });
22
28
  });
23
29
  describe("currentPageContextHelper", () => {
@@ -28,11 +34,17 @@ describe("Context Helpers (prebuilt functions)", () => {
28
34
  expect(context).toBeNull();
29
35
  return;
30
36
  }
37
+ // Type guard: ensure context is an object
38
+ expect(typeof context).toBe("object");
39
+ if (typeof context !== "object") {
40
+ throw new Error("Expected context to be an object");
41
+ }
31
42
  // Shape: { url: string, title: string }
32
43
  expect(context).toHaveProperty("url");
33
44
  expect(context).toHaveProperty("title");
34
- expect(typeof context.url).toBe("string");
35
- expect(typeof context.title).toBe("string");
45
+ const contextObj = context;
46
+ expect(typeof contextObj.url).toBe("string");
47
+ expect(typeof contextObj.title).toBe("string");
36
48
  });
37
49
  });
38
50
  });
@@ -1 +1 @@
1
- {"version":3,"file":"context-helpers.test.js","sourceRoot":"","sources":["../../src/context-helpers/context-helpers.test.ts"],"names":[],"mappings":";;AAAA,mCAA6E;AAE7E;;;;;GAKG;AACH,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAC9D,MAAM,OAAO,GAAG,IAAA,gCAAwB,GAAE,CAAC;YAE3C,kCAAkC;YAClC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAE/B,+BAA+B;YAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,CAAC,OAAO,OAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEjD,iCAAiC;YACjC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,OAAQ,CAAC,SAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAClE,MAAM,OAAO,GAAG,IAAA,gCAAwB,GAAE,CAAC;YAE3C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,sDAAsD;gBACtD,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;YAED,wCAAwC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACtC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAExC,MAAM,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC1C,MAAM,CAAC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { currentPageContextHelper, currentTimeContextHelper } from \"./index\";\n\n/**\n * Tests for prebuilt context helper functions.\n *\n * These helpers now return raw values (or null) instead of { name, context }.\n * The provider is responsible for wrapping values as { name, context }.\n */\ndescribe(\"Context Helpers (prebuilt functions)\", () => {\n describe(\"currentTimeContextHelper\", () => {\n it(\"should return user time context with required fields\", () => {\n const context = currentTimeContextHelper();\n\n // Should not be null (error case)\n expect(context).not.toBeNull();\n\n // Shape: { timestamp: string }\n expect(context).toHaveProperty(\"timestamp\");\n expect(typeof context!.timestamp).toBe(\"string\");\n\n // Verify timestamp string parses\n expect(() => new Date(context!.timestamp as string)).not.toThrow();\n });\n });\n\n describe(\"currentPageContextHelper\", () => {\n it(\"should return page context in browser, or null otherwise\", () => {\n const context = currentPageContextHelper();\n\n if (context === null) {\n // Non-browser environments should return null to skip\n expect(context).toBeNull();\n return;\n }\n\n // Shape: { url: string, title: string }\n expect(context).toHaveProperty(\"url\");\n expect(context).toHaveProperty(\"title\");\n\n expect(typeof context.url).toBe(\"string\");\n expect(typeof context.title).toBe(\"string\");\n });\n });\n});\n"]}
1
+ {"version":3,"file":"context-helpers.test.js","sourceRoot":"","sources":["../../src/context-helpers/context-helpers.test.ts"],"names":[],"mappings":";;AAAA,mCAA6E;AAE7E;;;;;GAKG;AACH,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAC9D,MAAM,OAAO,GAAG,IAAA,gCAAwB,GAAE,CAAC;YAE3C,kCAAkC;YAClC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAE/B,0CAA0C;YAC1C,MAAM,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACpD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAC9D,CAAC;YAED,+BAA+B;YAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,UAAU,GAAG,OAAkC,CAAC;YACtD,MAAM,CAAC,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEnD,iCAAiC;YACjC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,SAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAClE,MAAM,OAAO,GAAG,IAAA,gCAAwB,GAAE,CAAC;YAE3C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,sDAAsD;gBACtD,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;YAED,0CAA0C;YAC1C,MAAM,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACtD,CAAC;YAED,wCAAwC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACtC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAExC,MAAM,UAAU,GAAG,OAAkC,CAAC;YACtD,MAAM,CAAC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { currentPageContextHelper, currentTimeContextHelper } from \"./index\";\n\n/**\n * Tests for prebuilt context helper functions.\n *\n * These helpers now return raw values (or null) instead of { name, context }.\n * The provider is responsible for wrapping values as { name, context }.\n */\ndescribe(\"Context Helpers (prebuilt functions)\", () => {\n describe(\"currentTimeContextHelper\", () => {\n it(\"should return user time context with required fields\", () => {\n const context = currentTimeContextHelper();\n\n // Should not be null (error case)\n expect(context).not.toBeNull();\n\n // Type guard: ensure context is an object\n expect(typeof context).toBe(\"object\");\n if (typeof context !== \"object\" || context === null) {\n throw new Error(\"Expected context to be a non-null object\");\n }\n\n // Shape: { timestamp: string }\n expect(context).toHaveProperty(\"timestamp\");\n const contextObj = context as Record<string, unknown>;\n expect(typeof contextObj.timestamp).toBe(\"string\");\n\n // Verify timestamp string parses\n expect(() => new Date(contextObj.timestamp as string)).not.toThrow();\n });\n });\n\n describe(\"currentPageContextHelper\", () => {\n it(\"should return page context in browser, or null otherwise\", () => {\n const context = currentPageContextHelper();\n\n if (context === null) {\n // Non-browser environments should return null to skip\n expect(context).toBeNull();\n return;\n }\n\n // Type guard: ensure context is an object\n expect(typeof context).toBe(\"object\");\n if (typeof context !== \"object\") {\n throw new Error(\"Expected context to be an object\");\n }\n\n // Shape: { url: string, title: string }\n expect(context).toHaveProperty(\"url\");\n expect(context).toHaveProperty(\"title\");\n\n const contextObj = context as Record<string, unknown>;\n expect(typeof contextObj.url).toBe(\"string\");\n expect(typeof contextObj.title).toBe(\"string\");\n });\n });\n});\n"]}
@@ -22,7 +22,7 @@ export declare const currentInteractablesContextHelper: ContextHelperFn;
22
22
  * Creates an interactables context helper with access to the current components.
23
23
  * This is used internally by TamboInteractableProvider.
24
24
  * @param components Array of interactable components
25
- * @returns Context helper function
25
+ * @returns A context helper function that returns component metadata or null if no components exist
26
26
  */
27
- export declare const createInteractablesContextHelper: (components: any[]) => ContextHelperFn;
27
+ export declare const createInteractablesContextHelper: (components: unknown[]) => ContextHelperFn;
28
28
  //# sourceMappingURL=current-interactables-context-helper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"current-interactables-context-helper.d.ts","sourceRoot":"","sources":["../../src/context-helpers/current-interactables-context-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAI/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC3C,YAAY,GAAG,EAAE,KAChB,eAwBF,CAAC"}
1
+ {"version":3,"file":"current-interactables-context-helper.d.ts","sourceRoot":"","sources":["../../src/context-helpers/current-interactables-context-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAI/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC3C,YAAY,OAAO,EAAE,KACpB,eAyCF,CAAC"}
@@ -29,7 +29,7 @@ exports.currentInteractablesContextHelper = currentInteractablesContextHelper;
29
29
  * Creates an interactables context helper with access to the current components.
30
30
  * This is used internally by TamboInteractableProvider.
31
31
  * @param components Array of interactable components
32
- * @returns Context helper function
32
+ * @returns A context helper function that returns component metadata or null if no components exist
33
33
  */
34
34
  const createInteractablesContextHelper = (components) => {
35
35
  return () => {
@@ -37,20 +37,36 @@ const createInteractablesContextHelper = (components) => {
37
37
  return null; // No interactable components on the page
38
38
  }
39
39
  return {
40
- description: "These are the interactable components currently visible on the page that you can read and modify. Each component has an id, componentName, current props, current state, and optional schemas. You can use tools to update these components' props and state on behalf of the user. Don't tell the user the ID of the components, only the name, unless they ask for it.",
41
- components: components.map((component) => ({
42
- id: component.id,
43
- componentName: component.name,
44
- description: component.description,
45
- props: component.props,
46
- propsSchema: component.propsSchema
47
- ? "Available - use component-specific update tools"
48
- : "Not specified",
49
- state: component.state,
50
- stateSchema: component.stateSchema
51
- ? "Available - use component-specific update tools"
52
- : "Not specified",
53
- })),
40
+ components: components.map((component) => {
41
+ // Type guard: ensure component is an object with the expected properties
42
+ if (typeof component !== "object" || component === null) {
43
+ return {
44
+ id: "unknown",
45
+ componentName: "unknown",
46
+ description: "invalid component",
47
+ props: undefined,
48
+ propsSchema: "Not specified",
49
+ state: undefined,
50
+ isSelectedForInteraction: false,
51
+ stateSchema: "Not specified",
52
+ };
53
+ }
54
+ const comp = component;
55
+ return {
56
+ id: String(comp.id ?? "unknown"),
57
+ componentName: String(comp.name ?? "unknown"),
58
+ description: String(comp.description ?? ""),
59
+ props: comp.props,
60
+ propsSchema: comp.propsSchema
61
+ ? "Available - use component-specific update tools"
62
+ : "Not specified",
63
+ state: comp.state,
64
+ isSelectedForInteraction: comp.isSelectedForInteraction ?? false,
65
+ stateSchema: comp.stateSchema
66
+ ? "Available - use component-specific update tools"
67
+ : "Not specified",
68
+ };
69
+ }),
54
70
  };
55
71
  };
56
72
  };
@@ -1 +1 @@
1
- {"version":3,"file":"current-interactables-context-helper.js","sourceRoot":"","sources":["../../src/context-helpers/current-interactables-context-helper.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,iCAAiC,GAAoB,GAAG,EAAE;IACrE,mFAAmF;IACnF,wEAAwE;IACxE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAJW,QAAA,iCAAiC,qCAI5C;AAEF;;;;;GAKG;AACI,MAAM,gCAAgC,GAAG,CAC9C,UAAiB,EACA,EAAE;IACnB,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC,CAAC,yCAAyC;QACxD,CAAC;QAED,OAAO;YACL,WAAW,EACT,0WAA0W;YAC5W,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACzC,EAAE,EAAE,SAAS,CAAC,EAAE;gBAChB,aAAa,EAAE,SAAS,CAAC,IAAI;gBAC7B,WAAW,EAAE,SAAS,CAAC,WAAW;gBAClC,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,WAAW,EAAE,SAAS,CAAC,WAAW;oBAChC,CAAC,CAAC,iDAAiD;oBACnD,CAAC,CAAC,eAAe;gBACnB,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,WAAW,EAAE,SAAS,CAAC,WAAW;oBAChC,CAAC,CAAC,iDAAiD;oBACnD,CAAC,CAAC,eAAe;aACpB,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AA1BW,QAAA,gCAAgC,oCA0B3C","sourcesContent":["import { ContextHelperFn } from \"./types\";\n\n/**\n * Prebuilt context helper that provides information about all interactable components currently on the page.\n * This gives the AI awareness of what components it can interact with and their current state.\n * @returns an object with description and components, or null to skip including this context.\n * To disable this helper, override it with a function that returns null:\n * @example\n * ```tsx\n * // To disable the default interactables context\n * const { addContextHelper } = useTamboContextHelpers();\n * addContextHelper(\"interactables\", () => null);\n *\n * // To customize the context\n * addContextHelper(\"interactables\", () => ({\n * description: \"Custom description\",\n * components: getCustomComponentsSubset()\n * }));\n * ```\n */\nexport const currentInteractablesContextHelper: ContextHelperFn = () => {\n // This will be provided by the interactable provider when it registers this helper\n // Since we're provider-only now, this function gets replaced at runtime\n return null;\n};\n\n/**\n * Creates an interactables context helper with access to the current components.\n * This is used internally by TamboInteractableProvider.\n * @param components Array of interactable components\n * @returns Context helper function\n */\nexport const createInteractablesContextHelper = (\n components: any[],\n): ContextHelperFn => {\n return () => {\n if (!Array.isArray(components) || components.length === 0) {\n return null; // No interactable components on the page\n }\n\n return {\n description:\n \"These are the interactable components currently visible on the page that you can read and modify. Each component has an id, componentName, current props, current state, and optional schemas. You can use tools to update these components' props and state on behalf of the user. Don't tell the user the ID of the components, only the name, unless they ask for it.\",\n components: components.map((component) => ({\n id: component.id,\n componentName: component.name,\n description: component.description,\n props: component.props,\n propsSchema: component.propsSchema\n ? \"Available - use component-specific update tools\"\n : \"Not specified\",\n state: component.state,\n stateSchema: component.stateSchema\n ? \"Available - use component-specific update tools\"\n : \"Not specified\",\n })),\n };\n };\n};\n"]}
1
+ {"version":3,"file":"current-interactables-context-helper.js","sourceRoot":"","sources":["../../src/context-helpers/current-interactables-context-helper.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,iCAAiC,GAAoB,GAAG,EAAE;IACrE,mFAAmF;IACnF,wEAAwE;IACxE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAJW,QAAA,iCAAiC,qCAI5C;AAEF;;;;;GAKG;AACI,MAAM,gCAAgC,GAAG,CAC9C,UAAqB,EACJ,EAAE;IACnB,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC,CAAC,yCAAyC;QACxD,CAAC;QAED,OAAO;YACL,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;gBACvC,yEAAyE;gBACzE,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACxD,OAAO;wBACL,EAAE,EAAE,SAAS;wBACb,aAAa,EAAE,SAAS;wBACxB,WAAW,EAAE,mBAAmB;wBAChC,KAAK,EAAE,SAAS;wBAChB,WAAW,EAAE,eAAe;wBAC5B,KAAK,EAAE,SAAS;wBAChB,wBAAwB,EAAE,KAAK;wBAC/B,WAAW,EAAE,eAAe;qBAC7B,CAAC;gBACJ,CAAC;gBAED,MAAM,IAAI,GAAG,SAAoC,CAAC;gBAClD,OAAO;oBACL,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,SAAS,CAAC;oBAChC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;oBAC7C,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;oBAC3C,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,WAAW,EAAE,IAAI,CAAC,WAAW;wBAC3B,CAAC,CAAC,iDAAiD;wBACnD,CAAC,CAAC,eAAe;oBACnB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,wBAAwB,EACrB,IAAI,CAAC,wBAAgD,IAAI,KAAK;oBACjE,WAAW,EAAE,IAAI,CAAC,WAAW;wBAC3B,CAAC,CAAC,iDAAiD;wBACnD,CAAC,CAAC,eAAe;iBACpB,CAAC;YACJ,CAAC,CAAC;SACH,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AA3CW,QAAA,gCAAgC,oCA2C3C","sourcesContent":["import { ContextHelperFn } from \"./types\";\n\n/**\n * Prebuilt context helper that provides information about all interactable components currently on the page.\n * This gives the AI awareness of what components it can interact with and their current state.\n * @returns an object with description and components, or null to skip including this context.\n * To disable this helper, override it with a function that returns null:\n * @example\n * ```tsx\n * // To disable the default interactables context\n * const { addContextHelper } = useTamboContextHelpers();\n * addContextHelper(\"interactables\", () => null);\n *\n * // To customize the context\n * addContextHelper(\"interactables\", () => ({\n * description: \"Custom description\",\n * components: getCustomComponentsSubset()\n * }));\n * ```\n */\nexport const currentInteractablesContextHelper: ContextHelperFn = () => {\n // This will be provided by the interactable provider when it registers this helper\n // Since we're provider-only now, this function gets replaced at runtime\n return null;\n};\n\n/**\n * Creates an interactables context helper with access to the current components.\n * This is used internally by TamboInteractableProvider.\n * @param components Array of interactable components\n * @returns A context helper function that returns component metadata or null if no components exist\n */\nexport const createInteractablesContextHelper = (\n components: unknown[],\n): ContextHelperFn => {\n return () => {\n if (!Array.isArray(components) || components.length === 0) {\n return null; // No interactable components on the page\n }\n\n return {\n components: components.map((component) => {\n // Type guard: ensure component is an object with the expected properties\n if (typeof component !== \"object\" || component === null) {\n return {\n id: \"unknown\",\n componentName: \"unknown\",\n description: \"invalid component\",\n props: undefined,\n propsSchema: \"Not specified\",\n state: undefined,\n isSelectedForInteraction: false,\n stateSchema: \"Not specified\",\n };\n }\n\n const comp = component as Record<string, unknown>;\n return {\n id: String(comp.id ?? \"unknown\"),\n componentName: String(comp.name ?? \"unknown\"),\n description: String(comp.description ?? \"\"),\n props: comp.props,\n propsSchema: comp.propsSchema\n ? \"Available - use component-specific update tools\"\n : \"Not specified\",\n state: comp.state,\n isSelectedForInteraction:\n (comp.isSelectedForInteraction as boolean | undefined) ?? false,\n stateSchema: comp.stateSchema\n ? \"Available - use component-specific update tools\"\n : \"Not specified\",\n };\n }),\n };\n };\n};\n"]}
@@ -2,13 +2,13 @@
2
2
  * Global context helpers registry.
3
3
  * Consumers can add/remove helpers and resolve additional context anywhere.
4
4
  */
5
- export type HelperFn = () => any | null | undefined | Promise<any | null | undefined>;
5
+ export type HelperFn = () => unknown | null | undefined | Promise<unknown | null | undefined>;
6
6
  /**
7
7
  * Resolve all helpers to AdditionalContext entries, skipping null/undefined and errors.
8
8
  * @returns The resolved additional context.
9
9
  */
10
10
  export declare function resolveAdditionalContext(helpers: Record<string, HelperFn>): Promise<{
11
11
  name: string;
12
- context: any;
12
+ context: unknown;
13
13
  }[]>;
14
14
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,MACnB,GAAG,GACH,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEpC;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,EAAE,CAAC,CAkB3C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,MACnB,OAAO,GACP,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAExC;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAkB/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAYH,4DAoBC;AAxBD;;;GAGG;AACI,KAAK,UAAU,wBAAwB,CAC5C,OAAiC;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;YACzB,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAqC,CAAC;AACrE,CAAC","sourcesContent":["/**\n * Global context helpers registry.\n * Consumers can add/remove helpers and resolve additional context anywhere.\n */\n\nexport type HelperFn = () =>\n | any\n | null\n | undefined\n | Promise<any | null | undefined>;\n\n/**\n * Resolve all helpers to AdditionalContext entries, skipping null/undefined and errors.\n * @returns The resolved additional context.\n */\nexport async function resolveAdditionalContext(\n helpers: Record<string, HelperFn>,\n): Promise<{ name: string; context: any }[]> {\n const entries = Object.entries(helpers);\n if (entries.length === 0) return [];\n\n const results = await Promise.all(\n entries.map(async ([name, fn]) => {\n try {\n const value = await fn();\n if (value == null) return null;\n return { name, context: value };\n } catch (error) {\n console.error(`Error running context helper ${name}:`, error);\n return null;\n }\n }),\n );\n\n return results.filter(Boolean) as { name: string; context: any }[];\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAYH,4DAoBC;AAxBD;;;GAGG;AACI,KAAK,UAAU,wBAAwB,CAC5C,OAAiC;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;YACzB,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAyC,CAAC;AACzE,CAAC","sourcesContent":["/**\n * Global context helpers registry.\n * Consumers can add/remove helpers and resolve additional context anywhere.\n */\n\nexport type HelperFn = () =>\n | unknown\n | null\n | undefined\n | Promise<unknown | null | undefined>;\n\n/**\n * Resolve all helpers to AdditionalContext entries, skipping null/undefined and errors.\n * @returns The resolved additional context.\n */\nexport async function resolveAdditionalContext(\n helpers: Record<string, HelperFn>,\n): Promise<{ name: string; context: unknown }[]> {\n const entries = Object.entries(helpers);\n if (entries.length === 0) return [];\n\n const results = await Promise.all(\n entries.map(async ([name, fn]) => {\n try {\n const value = await fn();\n if (value == null) return null;\n return { name, context: value };\n } catch (error) {\n console.error(`Error running context helper ${name}:`, error);\n return null;\n }\n }),\n );\n\n return results.filter(Boolean) as { name: string; context: unknown }[];\n}\n"]}
@@ -5,13 +5,13 @@ export interface AdditionalContext {
5
5
  /** The name of the context type */
6
6
  name: string;
7
7
  /** The context data */
8
- context: any;
8
+ context: unknown;
9
9
  }
10
10
  /**
11
11
  * A context helper is a function that returns data to include in the context,
12
12
  * or null/undefined to skip including anything.
13
13
  */
14
- export type ContextHelperFn = () => any | null | undefined | Promise<any | null | undefined>;
14
+ export type ContextHelperFn = () => unknown | null | undefined | Promise<unknown | null | undefined>;
15
15
  /**
16
16
  * A collection of context helpers keyed by their context name.
17
17
  * The key becomes the AdditionalContext.name sent to the model.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAC1B,GAAG,GACH,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEpC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAC1B,OAAO,GACP,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Interface for additional context that can be added to messages\n */\nexport interface AdditionalContext {\n /** The name of the context type */\n name: string;\n /** The context data */\n context: any;\n}\n\n/**\n * A context helper is a function that returns data to include in the context,\n * or null/undefined to skip including anything.\n */\nexport type ContextHelperFn = () =>\n | any\n | null\n | undefined\n | Promise<any | null | undefined>;\n\n/**\n * A collection of context helpers keyed by their context name.\n * The key becomes the AdditionalContext.name sent to the model.\n */\nexport type ContextHelpers = Record<string, ContextHelperFn>;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Interface for additional context that can be added to messages\n */\nexport interface AdditionalContext {\n /** The name of the context type */\n name: string;\n /** The context data */\n context: unknown;\n}\n\n/**\n * A context helper is a function that returns data to include in the context,\n * or null/undefined to skip including anything.\n */\nexport type ContextHelperFn = () =>\n | unknown\n | null\n | undefined\n | Promise<unknown | null | undefined>;\n\n/**\n * A collection of context helpers keyed by their context name.\n * The key becomes the AdditionalContext.name sent to the model.\n */\nexport type ContextHelpers = Record<string, ContextHelperFn>;\n"]}
@@ -6,15 +6,16 @@ const use_message_images_1 = require("./use-message-images");
6
6
  global.crypto = {
7
7
  randomUUID: jest.fn(() => "mock-uuid-" + Math.random()),
8
8
  };
9
- // Mock FileReader
10
- const mockFileReader = {
11
- readAsDataURL: jest.fn(),
12
- onload: null,
13
- onerror: null,
14
- result: "data:image/png;base64,mock-data",
15
- };
16
- global.FileReader = jest.fn(() => {
17
- const reader = { ...mockFileReader };
9
+ // Track FileReader instances for error simulation
10
+ let fileReaderInstances = [];
11
+ // Default FileReader mock that succeeds
12
+ const createSuccessfulFileReader = () => {
13
+ const reader = {
14
+ readAsDataURL: jest.fn(),
15
+ onload: null,
16
+ onerror: null,
17
+ result: "data:image/png;base64,mock-data",
18
+ };
18
19
  reader.readAsDataURL = jest.fn(() => {
19
20
  setTimeout(() => {
20
21
  if (reader.onload) {
@@ -22,45 +23,181 @@ global.FileReader = jest.fn(() => {
22
23
  }
23
24
  }, 0);
24
25
  });
26
+ fileReaderInstances.push(reader);
25
27
  return reader;
26
- });
28
+ };
29
+ // FileReader mock that fails
30
+ const createFailingFileReader = () => {
31
+ const reader = {
32
+ readAsDataURL: jest.fn(),
33
+ onload: null,
34
+ onerror: null,
35
+ result: "",
36
+ };
37
+ reader.readAsDataURL = jest.fn(() => {
38
+ setTimeout(() => {
39
+ if (reader.onerror) {
40
+ reader.onerror(new Error("Failed to read file"));
41
+ }
42
+ }, 0);
43
+ });
44
+ fileReaderInstances.push(reader);
45
+ return reader;
46
+ };
47
+ // Default to successful FileReader
48
+ global.FileReader = jest.fn(() => createSuccessfulFileReader());
27
49
  describe("useMessageImages", () => {
28
50
  beforeEach(() => {
29
51
  jest.clearAllMocks();
52
+ fileReaderInstances = [];
53
+ // Reset to default successful FileReader
54
+ global.FileReader = jest.fn(() => createSuccessfulFileReader());
30
55
  });
31
- it("should initialize with empty images array", () => {
32
- const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
33
- expect(result.current.images).toEqual([]);
56
+ describe("Initialization", () => {
57
+ it("should initialize with empty images array", () => {
58
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
59
+ expect(result.current.images).toEqual([]);
60
+ });
61
+ it("should expose all management functions", () => {
62
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
63
+ expect(typeof result.current.addImage).toBe("function");
64
+ expect(typeof result.current.addImages).toBe("function");
65
+ expect(typeof result.current.removeImage).toBe("function");
66
+ expect(typeof result.current.clearImages).toBe("function");
67
+ });
34
68
  });
35
- it("should reject non-image files", async () => {
36
- const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
37
- const mockFile = new File(["test"], "test-document.pdf", {
38
- type: "application/pdf",
69
+ describe("addImage", () => {
70
+ it("should add a valid image file", async () => {
71
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
72
+ const mockFile = new File(["image data"], "photo.png", {
73
+ type: "image/png",
74
+ });
75
+ await (0, react_1.act)(async () => {
76
+ await result.current.addImage(mockFile);
77
+ });
78
+ expect(result.current.images).toHaveLength(1);
79
+ expect(result.current.images[0].name).toBe("photo.png");
80
+ expect(result.current.images[0].type).toBe("image/png");
81
+ expect(result.current.images[0].dataUrl).toBe("data:image/png;base64,mock-data");
82
+ });
83
+ it("should reject non-image files", async () => {
84
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
85
+ const mockFile = new File(["test"], "test-document.pdf", {
86
+ type: "application/pdf",
87
+ });
88
+ await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
89
+ });
90
+ it("should reject when FileReader fails", async () => {
91
+ // Use failing FileReader
92
+ global.FileReader = jest.fn(() => createFailingFileReader());
93
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
94
+ const mockFile = new File(["image data"], "photo.png", {
95
+ type: "image/png",
96
+ });
97
+ await expect(result.current.addImage(mockFile)).rejects.toThrow();
39
98
  });
40
- await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
41
99
  });
42
- it("should clear all images", () => {
43
- const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
44
- (0, react_1.act)(() => {
45
- result.current.clearImages();
100
+ describe("addImages (batch)", () => {
101
+ it("should add multiple valid images at once", async () => {
102
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
103
+ const mockFiles = [
104
+ new File(["image1"], "photo1.png", { type: "image/png" }),
105
+ new File(["image2"], "photo2.jpg", { type: "image/jpeg" }),
106
+ new File(["image3"], "photo3.gif", { type: "image/gif" }),
107
+ ];
108
+ await (0, react_1.act)(async () => {
109
+ await result.current.addImages(mockFiles);
110
+ });
111
+ expect(result.current.images).toHaveLength(3);
112
+ expect(result.current.images[0].name).toBe("photo1.png");
113
+ expect(result.current.images[1].name).toBe("photo2.jpg");
114
+ expect(result.current.images[2].name).toBe("photo3.gif");
115
+ });
116
+ it("should filter non-images from batch and add valid ones", async () => {
117
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
118
+ const mockFiles = [
119
+ new File(["image"], "photo.png", { type: "image/png" }),
120
+ new File(["pdf"], "document.pdf", { type: "application/pdf" }),
121
+ new File(["image"], "another.jpg", { type: "image/jpeg" }),
122
+ ];
123
+ await (0, react_1.act)(async () => {
124
+ await result.current.addImages(mockFiles);
125
+ });
126
+ // Should only add the 2 valid images
127
+ expect(result.current.images).toHaveLength(2);
128
+ expect(result.current.images[0].name).toBe("photo.png");
129
+ expect(result.current.images[1].name).toBe("another.jpg");
130
+ });
131
+ it("should reject batch with zero valid images", async () => {
132
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
133
+ const mockFiles = [
134
+ new File(["test"], "document.pdf", { type: "application/pdf" }),
135
+ new File(["test"], "text.txt", { type: "text/plain" }),
136
+ ];
137
+ await expect(result.current.addImages(mockFiles)).rejects.toThrow("No valid image files provided");
46
138
  });
47
- expect(result.current.images).toHaveLength(0);
48
139
  });
49
- it("should handle image validation correctly", () => {
50
- const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
51
- // Test that hooks are available
52
- expect(typeof result.current.addImage).toBe("function");
53
- expect(typeof result.current.addImages).toBe("function");
54
- expect(typeof result.current.removeImage).toBe("function");
55
- expect(typeof result.current.clearImages).toBe("function");
140
+ describe("removeImage", () => {
141
+ it("should remove image by id", async () => {
142
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
143
+ const mockFile = new File(["image"], "photo.png", { type: "image/png" });
144
+ await (0, react_1.act)(async () => {
145
+ await result.current.addImage(mockFile);
146
+ });
147
+ const imageId = result.current.images[0].id;
148
+ (0, react_1.act)(() => {
149
+ result.current.removeImage(imageId);
150
+ });
151
+ expect(result.current.images).toHaveLength(0);
152
+ });
153
+ it("should handle removing non-existent image gracefully", async () => {
154
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
155
+ const mockFile = new File(["image"], "photo.png", { type: "image/png" });
156
+ await (0, react_1.act)(async () => {
157
+ await result.current.addImage(mockFile);
158
+ });
159
+ // Try to remove with a fake ID - should not throw or affect existing images
160
+ (0, react_1.act)(() => {
161
+ result.current.removeImage("non-existent-id");
162
+ });
163
+ // Original image should still be there
164
+ expect(result.current.images).toHaveLength(1);
165
+ });
166
+ it("should only remove the targeted image", async () => {
167
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
168
+ const files = [
169
+ new File(["image1"], "photo1.png", { type: "image/png" }),
170
+ new File(["image2"], "photo2.png", { type: "image/png" }),
171
+ new File(["image3"], "photo3.png", { type: "image/png" }),
172
+ ];
173
+ await (0, react_1.act)(async () => {
174
+ await result.current.addImages(files);
175
+ });
176
+ const middleImageId = result.current.images[1].id;
177
+ (0, react_1.act)(() => {
178
+ result.current.removeImage(middleImageId);
179
+ });
180
+ expect(result.current.images).toHaveLength(2);
181
+ expect(result.current.images[0].name).toBe("photo1.png");
182
+ expect(result.current.images[1].name).toBe("photo3.png");
183
+ });
56
184
  });
57
- it("should reject when no valid image files provided to addImages", async () => {
58
- const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
59
- const mockFiles = [
60
- new File(["test"], "document.pdf", { type: "application/pdf" }),
61
- new File(["test"], "text.txt", { type: "text/plain" }),
62
- ];
63
- await expect(result.current.addImages(mockFiles)).rejects.toThrow("No valid image files provided");
185
+ describe("clearImages", () => {
186
+ it("should remove all images", async () => {
187
+ const { result } = (0, react_1.renderHook)(() => (0, use_message_images_1.useMessageImages)());
188
+ const files = [
189
+ new File(["image1"], "photo1.png", { type: "image/png" }),
190
+ new File(["image2"], "photo2.png", { type: "image/png" }),
191
+ ];
192
+ await (0, react_1.act)(async () => {
193
+ await result.current.addImages(files);
194
+ });
195
+ expect(result.current.images).toHaveLength(2);
196
+ (0, react_1.act)(() => {
197
+ result.current.clearImages();
198
+ });
199
+ expect(result.current.images).toHaveLength(0);
200
+ });
64
201
  });
65
202
  });
66
203
  //# sourceMappingURL=use-message-images.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Mock FileReader\nconst mockFileReader = {\n readAsDataURL: jest.fn(),\n onload: null as any,\n onerror: null as any,\n result: \"data:image/png;base64,mock-data\",\n};\n\n(global as any).FileReader = jest.fn(() => {\n const reader = { ...mockFileReader };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n return reader;\n});\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should clear all images\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle image validation correctly\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n // Test that hooks are available\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n\n it(\"should reject when no valid image files provided to addImages\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n});\n"]}
1
+ {"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Track FileReader instances for error simulation\nlet fileReaderInstances: {\n readAsDataURL: jest.Mock;\n onload: ((e: unknown) => void) | null;\n onerror: ((e: unknown) => void) | null;\n result: string;\n}[] = [];\n\n// Default FileReader mock that succeeds\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// FileReader mock that fails\nconst createFailingFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onerror) {\n reader.onerror(new Error(\"Failed to read file\"));\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// Default to successful FileReader\n(global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n fileReaderInstances = [];\n // Reset to default successful FileReader\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n });\n\n describe(\"Initialization\", () => {\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should expose all management functions\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n });\n\n describe(\"addImage\", () => {\n it(\"should add a valid image file\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n expect(result.current.images).toHaveLength(1);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[0].type).toBe(\"image/png\");\n expect(result.current.images[0].dataUrl).toBe(\n \"data:image/png;base64,mock-data\",\n );\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should reject when FileReader fails\", async () => {\n // Use failing FileReader\n (global as any).FileReader = jest.fn(() => createFailingFileReader());\n\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow();\n });\n });\n\n describe(\"addImages (batch)\", () => {\n it(\"should add multiple valid images at once\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.jpg\", { type: \"image/jpeg\" }),\n new File([\"image3\"], \"photo3.gif\", { type: \"image/gif\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n expect(result.current.images).toHaveLength(3);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo2.jpg\");\n expect(result.current.images[2].name).toBe(\"photo3.gif\");\n });\n\n it(\"should filter non-images from batch and add valid ones\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n new File([\"pdf\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"image\"], \"another.jpg\", { type: \"image/jpeg\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n // Should only add the 2 valid images\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[1].name).toBe(\"another.jpg\");\n });\n\n it(\"should reject batch with zero valid images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n });\n\n describe(\"removeImage\", () => {\n it(\"should remove image by id\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n const imageId = result.current.images[0].id;\n\n act(() => {\n result.current.removeImage(imageId);\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle removing non-existent image gracefully\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n // Try to remove with a fake ID - should not throw or affect existing images\n act(() => {\n result.current.removeImage(\"non-existent-id\");\n });\n\n // Original image should still be there\n expect(result.current.images).toHaveLength(1);\n });\n\n it(\"should only remove the targeted image\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n new File([\"image3\"], \"photo3.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n const middleImageId = result.current.images[1].id;\n\n act(() => {\n result.current.removeImage(middleImageId);\n });\n\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo3.png\");\n });\n });\n\n describe(\"clearImages\", () => {\n it(\"should remove all images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n expect(result.current.images).toHaveLength(2);\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n });\n});\n"]}
@@ -13,7 +13,7 @@ export declare function useTamboVoice(): {
13
13
  startRecording: () => void;
14
14
  stopRecording: () => void;
15
15
  isRecording: boolean;
16
- mediaAccessError: string;
16
+ mediaAccessError: string | null;
17
17
  isTranscribing: boolean;
18
18
  transcript: string | null;
19
19
  transcriptionError: string | null;
@@ -65,7 +65,7 @@ function useTamboVoice() {
65
65
  startRecording,
66
66
  stopRecording,
67
67
  isRecording,
68
- mediaAccessError: mediaAccessError ?? null,
68
+ mediaAccessError: mediaAccessError === "" ? null : mediaAccessError,
69
69
  isTranscribing: transcriptionMutation.isPending,
70
70
  transcript,
71
71
  transcriptionError: transcriptionMutation.error?.message ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":";;AAgBA,sCAyEC;AAzFD,iCAAyD;AACzD,+DAA6D;AAC7D,8EAAoE;AACpE,2DAAuD;AAEvD;;;;;;;;;;GAUG;AACH,SAAgB,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,IAAA,gBAAQ,EAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAA,sCAAc,GAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,IAAA,4CAAqB,EAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,IAAA,oCAAgB,EAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,IAAI,IAAI;QAC1C,cAAc,EAAE,qBAAqB,CAAC,SAAS;QAC/C,UAAU;QACV,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { useReactMediaRecorder } from \"react-media-recorder\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboMutation } from \"./react-query-hooks\";\n\n/**\n * Exposes functionality to record speech and transcribe it using the Tambo API.\n * @returns An object with:\n * - startRecording: A function to start recording audio and reset the current transcript.\n * - stopRecording: A function to stop recording audio and automatically kick off transcription.\n * - isRecording: A boolean indicating if the user is recording audio.\n * - isTranscribing: A boolean indicating if the audio is being transcribed.\n * - transcript: The transcript of the recorded audio.\n * - transcriptionError: An error message if the transcription fails.\n * - mediaAccessError: An error message if microphone access fails.\n */\nexport function useTamboVoice() {\n const [transcript, setTranscript] = useState<string | null>(null);\n const client = useTamboClient();\n const {\n status,\n startRecording: startMediaRecording,\n stopRecording: stopMediaRecording,\n mediaBlobUrl,\n error: mediaAccessError,\n } = useReactMediaRecorder({\n audio: true,\n video: false,\n blobPropertyBag: { type: \"audio/webm\" },\n });\n\n const isRecording = status === \"recording\";\n\n const transcriptionMutation = useTamboMutation<\n string,\n Error,\n string // blobUrl parameter\n >({\n mutationFn: async (blobUrl: string) => {\n const response = await fetch(blobUrl);\n const audioBlob = await response.blob();\n const file = new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n });\n\n return await client.beta.audio.transcribe({ file });\n },\n onSuccess: (transcription: string) => {\n setTranscript(transcription);\n },\n });\n\n // Trigger transcription when recording stops and we have a blob URL\n const shouldTranscribe =\n status === \"stopped\" &&\n mediaBlobUrl &&\n !transcriptionMutation.isPending &&\n !transcriptionMutation.isSuccess;\n\n useEffect(() => {\n if (shouldTranscribe) {\n transcriptionMutation.mutate(mediaBlobUrl);\n }\n }, [shouldTranscribe, mediaBlobUrl, transcriptionMutation]);\n\n const startRecording = useCallback(() => {\n if (isRecording) return;\n\n // Reset state when starting new recording\n setTranscript(null);\n transcriptionMutation.reset(); // Clear any previous mutation state\n startMediaRecording();\n }, [isRecording, startMediaRecording, transcriptionMutation]);\n\n const stopRecording = useCallback(() => {\n if (isRecording) {\n stopMediaRecording();\n }\n }, [isRecording, stopMediaRecording]);\n\n return {\n startRecording,\n stopRecording,\n isRecording,\n mediaAccessError: mediaAccessError ?? null,\n isTranscribing: transcriptionMutation.isPending,\n transcript,\n transcriptionError: transcriptionMutation.error?.message ?? null,\n };\n}\n"]}
1
+ {"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":";;AAgBA,sCAyEC;AAzFD,iCAAyD;AACzD,+DAA6D;AAC7D,8EAAoE;AACpE,2DAAuD;AAEvD;;;;;;;;;;GAUG;AACH,SAAgB,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,IAAA,gBAAQ,EAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAA,sCAAc,GAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,IAAA,4CAAqB,EAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,IAAA,oCAAgB,EAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QACnE,cAAc,EAAE,qBAAqB,CAAC,SAAS;QAC/C,UAAU;QACV,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { useReactMediaRecorder } from \"react-media-recorder\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboMutation } from \"./react-query-hooks\";\n\n/**\n * Exposes functionality to record speech and transcribe it using the Tambo API.\n * @returns An object with:\n * - startRecording: A function to start recording audio and reset the current transcript.\n * - stopRecording: A function to stop recording audio and automatically kick off transcription.\n * - isRecording: A boolean indicating if the user is recording audio.\n * - isTranscribing: A boolean indicating if the audio is being transcribed.\n * - transcript: The transcript of the recorded audio.\n * - transcriptionError: An error message if the transcription fails.\n * - mediaAccessError: An error message if microphone access fails.\n */\nexport function useTamboVoice() {\n const [transcript, setTranscript] = useState<string | null>(null);\n const client = useTamboClient();\n const {\n status,\n startRecording: startMediaRecording,\n stopRecording: stopMediaRecording,\n mediaBlobUrl,\n error: mediaAccessError,\n } = useReactMediaRecorder({\n audio: true,\n video: false,\n blobPropertyBag: { type: \"audio/webm\" },\n });\n\n const isRecording = status === \"recording\";\n\n const transcriptionMutation = useTamboMutation<\n string,\n Error,\n string // blobUrl parameter\n >({\n mutationFn: async (blobUrl: string) => {\n const response = await fetch(blobUrl);\n const audioBlob = await response.blob();\n const file = new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n });\n\n return await client.beta.audio.transcribe({ file });\n },\n onSuccess: (transcription: string) => {\n setTranscript(transcription);\n },\n });\n\n // Trigger transcription when recording stops and we have a blob URL\n const shouldTranscribe =\n status === \"stopped\" &&\n mediaBlobUrl &&\n !transcriptionMutation.isPending &&\n !transcriptionMutation.isSuccess;\n\n useEffect(() => {\n if (shouldTranscribe) {\n transcriptionMutation.mutate(mediaBlobUrl);\n }\n }, [shouldTranscribe, mediaBlobUrl, transcriptionMutation]);\n\n const startRecording = useCallback(() => {\n if (isRecording) return;\n\n // Reset state when starting new recording\n setTranscript(null);\n transcriptionMutation.reset(); // Clear any previous mutation state\n startMediaRecording();\n }, [isRecording, startMediaRecording, transcriptionMutation]);\n\n const stopRecording = useCallback(() => {\n if (isRecording) {\n stopMediaRecording();\n }\n }, [isRecording, stopMediaRecording]);\n\n return {\n startRecording,\n stopRecording,\n isRecording,\n mediaAccessError: mediaAccessError === \"\" ? null : mediaAccessError,\n isTranscribing: transcriptionMutation.isPending,\n transcript,\n transcriptionError: transcriptionMutation.error?.message ?? null,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-tambo-voice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tambo-voice.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.test.tsx"],"names":[],"mappings":""}