@tambo-ai/react 0.70.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/dist/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  2. package/dist/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  3. package/dist/v1/hooks/use-tambo-v1-messages.js +54 -0
  4. package/dist/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  5. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  6. package/dist/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  7. package/dist/v1/hooks/use-tambo-v1-messages.test.js +137 -0
  8. package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  9. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  10. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  11. package/dist/v1/hooks/use-tambo-v1-send-message.js +227 -0
  12. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  13. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  14. package/dist/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  15. package/dist/v1/hooks/use-tambo-v1-send-message.test.js +827 -0
  16. package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  17. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  18. package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  19. package/dist/v1/hooks/use-tambo-v1-thread-list.js +56 -0
  20. package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  21. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  22. package/dist/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  23. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +98 -0
  24. package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  25. package/dist/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  26. package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  27. package/dist/v1/hooks/use-tambo-v1-thread.js +49 -0
  28. package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  29. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  30. package/dist/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  31. package/dist/v1/hooks/use-tambo-v1-thread.test.js +83 -0
  32. package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  33. package/dist/v1/hooks/use-tambo-v1.d.ts +107 -0
  34. package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  35. package/dist/v1/hooks/use-tambo-v1.js +87 -0
  36. package/dist/v1/hooks/use-tambo-v1.js.map +1 -0
  37. package/dist/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  38. package/dist/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  39. package/dist/v1/hooks/use-tambo-v1.test.js +150 -0
  40. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -0
  41. package/dist/v1/index.d.ts +54 -16
  42. package/dist/v1/index.d.ts.map +1 -1
  43. package/dist/v1/index.js +85 -26
  44. package/dist/v1/index.js.map +1 -1
  45. package/dist/v1/providers/tambo-v1-provider.d.ts +91 -0
  46. package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  47. package/dist/v1/providers/tambo-v1-provider.js +110 -0
  48. package/dist/v1/providers/tambo-v1-provider.js.map +1 -0
  49. package/dist/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  50. package/dist/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  51. package/dist/v1/providers/tambo-v1-provider.test.js +123 -0
  52. package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -0
  53. package/dist/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  54. package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  55. package/dist/v1/providers/tambo-v1-stream-context.js +230 -0
  56. package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -0
  57. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  58. package/dist/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  59. package/dist/v1/providers/tambo-v1-stream-context.test.js +85 -0
  60. package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  61. package/dist/v1/types/component.d.ts +5 -2
  62. package/dist/v1/types/component.d.ts.map +1 -1
  63. package/dist/v1/types/component.js +5 -2
  64. package/dist/v1/types/component.js.map +1 -1
  65. package/dist/v1/types/event.d.ts +21 -12
  66. package/dist/v1/types/event.d.ts.map +1 -1
  67. package/dist/v1/types/event.js +46 -1
  68. package/dist/v1/types/event.js.map +1 -1
  69. package/dist/v1/types/event.test.d.ts +2 -0
  70. package/dist/v1/types/event.test.d.ts.map +1 -0
  71. package/dist/v1/types/event.test.js +70 -0
  72. package/dist/v1/types/event.test.js.map +1 -0
  73. package/dist/v1/types/message.d.ts +4 -8
  74. package/dist/v1/types/message.d.ts.map +1 -1
  75. package/dist/v1/types/message.js +1 -1
  76. package/dist/v1/types/message.js.map +1 -1
  77. package/dist/v1/types/thread.d.ts +1 -3
  78. package/dist/v1/types/thread.d.ts.map +1 -1
  79. package/dist/v1/types/thread.js +1 -1
  80. package/dist/v1/types/thread.js.map +1 -1
  81. package/dist/v1/utils/event-accumulator.d.ts +100 -0
  82. package/dist/v1/utils/event-accumulator.d.ts.map +1 -0
  83. package/dist/v1/utils/event-accumulator.js +715 -0
  84. package/dist/v1/utils/event-accumulator.js.map +1 -0
  85. package/dist/v1/utils/event-accumulator.test.d.ts +2 -0
  86. package/dist/v1/utils/event-accumulator.test.d.ts.map +1 -0
  87. package/dist/v1/utils/event-accumulator.test.js +1010 -0
  88. package/dist/v1/utils/event-accumulator.test.js.map +1 -0
  89. package/dist/v1/utils/json-patch.d.ts +18 -0
  90. package/dist/v1/utils/json-patch.d.ts.map +1 -0
  91. package/dist/v1/utils/json-patch.js +35 -0
  92. package/dist/v1/utils/json-patch.js.map +1 -0
  93. package/dist/v1/utils/json-patch.test.d.ts +2 -0
  94. package/dist/v1/utils/json-patch.test.d.ts.map +1 -0
  95. package/dist/v1/utils/json-patch.test.js +28 -0
  96. package/dist/v1/utils/json-patch.test.js.map +1 -0
  97. package/dist/v1/utils/registry-conversion.d.ts +53 -0
  98. package/dist/v1/utils/registry-conversion.d.ts.map +1 -0
  99. package/dist/v1/utils/registry-conversion.js +114 -0
  100. package/dist/v1/utils/registry-conversion.js.map +1 -0
  101. package/dist/v1/utils/registry-conversion.test.d.ts +2 -0
  102. package/dist/v1/utils/registry-conversion.test.d.ts.map +1 -0
  103. package/dist/v1/utils/registry-conversion.test.js +179 -0
  104. package/dist/v1/utils/registry-conversion.test.js.map +1 -0
  105. package/dist/v1/utils/stream-handler.d.ts +45 -0
  106. package/dist/v1/utils/stream-handler.d.ts.map +1 -0
  107. package/dist/v1/utils/stream-handler.js +47 -0
  108. package/dist/v1/utils/stream-handler.js.map +1 -0
  109. package/dist/v1/utils/stream-handler.test.d.ts +2 -0
  110. package/dist/v1/utils/stream-handler.test.d.ts.map +1 -0
  111. package/dist/v1/utils/stream-handler.test.js +74 -0
  112. package/dist/v1/utils/stream-handler.test.js.map +1 -0
  113. package/dist/v1/utils/tool-call-tracker.d.ts +41 -0
  114. package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -0
  115. package/dist/v1/utils/tool-call-tracker.js +90 -0
  116. package/dist/v1/utils/tool-call-tracker.js.map +1 -0
  117. package/dist/v1/utils/tool-executor.d.ts +33 -0
  118. package/dist/v1/utils/tool-executor.d.ts.map +1 -0
  119. package/dist/v1/utils/tool-executor.js +103 -0
  120. package/dist/v1/utils/tool-executor.js.map +1 -0
  121. package/dist/v1/utils/tool-executor.test.d.ts +2 -0
  122. package/dist/v1/utils/tool-executor.test.d.ts.map +1 -0
  123. package/dist/v1/utils/tool-executor.test.js +222 -0
  124. package/dist/v1/utils/tool-executor.test.js.map +1 -0
  125. package/esm/v1/hooks/use-tambo-v1-messages.d.ts +58 -0
  126. package/esm/v1/hooks/use-tambo-v1-messages.d.ts.map +1 -0
  127. package/esm/v1/hooks/use-tambo-v1-messages.js +51 -0
  128. package/esm/v1/hooks/use-tambo-v1-messages.js.map +1 -0
  129. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts +2 -0
  130. package/esm/v1/hooks/use-tambo-v1-messages.test.d.ts.map +1 -0
  131. package/esm/v1/hooks/use-tambo-v1-messages.test.js +132 -0
  132. package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -0
  133. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +96 -0
  134. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -0
  135. package/esm/v1/hooks/use-tambo-v1-send-message.js +223 -0
  136. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -0
  137. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts +2 -0
  138. package/esm/v1/hooks/use-tambo-v1-send-message.test.d.ts.map +1 -0
  139. package/esm/v1/hooks/use-tambo-v1-send-message.test.js +822 -0
  140. package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -0
  141. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +61 -0
  142. package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -0
  143. package/esm/v1/hooks/use-tambo-v1-thread-list.js +53 -0
  144. package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -0
  145. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts +2 -0
  146. package/esm/v1/hooks/use-tambo-v1-thread-list.test.d.ts.map +1 -0
  147. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +93 -0
  148. package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -0
  149. package/esm/v1/hooks/use-tambo-v1-thread.d.ts +37 -0
  150. package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -0
  151. package/esm/v1/hooks/use-tambo-v1-thread.js +46 -0
  152. package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -0
  153. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts +2 -0
  154. package/esm/v1/hooks/use-tambo-v1-thread.test.d.ts.map +1 -0
  155. package/esm/v1/hooks/use-tambo-v1-thread.test.js +78 -0
  156. package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -0
  157. package/esm/v1/hooks/use-tambo-v1.d.ts +107 -0
  158. package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -0
  159. package/esm/v1/hooks/use-tambo-v1.js +84 -0
  160. package/esm/v1/hooks/use-tambo-v1.js.map +1 -0
  161. package/esm/v1/hooks/use-tambo-v1.test.d.ts +2 -0
  162. package/esm/v1/hooks/use-tambo-v1.test.d.ts.map +1 -0
  163. package/esm/v1/hooks/use-tambo-v1.test.js +145 -0
  164. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -0
  165. package/esm/v1/index.d.ts +54 -16
  166. package/esm/v1/index.d.ts.map +1 -1
  167. package/esm/v1/index.js +64 -27
  168. package/esm/v1/index.js.map +1 -1
  169. package/esm/v1/providers/tambo-v1-provider.d.ts +91 -0
  170. package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -0
  171. package/esm/v1/providers/tambo-v1-provider.js +74 -0
  172. package/esm/v1/providers/tambo-v1-provider.js.map +1 -0
  173. package/esm/v1/providers/tambo-v1-provider.test.d.ts +2 -0
  174. package/esm/v1/providers/tambo-v1-provider.test.d.ts.map +1 -0
  175. package/esm/v1/providers/tambo-v1-provider.test.js +118 -0
  176. package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -0
  177. package/esm/v1/providers/tambo-v1-stream-context.d.ts +136 -0
  178. package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -0
  179. package/esm/v1/providers/tambo-v1-stream-context.js +191 -0
  180. package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -0
  181. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts +2 -0
  182. package/esm/v1/providers/tambo-v1-stream-context.test.d.ts.map +1 -0
  183. package/esm/v1/providers/tambo-v1-stream-context.test.js +80 -0
  184. package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -0
  185. package/esm/v1/types/component.d.ts +5 -2
  186. package/esm/v1/types/component.d.ts.map +1 -1
  187. package/esm/v1/types/component.js +5 -2
  188. package/esm/v1/types/component.js.map +1 -1
  189. package/esm/v1/types/event.d.ts +21 -12
  190. package/esm/v1/types/event.d.ts.map +1 -1
  191. package/esm/v1/types/event.js +44 -2
  192. package/esm/v1/types/event.js.map +1 -1
  193. package/esm/v1/types/event.test.d.ts +2 -0
  194. package/esm/v1/types/event.test.d.ts.map +1 -0
  195. package/esm/v1/types/event.test.js +68 -0
  196. package/esm/v1/types/event.test.js.map +1 -0
  197. package/esm/v1/types/message.d.ts +4 -8
  198. package/esm/v1/types/message.d.ts.map +1 -1
  199. package/esm/v1/types/message.js +1 -1
  200. package/esm/v1/types/message.js.map +1 -1
  201. package/esm/v1/types/thread.d.ts +1 -3
  202. package/esm/v1/types/thread.d.ts.map +1 -1
  203. package/esm/v1/types/thread.js +1 -1
  204. package/esm/v1/types/thread.js.map +1 -1
  205. package/esm/v1/utils/event-accumulator.d.ts +100 -0
  206. package/esm/v1/utils/event-accumulator.d.ts.map +1 -0
  207. package/esm/v1/utils/event-accumulator.js +708 -0
  208. package/esm/v1/utils/event-accumulator.js.map +1 -0
  209. package/esm/v1/utils/event-accumulator.test.d.ts +2 -0
  210. package/esm/v1/utils/event-accumulator.test.d.ts.map +1 -0
  211. package/esm/v1/utils/event-accumulator.test.js +1008 -0
  212. package/esm/v1/utils/event-accumulator.test.js.map +1 -0
  213. package/esm/v1/utils/json-patch.d.ts +18 -0
  214. package/esm/v1/utils/json-patch.d.ts.map +1 -0
  215. package/esm/v1/utils/json-patch.js +32 -0
  216. package/esm/v1/utils/json-patch.js.map +1 -0
  217. package/esm/v1/utils/json-patch.test.d.ts +2 -0
  218. package/esm/v1/utils/json-patch.test.d.ts.map +1 -0
  219. package/esm/v1/utils/json-patch.test.js +26 -0
  220. package/esm/v1/utils/json-patch.test.js.map +1 -0
  221. package/esm/v1/utils/registry-conversion.d.ts +53 -0
  222. package/esm/v1/utils/registry-conversion.d.ts.map +1 -0
  223. package/esm/v1/utils/registry-conversion.js +108 -0
  224. package/esm/v1/utils/registry-conversion.js.map +1 -0
  225. package/esm/v1/utils/registry-conversion.test.d.ts +2 -0
  226. package/esm/v1/utils/registry-conversion.test.d.ts.map +1 -0
  227. package/esm/v1/utils/registry-conversion.test.js +177 -0
  228. package/esm/v1/utils/registry-conversion.test.js.map +1 -0
  229. package/esm/v1/utils/stream-handler.d.ts +45 -0
  230. package/esm/v1/utils/stream-handler.d.ts.map +1 -0
  231. package/esm/v1/utils/stream-handler.js +44 -0
  232. package/esm/v1/utils/stream-handler.js.map +1 -0
  233. package/esm/v1/utils/stream-handler.test.d.ts +2 -0
  234. package/esm/v1/utils/stream-handler.test.d.ts.map +1 -0
  235. package/esm/v1/utils/stream-handler.test.js +72 -0
  236. package/esm/v1/utils/stream-handler.test.js.map +1 -0
  237. package/esm/v1/utils/tool-call-tracker.d.ts +41 -0
  238. package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -0
  239. package/esm/v1/utils/tool-call-tracker.js +86 -0
  240. package/esm/v1/utils/tool-call-tracker.js.map +1 -0
  241. package/esm/v1/utils/tool-executor.d.ts +33 -0
  242. package/esm/v1/utils/tool-executor.d.ts.map +1 -0
  243. package/esm/v1/utils/tool-executor.js +99 -0
  244. package/esm/v1/utils/tool-executor.js.map +1 -0
  245. package/esm/v1/utils/tool-executor.test.d.ts +2 -0
  246. package/esm/v1/utils/tool-executor.test.d.ts.map +1 -0
  247. package/esm/v1/utils/tool-executor.test.js +220 -0
  248. package/esm/v1/utils/tool-executor.test.js.map +1 -0
  249. package/package.json +7 -6
  250. package/dist/v1/types/tool.d.ts +0 -52
  251. package/dist/v1/types/tool.d.ts.map +0 -1
  252. package/dist/v1/types/tool.js +0 -11
  253. package/dist/v1/types/tool.js.map +0 -1
  254. package/esm/v1/types/tool.d.ts +0 -52
  255. package/esm/v1/types/tool.d.ts.map +0 -1
  256. package/esm/v1/types/tool.js +0 -10
  257. package/esm/v1/types/tool.js.map +0 -1
@@ -0,0 +1,822 @@
1
+ import { EventType } from "@ag-ui/core";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+ import { renderHook, waitFor, act } from "@testing-library/react";
4
+ import React from "react";
5
+ import { z } from "zod";
6
+ import { useTamboClient } from "../../providers/tambo-client-provider";
7
+ import { TamboRegistryContext } from "../../providers/tambo-registry-provider";
8
+ import { TamboV1StreamProvider } from "../providers/tambo-v1-stream-context";
9
+ import { createRunStream, useTamboV1SendMessage, } from "./use-tambo-v1-send-message";
10
+ jest.mock("../../providers/tambo-client-provider", () => ({
11
+ useTamboClient: jest.fn(),
12
+ }));
13
+ describe("useTamboV1SendMessage", () => {
14
+ const mockThreadsRunsApi = {
15
+ run: jest.fn(),
16
+ create: jest.fn(),
17
+ };
18
+ const mockTamboAI = {
19
+ apiKey: "",
20
+ threads: {
21
+ runs: mockThreadsRunsApi,
22
+ },
23
+ };
24
+ const mockRegistry = {
25
+ componentList: new Map(),
26
+ toolRegistry: new Map(),
27
+ };
28
+ let queryClient;
29
+ function TestWrapper({ children }) {
30
+ return (React.createElement(QueryClientProvider, { client: queryClient },
31
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
32
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
33
+ }
34
+ beforeEach(() => {
35
+ queryClient = new QueryClient({
36
+ defaultOptions: {
37
+ queries: { retry: false },
38
+ mutations: { retry: false },
39
+ },
40
+ });
41
+ jest.mocked(useTamboClient).mockReturnValue(mockTamboAI);
42
+ mockThreadsRunsApi.run.mockReset();
43
+ mockThreadsRunsApi.create.mockReset();
44
+ });
45
+ it("returns a mutation object", () => {
46
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
47
+ wrapper: TestWrapper,
48
+ });
49
+ expect(result.current.mutate).toBeDefined();
50
+ expect(result.current.mutateAsync).toBeDefined();
51
+ expect(result.current.isPending).toBe(false);
52
+ });
53
+ it("returns a mutation object when threadId is not provided", () => {
54
+ const { result } = renderHook(() => useTamboV1SendMessage(), {
55
+ wrapper: TestWrapper,
56
+ });
57
+ expect(result.current.mutate).toBeDefined();
58
+ expect(result.current.mutateAsync).toBeDefined();
59
+ });
60
+ });
61
+ describe("createRunStream", () => {
62
+ const mockStream = {
63
+ [Symbol.asyncIterator]: async function* () {
64
+ yield { type: "RUN_STARTED", runId: "run_1", threadId: "thread_123" };
65
+ },
66
+ };
67
+ const mockThreadsRunsApi = {
68
+ run: jest.fn(),
69
+ create: jest.fn(),
70
+ };
71
+ const mockClient = {
72
+ threads: {
73
+ runs: mockThreadsRunsApi,
74
+ },
75
+ };
76
+ const mockRegistry = {
77
+ componentList: new Map([
78
+ [
79
+ "TestComponent",
80
+ {
81
+ name: "TestComponent",
82
+ description: "A test component",
83
+ component: () => null,
84
+ propsSchema: z.object({ title: z.string() }),
85
+ },
86
+ ],
87
+ ]),
88
+ toolRegistry: new Map([
89
+ [
90
+ "testTool",
91
+ {
92
+ name: "testTool",
93
+ description: "A test tool",
94
+ tool: async () => "result",
95
+ inputSchema: z.object({ query: z.string() }),
96
+ },
97
+ ],
98
+ ]),
99
+ };
100
+ const testMessage = {
101
+ role: "user",
102
+ content: [{ type: "text", text: "Hello" }],
103
+ };
104
+ beforeEach(() => {
105
+ mockThreadsRunsApi.run.mockReset();
106
+ mockThreadsRunsApi.create.mockReset();
107
+ });
108
+ it("calls client.threads.runs.run when threadId is provided", async () => {
109
+ mockThreadsRunsApi.run.mockResolvedValue(mockStream);
110
+ const result = await createRunStream({
111
+ client: mockClient,
112
+ threadId: "thread_123",
113
+ message: testMessage,
114
+ registry: mockRegistry,
115
+ });
116
+ expect(mockThreadsRunsApi.run).toHaveBeenCalledWith("thread_123", {
117
+ message: testMessage,
118
+ availableComponents: expect.any(Array),
119
+ tools: expect.any(Array),
120
+ });
121
+ expect(mockThreadsRunsApi.create).not.toHaveBeenCalled();
122
+ expect(result.stream).toBe(mockStream);
123
+ expect(result.initialThreadId).toBe("thread_123");
124
+ });
125
+ it("calls client.threads.runs.create when threadId is not provided", async () => {
126
+ mockThreadsRunsApi.create.mockResolvedValue(mockStream);
127
+ const result = await createRunStream({
128
+ client: mockClient,
129
+ threadId: undefined,
130
+ message: testMessage,
131
+ registry: mockRegistry,
132
+ });
133
+ expect(mockThreadsRunsApi.create).toHaveBeenCalledWith({
134
+ message: testMessage,
135
+ availableComponents: expect.any(Array),
136
+ tools: expect.any(Array),
137
+ });
138
+ expect(mockThreadsRunsApi.run).not.toHaveBeenCalled();
139
+ expect(result.stream).toBe(mockStream);
140
+ expect(result.initialThreadId).toBeUndefined();
141
+ });
142
+ it("converts registry components to availableComponents format", async () => {
143
+ mockThreadsRunsApi.run.mockResolvedValue(mockStream);
144
+ await createRunStream({
145
+ client: mockClient,
146
+ threadId: "thread_123",
147
+ message: testMessage,
148
+ registry: mockRegistry,
149
+ });
150
+ const callArgs = mockThreadsRunsApi.run.mock.calls[0][1];
151
+ expect(callArgs.availableComponents).toEqual([
152
+ {
153
+ name: "TestComponent",
154
+ description: "A test component",
155
+ propsSchema: expect.any(Object),
156
+ },
157
+ ]);
158
+ });
159
+ it("converts registry tools to tools format", async () => {
160
+ mockThreadsRunsApi.run.mockResolvedValue(mockStream);
161
+ await createRunStream({
162
+ client: mockClient,
163
+ threadId: "thread_123",
164
+ message: testMessage,
165
+ registry: mockRegistry,
166
+ });
167
+ const callArgs = mockThreadsRunsApi.run.mock.calls[0][1];
168
+ expect(callArgs.tools).toEqual([
169
+ expect.objectContaining({
170
+ name: "testTool",
171
+ description: "A test tool",
172
+ }),
173
+ ]);
174
+ });
175
+ it("handles empty registry", async () => {
176
+ mockThreadsRunsApi.run.mockResolvedValue(mockStream);
177
+ const emptyRegistry = {
178
+ componentList: new Map(),
179
+ toolRegistry: new Map(),
180
+ };
181
+ await createRunStream({
182
+ client: mockClient,
183
+ threadId: "thread_123",
184
+ message: testMessage,
185
+ registry: emptyRegistry,
186
+ });
187
+ const callArgs = mockThreadsRunsApi.run.mock.calls[0][1];
188
+ expect(callArgs.availableComponents).toEqual([]);
189
+ expect(callArgs.tools).toEqual([]);
190
+ });
191
+ });
192
+ describe("useTamboV1SendMessage mutation", () => {
193
+ const mockThreadsRunsApi = {
194
+ run: jest.fn(),
195
+ create: jest.fn(),
196
+ };
197
+ const mockTamboAI = {
198
+ apiKey: "",
199
+ threads: {
200
+ runs: mockThreadsRunsApi,
201
+ },
202
+ };
203
+ let queryClient;
204
+ function createAsyncIterator(events) {
205
+ return {
206
+ [Symbol.asyncIterator]: async function* () {
207
+ for (const event of events) {
208
+ yield event;
209
+ }
210
+ },
211
+ };
212
+ }
213
+ beforeEach(() => {
214
+ queryClient = new QueryClient({
215
+ defaultOptions: {
216
+ queries: { retry: false },
217
+ mutations: { retry: false },
218
+ },
219
+ });
220
+ jest.mocked(useTamboClient).mockReturnValue(mockTamboAI);
221
+ mockThreadsRunsApi.run.mockReset();
222
+ mockThreadsRunsApi.create.mockReset();
223
+ });
224
+ it("extracts threadId from RUN_STARTED event when creating new thread", async () => {
225
+ const mockStream = createAsyncIterator([
226
+ {
227
+ type: EventType.RUN_STARTED,
228
+ runId: "run_1",
229
+ threadId: "new_thread_123",
230
+ },
231
+ { type: EventType.RUN_FINISHED },
232
+ ]);
233
+ mockThreadsRunsApi.create.mockResolvedValue(mockStream);
234
+ const mockRegistry = {
235
+ componentList: new Map(),
236
+ toolRegistry: new Map(),
237
+ };
238
+ function TestWrapper({ children }) {
239
+ return (React.createElement(QueryClientProvider, { client: queryClient },
240
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
241
+ React.createElement(TamboV1StreamProvider, null, children))));
242
+ }
243
+ const { result } = renderHook(() => useTamboV1SendMessage(), {
244
+ wrapper: TestWrapper,
245
+ });
246
+ let mutationResult;
247
+ await act(async () => {
248
+ mutationResult = await result.current.mutateAsync({
249
+ message: {
250
+ role: "user",
251
+ content: [{ type: "text", text: "Hello" }],
252
+ },
253
+ });
254
+ });
255
+ expect(mutationResult?.threadId).toBe("new_thread_123");
256
+ });
257
+ it("throws error when first event is not RUN_STARTED on new thread", async () => {
258
+ const mockStream = createAsyncIterator([
259
+ { type: EventType.TEXT_MESSAGE_START, messageId: "msg_1" },
260
+ ]);
261
+ mockThreadsRunsApi.create.mockResolvedValue(mockStream);
262
+ const mockRegistry = {
263
+ componentList: new Map(),
264
+ toolRegistry: new Map(),
265
+ };
266
+ function TestWrapper({ children }) {
267
+ return (React.createElement(QueryClientProvider, { client: queryClient },
268
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
269
+ React.createElement(TamboV1StreamProvider, null, children))));
270
+ }
271
+ const { result } = renderHook(() => useTamboV1SendMessage(), {
272
+ wrapper: TestWrapper,
273
+ });
274
+ await expect(act(async () => {
275
+ await result.current.mutateAsync({
276
+ message: {
277
+ role: "user",
278
+ content: [{ type: "text", text: "Hello" }],
279
+ },
280
+ });
281
+ })).rejects.toThrow("Expected first event to be RUN_STARTED with threadId");
282
+ });
283
+ it("executes tools on awaiting_input event", async () => {
284
+ const toolExecuted = jest.fn().mockResolvedValue("tool result");
285
+ const testTool = {
286
+ name: "test_tool",
287
+ description: "A test tool",
288
+ tool: toolExecuted,
289
+ inputSchema: z.object({ query: z.string() }),
290
+ outputSchema: z.string(),
291
+ };
292
+ const mockRegistry = {
293
+ componentList: new Map(),
294
+ toolRegistry: new Map([["test_tool", testTool]]),
295
+ };
296
+ const initialStream = createAsyncIterator([
297
+ {
298
+ type: EventType.RUN_STARTED,
299
+ runId: "run_1",
300
+ threadId: "thread_123",
301
+ },
302
+ {
303
+ type: EventType.TEXT_MESSAGE_START,
304
+ messageId: "msg_1",
305
+ role: "assistant",
306
+ },
307
+ {
308
+ type: EventType.TOOL_CALL_START,
309
+ toolCallId: "call_1",
310
+ toolCallName: "test_tool",
311
+ parentMessageId: "msg_1",
312
+ },
313
+ {
314
+ type: EventType.TOOL_CALL_ARGS,
315
+ toolCallId: "call_1",
316
+ delta: '{"query":"test"}',
317
+ },
318
+ {
319
+ type: EventType.TOOL_CALL_END,
320
+ toolCallId: "call_1",
321
+ },
322
+ {
323
+ type: EventType.CUSTOM,
324
+ name: "tambo.run.awaiting_input",
325
+ value: { pendingToolCallIds: ["call_1"] },
326
+ },
327
+ ]);
328
+ const continueStream = createAsyncIterator([
329
+ {
330
+ type: EventType.RUN_STARTED,
331
+ runId: "run_2",
332
+ threadId: "thread_123",
333
+ },
334
+ { type: EventType.RUN_FINISHED },
335
+ ]);
336
+ mockThreadsRunsApi.run
337
+ .mockResolvedValueOnce(initialStream)
338
+ .mockResolvedValueOnce(continueStream);
339
+ function TestWrapper({ children }) {
340
+ return (React.createElement(QueryClientProvider, { client: queryClient },
341
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
342
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
343
+ }
344
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
345
+ wrapper: TestWrapper,
346
+ });
347
+ await act(async () => {
348
+ await result.current.mutateAsync({
349
+ message: {
350
+ role: "user",
351
+ content: [{ type: "text", text: "Hello" }],
352
+ },
353
+ });
354
+ });
355
+ expect(toolExecuted).toHaveBeenCalledWith({ query: "test" });
356
+ // Verify continuation was called with tool results
357
+ expect(mockThreadsRunsApi.run).toHaveBeenCalledTimes(2);
358
+ const continueCall = mockThreadsRunsApi.run.mock.calls[1];
359
+ expect(continueCall[0]).toBe("thread_123");
360
+ expect(continueCall[1].previousRunId).toBe("run_1");
361
+ expect(continueCall[1].message.content[0]).toEqual({
362
+ type: "tool_result",
363
+ toolUseId: "call_1",
364
+ content: [{ type: "text", text: "tool result" }],
365
+ });
366
+ });
367
+ it("handles tool calls with chunked args", async () => {
368
+ const toolExecuted = jest.fn().mockResolvedValue({ result: 42 });
369
+ const testTool = {
370
+ name: "chunked_tool",
371
+ description: "Tool with chunked args",
372
+ tool: toolExecuted,
373
+ inputSchema: z.object({ a: z.number(), b: z.number() }),
374
+ outputSchema: z.object({ result: z.number() }),
375
+ };
376
+ const mockRegistry = {
377
+ componentList: new Map(),
378
+ toolRegistry: new Map([["chunked_tool", testTool]]),
379
+ };
380
+ const initialStream = createAsyncIterator([
381
+ {
382
+ type: EventType.RUN_STARTED,
383
+ runId: "run_1",
384
+ threadId: "thread_123",
385
+ },
386
+ {
387
+ type: EventType.TEXT_MESSAGE_START,
388
+ messageId: "msg_1",
389
+ role: "assistant",
390
+ },
391
+ {
392
+ type: EventType.TOOL_CALL_START,
393
+ toolCallId: "call_1",
394
+ toolCallName: "chunked_tool",
395
+ parentMessageId: "msg_1",
396
+ },
397
+ {
398
+ type: EventType.TOOL_CALL_ARGS,
399
+ toolCallId: "call_1",
400
+ delta: '{"a":',
401
+ },
402
+ {
403
+ type: EventType.TOOL_CALL_ARGS,
404
+ toolCallId: "call_1",
405
+ delta: "10,",
406
+ },
407
+ {
408
+ type: EventType.TOOL_CALL_ARGS,
409
+ toolCallId: "call_1",
410
+ delta: '"b":20}',
411
+ },
412
+ {
413
+ type: EventType.TOOL_CALL_END,
414
+ toolCallId: "call_1",
415
+ },
416
+ {
417
+ type: EventType.CUSTOM,
418
+ name: "tambo.run.awaiting_input",
419
+ value: { pendingToolCallIds: ["call_1"] },
420
+ },
421
+ ]);
422
+ const continueStream = createAsyncIterator([
423
+ {
424
+ type: EventType.RUN_STARTED,
425
+ runId: "run_2",
426
+ threadId: "thread_123",
427
+ },
428
+ { type: EventType.RUN_FINISHED },
429
+ ]);
430
+ mockThreadsRunsApi.run
431
+ .mockResolvedValueOnce(initialStream)
432
+ .mockResolvedValueOnce(continueStream);
433
+ function TestWrapper({ children }) {
434
+ return (React.createElement(QueryClientProvider, { client: queryClient },
435
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
436
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
437
+ }
438
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
439
+ wrapper: TestWrapper,
440
+ });
441
+ await act(async () => {
442
+ await result.current.mutateAsync({
443
+ message: {
444
+ role: "user",
445
+ content: [{ type: "text", text: "Calculate" }],
446
+ },
447
+ });
448
+ });
449
+ expect(toolExecuted).toHaveBeenCalledWith({ a: 10, b: 20 });
450
+ });
451
+ it("handles missing TOOL_CALL_ARGS gracefully", async () => {
452
+ // Test that tools can be executed even when no args events are received
453
+ // (the hook keeps input as empty object)
454
+ const toolExecuted = jest.fn().mockResolvedValue("result");
455
+ const testTool = {
456
+ name: "test_tool",
457
+ description: "Test tool",
458
+ tool: toolExecuted,
459
+ inputSchema: z.object({}),
460
+ outputSchema: z.string(),
461
+ };
462
+ const mockRegistry = {
463
+ componentList: new Map(),
464
+ toolRegistry: new Map([["test_tool", testTool]]),
465
+ };
466
+ const initialStream = createAsyncIterator([
467
+ {
468
+ type: EventType.RUN_STARTED,
469
+ runId: "run_1",
470
+ threadId: "thread_123",
471
+ },
472
+ {
473
+ type: EventType.TEXT_MESSAGE_START,
474
+ messageId: "msg_1",
475
+ role: "assistant",
476
+ },
477
+ {
478
+ type: EventType.TOOL_CALL_START,
479
+ toolCallId: "call_1",
480
+ toolCallName: "test_tool",
481
+ parentMessageId: "msg_1",
482
+ },
483
+ // No TOOL_CALL_ARGS events - args will be empty
484
+ {
485
+ type: EventType.TOOL_CALL_END,
486
+ toolCallId: "call_1",
487
+ },
488
+ {
489
+ type: EventType.CUSTOM,
490
+ name: "tambo.run.awaiting_input",
491
+ value: { pendingToolCallIds: ["call_1"] },
492
+ },
493
+ ]);
494
+ const continueStream = createAsyncIterator([
495
+ {
496
+ type: EventType.RUN_STARTED,
497
+ runId: "run_2",
498
+ threadId: "thread_123",
499
+ },
500
+ { type: EventType.RUN_FINISHED },
501
+ ]);
502
+ mockThreadsRunsApi.run
503
+ .mockResolvedValueOnce(initialStream)
504
+ .mockResolvedValueOnce(continueStream);
505
+ function TestWrapper({ children }) {
506
+ return (React.createElement(QueryClientProvider, { client: queryClient },
507
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
508
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
509
+ }
510
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
511
+ wrapper: TestWrapper,
512
+ });
513
+ await act(async () => {
514
+ await result.current.mutateAsync({
515
+ message: {
516
+ role: "user",
517
+ content: [{ type: "text", text: "Test" }],
518
+ },
519
+ });
520
+ });
521
+ // Tool should be called with empty input due to no args events
522
+ expect(toolExecuted).toHaveBeenCalledWith({});
523
+ });
524
+ it("handles unknown tool in awaiting_input by returning error result", async () => {
525
+ const mockRegistry = {
526
+ componentList: new Map(),
527
+ toolRegistry: new Map(), // Empty registry - tool not found
528
+ };
529
+ const initialStream = createAsyncIterator([
530
+ {
531
+ type: EventType.RUN_STARTED,
532
+ runId: "run_1",
533
+ threadId: "thread_123",
534
+ },
535
+ {
536
+ type: EventType.TEXT_MESSAGE_START,
537
+ messageId: "msg_1",
538
+ role: "assistant",
539
+ },
540
+ {
541
+ type: EventType.TOOL_CALL_START,
542
+ toolCallId: "call_1",
543
+ toolCallName: "unknown_tool",
544
+ parentMessageId: "msg_1",
545
+ },
546
+ {
547
+ type: EventType.TOOL_CALL_ARGS,
548
+ toolCallId: "call_1",
549
+ delta: "{}",
550
+ },
551
+ {
552
+ type: EventType.TOOL_CALL_END,
553
+ toolCallId: "call_1",
554
+ },
555
+ {
556
+ type: EventType.CUSTOM,
557
+ name: "tambo.run.awaiting_input",
558
+ value: { pendingToolCallIds: ["call_1"] },
559
+ },
560
+ ]);
561
+ const continueStream = createAsyncIterator([
562
+ {
563
+ type: EventType.RUN_STARTED,
564
+ runId: "run_2",
565
+ threadId: "thread_123",
566
+ },
567
+ { type: EventType.RUN_FINISHED },
568
+ ]);
569
+ mockThreadsRunsApi.run
570
+ .mockResolvedValueOnce(initialStream)
571
+ .mockResolvedValueOnce(continueStream);
572
+ function TestWrapper({ children }) {
573
+ return (React.createElement(QueryClientProvider, { client: queryClient },
574
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
575
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
576
+ }
577
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
578
+ wrapper: TestWrapper,
579
+ });
580
+ await act(async () => {
581
+ await result.current.mutateAsync({
582
+ message: {
583
+ role: "user",
584
+ content: [{ type: "text", text: "Test" }],
585
+ },
586
+ });
587
+ });
588
+ // Verify continuation was called (second call)
589
+ expect(mockThreadsRunsApi.run).toHaveBeenCalledTimes(2);
590
+ // Continuation should include error message for unknown tool
591
+ const continueCall = mockThreadsRunsApi.run.mock.calls[1];
592
+ expect(continueCall[1].message.content[0]).toEqual({
593
+ type: "tool_result",
594
+ toolUseId: "call_1",
595
+ content: [
596
+ { type: "text", text: 'Tool "unknown_tool" not found in registry' },
597
+ ],
598
+ });
599
+ });
600
+ it("works with default registry context when no provider is present", () => {
601
+ // TamboRegistryContext has a default value, so the hook should work
602
+ // (though with an empty registry)
603
+ function TestWrapperWithoutRegistry({ children, }) {
604
+ return (React.createElement(QueryClientProvider, { client: queryClient },
605
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children)));
606
+ }
607
+ // Should not throw - default context is used
608
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
609
+ wrapper: TestWrapperWithoutRegistry,
610
+ });
611
+ expect(result.current.mutate).toBeDefined();
612
+ });
613
+ it("invalidates queries on successful mutation", async () => {
614
+ const mockStream = createAsyncIterator([
615
+ {
616
+ type: EventType.RUN_STARTED,
617
+ runId: "run_1",
618
+ threadId: "thread_123",
619
+ },
620
+ { type: EventType.RUN_FINISHED },
621
+ ]);
622
+ mockThreadsRunsApi.run.mockResolvedValue(mockStream);
623
+ const mockRegistry = {
624
+ componentList: new Map(),
625
+ toolRegistry: new Map(),
626
+ };
627
+ function TestWrapper({ children }) {
628
+ return (React.createElement(QueryClientProvider, { client: queryClient },
629
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
630
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
631
+ }
632
+ const invalidateQueriesSpy = jest.spyOn(queryClient, "invalidateQueries");
633
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
634
+ wrapper: TestWrapper,
635
+ });
636
+ await act(async () => {
637
+ await result.current.mutateAsync({
638
+ message: {
639
+ role: "user",
640
+ content: [{ type: "text", text: "Hello" }],
641
+ },
642
+ });
643
+ });
644
+ await waitFor(() => {
645
+ expect(invalidateQueriesSpy).toHaveBeenCalledWith({
646
+ queryKey: ["v1-threads", "thread_123"],
647
+ });
648
+ });
649
+ });
650
+ it("handles multi-round tool execution (tool→AI→tool→AI)", async () => {
651
+ const tool1Executed = jest.fn().mockResolvedValue("result from tool 1");
652
+ const tool2Executed = jest.fn().mockResolvedValue("result from tool 2");
653
+ const tool1 = {
654
+ name: "tool_1",
655
+ description: "First tool",
656
+ tool: tool1Executed,
657
+ inputSchema: z.object({ input: z.string() }),
658
+ outputSchema: z.string(),
659
+ };
660
+ const tool2 = {
661
+ name: "tool_2",
662
+ description: "Second tool",
663
+ tool: tool2Executed,
664
+ inputSchema: z.object({ input: z.string() }),
665
+ outputSchema: z.string(),
666
+ };
667
+ const mockRegistry = {
668
+ componentList: new Map(),
669
+ toolRegistry: new Map([
670
+ ["tool_1", tool1],
671
+ ["tool_2", tool2],
672
+ ]),
673
+ };
674
+ // Initial stream: AI calls tool_1
675
+ const initialStream = createAsyncIterator([
676
+ {
677
+ type: EventType.RUN_STARTED,
678
+ runId: "run_1",
679
+ threadId: "thread_123",
680
+ },
681
+ {
682
+ type: EventType.TEXT_MESSAGE_START,
683
+ messageId: "msg_1",
684
+ role: "assistant",
685
+ },
686
+ {
687
+ type: EventType.TOOL_CALL_START,
688
+ toolCallId: "call_1",
689
+ toolCallName: "tool_1",
690
+ parentMessageId: "msg_1",
691
+ },
692
+ {
693
+ type: EventType.TOOL_CALL_ARGS,
694
+ toolCallId: "call_1",
695
+ delta: '{"input":"first"}',
696
+ },
697
+ { type: EventType.TOOL_CALL_END, toolCallId: "call_1" },
698
+ {
699
+ type: EventType.CUSTOM,
700
+ name: "tambo.run.awaiting_input",
701
+ value: { pendingToolCallIds: ["call_1"] },
702
+ },
703
+ ]);
704
+ // Second stream: AI receives tool_1 result, then calls tool_2
705
+ const secondStream = createAsyncIterator([
706
+ {
707
+ type: EventType.RUN_STARTED,
708
+ runId: "run_2",
709
+ threadId: "thread_123",
710
+ },
711
+ {
712
+ type: EventType.TEXT_MESSAGE_START,
713
+ messageId: "msg_2",
714
+ role: "assistant",
715
+ },
716
+ {
717
+ type: EventType.TOOL_CALL_START,
718
+ toolCallId: "call_2",
719
+ toolCallName: "tool_2",
720
+ parentMessageId: "msg_2",
721
+ },
722
+ {
723
+ type: EventType.TOOL_CALL_ARGS,
724
+ toolCallId: "call_2",
725
+ delta: '{"input":"second"}',
726
+ },
727
+ { type: EventType.TOOL_CALL_END, toolCallId: "call_2" },
728
+ {
729
+ type: EventType.CUSTOM,
730
+ name: "tambo.run.awaiting_input",
731
+ value: { pendingToolCallIds: ["call_2"] },
732
+ },
733
+ ]);
734
+ // Third stream: AI receives tool_2 result, finishes
735
+ const thirdStream = createAsyncIterator([
736
+ {
737
+ type: EventType.RUN_STARTED,
738
+ runId: "run_3",
739
+ threadId: "thread_123",
740
+ },
741
+ { type: EventType.RUN_FINISHED },
742
+ ]);
743
+ mockThreadsRunsApi.run
744
+ .mockResolvedValueOnce(initialStream)
745
+ .mockResolvedValueOnce(secondStream)
746
+ .mockResolvedValueOnce(thirdStream);
747
+ function TestWrapper({ children }) {
748
+ return (React.createElement(QueryClientProvider, { client: queryClient },
749
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
750
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
751
+ }
752
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
753
+ wrapper: TestWrapper,
754
+ });
755
+ await act(async () => {
756
+ await result.current.mutateAsync({
757
+ message: {
758
+ role: "user",
759
+ content: [{ type: "text", text: "Hello" }],
760
+ },
761
+ });
762
+ });
763
+ // Both tools should have been executed
764
+ expect(tool1Executed).toHaveBeenCalledWith({ input: "first" });
765
+ expect(tool2Executed).toHaveBeenCalledWith({ input: "second" });
766
+ // Should have made 3 API calls (initial + 2 continuations)
767
+ expect(mockThreadsRunsApi.run).toHaveBeenCalledTimes(3);
768
+ // First continuation should have tool_1 result with previousRunId: run_1
769
+ const firstContinue = mockThreadsRunsApi.run.mock.calls[1];
770
+ expect(firstContinue[1].previousRunId).toBe("run_1");
771
+ expect(firstContinue[1].message.content[0]).toEqual({
772
+ type: "tool_result",
773
+ toolUseId: "call_1",
774
+ content: [{ type: "text", text: "result from tool 1" }],
775
+ });
776
+ // Second continuation should have tool_2 result with previousRunId: run_2
777
+ const secondContinue = mockThreadsRunsApi.run.mock.calls[2];
778
+ expect(secondContinue[1].previousRunId).toBe("run_2");
779
+ expect(secondContinue[1].message.content[0]).toEqual({
780
+ type: "tool_result",
781
+ toolUseId: "call_2",
782
+ content: [{ type: "text", text: "result from tool 2" }],
783
+ });
784
+ });
785
+ it("logs error on mutation failure", async () => {
786
+ const consoleSpy = jest
787
+ .spyOn(console, "error")
788
+ .mockImplementation(() => { });
789
+ const testError = new Error("API Error");
790
+ mockThreadsRunsApi.run.mockRejectedValue(testError);
791
+ const mockRegistry = {
792
+ componentList: new Map(),
793
+ toolRegistry: new Map(),
794
+ };
795
+ function TestWrapper({ children }) {
796
+ return (React.createElement(QueryClientProvider, { client: queryClient },
797
+ React.createElement(TamboRegistryContext.Provider, { value: mockRegistry },
798
+ React.createElement(TamboV1StreamProvider, { threadId: "thread_123" }, children))));
799
+ }
800
+ const { result } = renderHook(() => useTamboV1SendMessage("thread_123"), {
801
+ wrapper: TestWrapper,
802
+ });
803
+ try {
804
+ await act(async () => {
805
+ await result.current.mutateAsync({
806
+ message: {
807
+ role: "user",
808
+ content: [{ type: "text", text: "Hello" }],
809
+ },
810
+ });
811
+ });
812
+ }
813
+ catch {
814
+ // Expected to throw
815
+ }
816
+ await waitFor(() => {
817
+ expect(consoleSpy).toHaveBeenCalledWith("[useTamboV1SendMessage] Mutation failed:", testError);
818
+ });
819
+ consoleSpy.mockRestore();
820
+ });
821
+ });
822
+ //# sourceMappingURL=use-tambo-v1-send-message.test.js.map