@tambo-ai/react 1.0.3 → 1.1.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.
package/README.md CHANGED
@@ -105,7 +105,7 @@ function ChatInterface() {
105
105
  | [`useTamboThread()`](https://docs.tambo.co/concepts/conversation-storage) | Fetch a single thread by ID (React Query) |
106
106
  | [`useTamboThreadList()`](https://docs.tambo.co/concepts/conversation-storage) | Fetch thread list with filtering and pagination |
107
107
  | [`useTamboStreamStatus()`](https://docs.tambo.co/reference/react-sdk/hooks#usetambostreamstatus) | Monitor prop-level streaming status for progressive loading |
108
- | [`useTamboSuggestions()`](https://docs.tambo.co/concepts/suggestions) | Generate contextual suggestions |
108
+ | [`useTamboSuggestions()`](https://docs.tambo.co/guides/build-interfaces/build-chat-interface) | Generate contextual suggestions |
109
109
  | [`useTamboComponentState()`](https://docs.tambo.co/concepts/generative-interfaces/component-state) | Bidirectional component state synced with the backend |
110
110
  | [`useTamboVoice()`](https://docs.tambo.co/reference/react-sdk/hooks#usetambovoice) | Voice input and transcription |
111
111
 
@@ -147,6 +147,70 @@ describe("useTamboThreadInput", () => {
147
147
  debug: undefined,
148
148
  });
149
149
  });
150
+ it("clears input immediately when submit starts", async () => {
151
+ let resolveSubmit;
152
+ const pendingSubmit = new Promise((resolve) => {
153
+ resolveSubmit = resolve;
154
+ });
155
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
156
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_v1_thread_input_provider_1.useTamboThreadInput)(), {
157
+ wrapper: createWrapper(),
158
+ });
159
+ (0, react_1.act)(() => {
160
+ result.current.setValue("message to send");
161
+ });
162
+ let inFlightSubmit;
163
+ await (0, react_1.act)(async () => {
164
+ inFlightSubmit = result.current.submit();
165
+ await Promise.resolve();
166
+ });
167
+ await (0, react_1.waitFor)(() => {
168
+ expect(result.current.value).toBe("");
169
+ });
170
+ (0, react_1.act)(() => {
171
+ resolveSubmit?.({ threadId: "thread_123" });
172
+ });
173
+ if (!inFlightSubmit) {
174
+ throw new Error("Submit promise was not created");
175
+ }
176
+ await (0, react_1.act)(async () => {
177
+ await inFlightSubmit;
178
+ });
179
+ });
180
+ it("preserves input edits made while a previous submit is pending", async () => {
181
+ let resolveSubmit;
182
+ const pendingSubmit = new Promise((resolve) => {
183
+ resolveSubmit = resolve;
184
+ });
185
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
186
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_v1_thread_input_provider_1.useTamboThreadInput)(), {
187
+ wrapper: createWrapper(),
188
+ });
189
+ (0, react_1.act)(() => {
190
+ result.current.setValue("first message");
191
+ });
192
+ let inFlightSubmit;
193
+ (0, react_1.act)(() => {
194
+ inFlightSubmit = result.current.submit();
195
+ });
196
+ await (0, react_1.waitFor)(() => {
197
+ expect(result.current.isPending).toBe(true);
198
+ });
199
+ (0, react_1.act)(() => {
200
+ result.current.setValue("second message");
201
+ });
202
+ expect(result.current.value).toBe("second message");
203
+ (0, react_1.act)(() => {
204
+ resolveSubmit?.({ threadId: "thread_123" });
205
+ });
206
+ if (!inFlightSubmit) {
207
+ throw new Error("Submit promise was not created");
208
+ }
209
+ await (0, react_1.act)(async () => {
210
+ await inFlightSubmit;
211
+ });
212
+ expect(result.current.value).toBe("second message");
213
+ });
150
214
  it("throws error when submitting empty message", async () => {
151
215
  const { result } = (0, react_1.renderHook)(() => (0, tambo_v1_thread_input_provider_1.useTamboThreadInput)(), {
152
216
  wrapper: createWrapper(),
@@ -246,6 +310,45 @@ describe("useTamboThreadInput", () => {
246
310
  debug: undefined,
247
311
  });
248
312
  });
313
+ it("preserves images added while a previous submit is pending", async () => {
314
+ let resolveSubmit;
315
+ const pendingSubmit = new Promise((resolve) => {
316
+ resolveSubmit = resolve;
317
+ });
318
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
319
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_v1_thread_input_provider_1.useTamboThreadInput)(), {
320
+ wrapper: createWrapper(),
321
+ });
322
+ await (0, react_1.act)(async () => {
323
+ await result.current.addImage(new File(["image-1"], "one.png", { type: "image/png" }));
324
+ });
325
+ (0, react_1.act)(() => {
326
+ result.current.setValue("Test message");
327
+ });
328
+ let inFlightSubmit;
329
+ (0, react_1.act)(() => {
330
+ inFlightSubmit = result.current.submit();
331
+ });
332
+ await (0, react_1.waitFor)(() => {
333
+ expect(result.current.isPending).toBe(true);
334
+ });
335
+ await (0, react_1.act)(async () => {
336
+ await result.current.addImage(new File(["image-2"], "two.png", { type: "image/png" }));
337
+ });
338
+ (0, react_1.act)(() => {
339
+ resolveSubmit?.({ threadId: "thread_123" });
340
+ });
341
+ if (!inFlightSubmit) {
342
+ throw new Error("Submit promise was not created");
343
+ }
344
+ await (0, react_1.act)(async () => {
345
+ await inFlightSubmit;
346
+ });
347
+ await (0, react_1.waitFor)(() => {
348
+ expect(result.current.images.length).toBe(1);
349
+ expect(result.current.images[0]?.name).toBe("two.png");
350
+ });
351
+ });
249
352
  });
250
353
  describe("Thread ID Management", () => {
251
354
  it("initializes with placeholder threadId for optimistic UI", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-v1-thread-input.test.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-thread-input.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport {\n TamboThreadInputProvider,\n useTamboThreadInput,\n} from \"../providers/tambo-v1-thread-input-provider\";\nimport { TamboStreamProvider } from \"../providers/tambo-v1-stream-context\";\nimport { useTamboSendMessage } from \"./use-tambo-v1-send-message\";\nimport type { StreamAction, StreamState } from \"../utils/event-accumulator\";\n\n// Mock useTamboSendMessage\njest.mock(\"./use-tambo-v1-send-message\", () => ({\n useTamboSendMessage: jest.fn(),\n}));\n\n// Mock useTamboQueryClient to avoid TamboClientProvider dependency\njest.mock(\"../../providers/tambo-client-provider\", () => ({\n useTamboQueryClient: jest.fn(() => new QueryClient()),\n useTamboClient: jest.fn(),\n}));\n\njest.mock(\"../providers/tambo-v1-provider\", () => {\n const actual = jest.requireActual(\"../providers/tambo-v1-provider\");\n return {\n ...actual,\n useTamboConfig: () => ({ userKey: undefined }),\n };\n});\n\njest.mock(\"./use-tambo-v1-auth-state\", () => ({\n useTamboAuthState: () => ({\n status: \"identified\",\n source: \"userKey\",\n }),\n}));\n\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: unknown) => void) | null,\n onerror: null as ((e: unknown) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n reader.onload?.({});\n }, 0);\n });\n\n return reader;\n};\n\nconst originalFileReader = (global as any).FileReader;\n\ndescribe(\"useTamboThreadInput\", () => {\n const mockMutateAsync = jest.fn();\n let queryClient: QueryClient;\n\n function createWrapper({ streamState }: { streamState?: StreamState } = {}) {\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return function Wrapper({ children }: { children: React.ReactNode }) {\n const streamProviderProps =\n streamState === undefined\n ? {}\n : { state: streamState, dispatch: noopDispatch };\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider {...streamProviderProps}>\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n }\n\n beforeEach(() => {\n jest.clearAllMocks();\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n mockMutateAsync.mockResolvedValue({ threadId: \"thread_123\" });\n jest.mocked(useTamboSendMessage).mockReturnValue({\n mutateAsync: mockMutateAsync,\n mutate: jest.fn(),\n isPending: false,\n isError: false,\n error: null,\n isSuccess: false,\n isIdle: true,\n isPaused: false,\n status: \"idle\",\n data: undefined,\n variables: undefined,\n failureCount: 0,\n failureReason: null,\n reset: jest.fn(),\n context: undefined,\n submittedAt: 0,\n } as any);\n });\n\n afterEach(() => {\n (global as any).FileReader = originalFileReader;\n });\n\n describe(\"State Management\", () => {\n it(\"initializes with empty value\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.value).toBe(\"\");\n });\n\n it(\"updates value via setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello world\");\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n\n it(\"supports functional updates for setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello\");\n });\n\n act(() => {\n result.current.setValue((prev) => `${prev} world`);\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n });\n\n describe(\"Submit Behavior\", () => {\n it(\"submits message and clears input on success\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n const response = await result.current.submit();\n expect(response.threadId).toBe(\"thread_123\");\n });\n\n // Input should be cleared\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n // Should have called mutateAsync with correct message format\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Test message\" }],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"throws error when submitting empty message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"throws error when submitting whitespace-only message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\" \");\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"passes debug option to mutation\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Debug message\");\n });\n\n await act(async () => {\n await result.current.submit({ debug: true });\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Debug message\" }],\n },\n userMessageText: \"Debug message\",\n debug: true,\n });\n });\n\n it(\"submits image-only messages as resource content\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"\",\n debug: undefined,\n });\n\n await waitFor(() => {\n expect(result.current.images).toEqual([]);\n });\n });\n\n it(\"includes both text and image resource content when both are present\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n { type: \"text\", text: \"Test message\" },\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n });\n\n describe(\"Thread ID Management\", () => {\n it(\"initializes with placeholder threadId for optimistic UI\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n // Default state has placeholder thread for optimistic UI\n expect(result.current.threadId).toBe(\"placeholder\");\n });\n\n it(\"uses currentThreadId from stream state\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n expect(result.current.threadId).toBe(\"thread_stream\");\n expect(\n jest.mocked(useTamboSendMessage).mock.calls.map((call) => call[0]),\n ).toContain(\"thread_stream\");\n });\n\n it(\"uses stream state threadId when submitting messages\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n // Verify sendMessage was called with the stream state's threadId\n expect(mockMutateAsync).toHaveBeenCalledWith(\n expect.objectContaining({\n message: expect.objectContaining({\n content: [{ type: \"text\", text: \"Test message\" }],\n }),\n }),\n );\n });\n });\n\n describe(\"Image State\", () => {\n it(\"initializes with empty images array\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.images).toEqual([]);\n });\n\n it(\"exposes image management functions\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\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(\"Error handling\", () => {\n it(\"throws error when used outside provider\", () => {\n // Suppress console.error for this test\n const consoleSpy = jest\n .spyOn(console, \"error\")\n .mockImplementation(() => {});\n\n expect(() => {\n renderHook(() => useTamboThreadInput());\n }).toThrow(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n\n consoleSpy.mockRestore();\n });\n });\n\n describe(\"Thread Isolation\", () => {\n /**\n * Creates a wrapper that allows dynamically changing the thread ID.\n * This simulates switching between threads in the UI.\n */\n function createDynamicThreadWrapper(initialThreadId: string) {\n let currentThreadId = initialThreadId;\n const listeners = new Set<() => void>();\n\n const setThreadId = (newThreadId: string) => {\n currentThreadId = newThreadId;\n listeners.forEach((listener) => listener());\n };\n\n const Wrapper = ({ children }: { children: React.ReactNode }) => {\n const [threadId, setThreadIdState] = React.useState(currentThreadId);\n\n React.useEffect(() => {\n const listener = () => setThreadIdState(currentThreadId);\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }, []);\n\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider\n state={{ threadMap: {}, currentThreadId: threadId }}\n dispatch={noopDispatch}\n >\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n\n return { Wrapper, setThreadId };\n }\n\n it(\"isolates pending state per thread when switching threads\", async () => {\n // Create a deferred promise so we can control when the mutation resolves\n let resolveThreadA: (value: { threadId: string }) => void;\n const threadAPromise = new Promise<{ threadId: string }>((resolve) => {\n resolveThreadA = resolve;\n });\n\n mockMutateAsync.mockImplementation(async () => await threadAPromise);\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Verify we're on thread A\n expect(result.current.threadId).toBe(\"thread_A\");\n\n // Set input and start submission on thread A\n act(() => {\n result.current.setValue(\"Message for thread A\");\n });\n\n // Start the submission (don't await - we want it pending)\n let submitPromise: Promise<{ threadId: string | undefined }>;\n act(() => {\n submitPromise = result.current.submit();\n });\n\n // Thread A should now be pending\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B while thread A is still pending\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n // Wait for the thread switch to take effect\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should NOT be pending - its mutation state is independent\n // (The mutationKey includes threadId, so each thread has its own state)\n expect(result.current.isPending).toBe(false);\n\n // Now resolve thread A's submission\n act(() => {\n resolveThreadA!({ threadId: \"thread_A\" });\n });\n\n // Wait for promise to complete\n await act(async () => {\n await submitPromise!;\n });\n\n // Thread B should still not be pending\n expect(result.current.isPending).toBe(false);\n });\n\n it(\"multiple threads that never submitted should not be pending when another thread is\", async () => {\n // Create a promise that never resolves to keep thread A pending\n const neverResolvingPromise = new Promise<{ threadId: string }>(() => {});\n\n mockMutateAsync.mockImplementation(\n async () => await neverResolvingPromise,\n );\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Start submission on thread A\n act(() => {\n result.current.setValue(\"Message A\");\n });\n\n act(() => {\n void result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B (which has never submitted anything)\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should not be pending - it has never submitted\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread C (also never submitted)\n act(() => {\n setThreadId(\"thread_C\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_C\");\n });\n\n // Thread C should also not be pending\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread D (also never submitted)\n act(() => {\n setThreadId(\"thread_D\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_D\");\n });\n\n // Thread D should also not be pending\n // This confirms the fix works - without mutationKey, ALL threads would be pending\n expect(result.current.isPending).toBe(false);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"use-tambo-v1-thread-input.test.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-thread-input.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport {\n TamboThreadInputProvider,\n useTamboThreadInput,\n} from \"../providers/tambo-v1-thread-input-provider\";\nimport { TamboStreamProvider } from \"../providers/tambo-v1-stream-context\";\nimport { useTamboSendMessage } from \"./use-tambo-v1-send-message\";\nimport type { StreamAction, StreamState } from \"../utils/event-accumulator\";\n\n// Mock useTamboSendMessage\njest.mock(\"./use-tambo-v1-send-message\", () => ({\n useTamboSendMessage: jest.fn(),\n}));\n\n// Mock useTamboQueryClient to avoid TamboClientProvider dependency\njest.mock(\"../../providers/tambo-client-provider\", () => ({\n useTamboQueryClient: jest.fn(() => new QueryClient()),\n useTamboClient: jest.fn(),\n}));\n\njest.mock(\"../providers/tambo-v1-provider\", () => {\n const actual = jest.requireActual(\"../providers/tambo-v1-provider\");\n return {\n ...actual,\n useTamboConfig: () => ({ userKey: undefined }),\n };\n});\n\njest.mock(\"./use-tambo-v1-auth-state\", () => ({\n useTamboAuthState: () => ({\n status: \"identified\",\n source: \"userKey\",\n }),\n}));\n\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: unknown) => void) | null,\n onerror: null as ((e: unknown) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n reader.onload?.({});\n }, 0);\n });\n\n return reader;\n};\n\nconst originalFileReader = (global as any).FileReader;\n\ndescribe(\"useTamboThreadInput\", () => {\n const mockMutateAsync = jest.fn();\n let queryClient: QueryClient;\n\n function createWrapper({ streamState }: { streamState?: StreamState } = {}) {\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return function Wrapper({ children }: { children: React.ReactNode }) {\n const streamProviderProps =\n streamState === undefined\n ? {}\n : { state: streamState, dispatch: noopDispatch };\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider {...streamProviderProps}>\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n }\n\n beforeEach(() => {\n jest.clearAllMocks();\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n mockMutateAsync.mockResolvedValue({ threadId: \"thread_123\" });\n jest.mocked(useTamboSendMessage).mockReturnValue({\n mutateAsync: mockMutateAsync,\n mutate: jest.fn(),\n isPending: false,\n isError: false,\n error: null,\n isSuccess: false,\n isIdle: true,\n isPaused: false,\n status: \"idle\",\n data: undefined,\n variables: undefined,\n failureCount: 0,\n failureReason: null,\n reset: jest.fn(),\n context: undefined,\n submittedAt: 0,\n } as any);\n });\n\n afterEach(() => {\n (global as any).FileReader = originalFileReader;\n });\n\n describe(\"State Management\", () => {\n it(\"initializes with empty value\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.value).toBe(\"\");\n });\n\n it(\"updates value via setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello world\");\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n\n it(\"supports functional updates for setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello\");\n });\n\n act(() => {\n result.current.setValue((prev) => `${prev} world`);\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n });\n\n describe(\"Submit Behavior\", () => {\n it(\"submits message and clears input on success\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n const response = await result.current.submit();\n expect(response.threadId).toBe(\"thread_123\");\n });\n\n // Input should be cleared\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n // Should have called mutateAsync with correct message format\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Test message\" }],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"clears input immediately when submit starts\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"message to send\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n await act(async () => {\n inFlightSubmit = result.current.submit();\n await Promise.resolve();\n });\n\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n });\n\n it(\"preserves input edits made while a previous submit is pending\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"first message\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n act(() => {\n inFlightSubmit = result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n act(() => {\n result.current.setValue(\"second message\");\n });\n expect(result.current.value).toBe(\"second message\");\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n\n expect(result.current.value).toBe(\"second message\");\n });\n\n it(\"throws error when submitting empty message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"throws error when submitting whitespace-only message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\" \");\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"passes debug option to mutation\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Debug message\");\n });\n\n await act(async () => {\n await result.current.submit({ debug: true });\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Debug message\" }],\n },\n userMessageText: \"Debug message\",\n debug: true,\n });\n });\n\n it(\"submits image-only messages as resource content\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"\",\n debug: undefined,\n });\n\n await waitFor(() => {\n expect(result.current.images).toEqual([]);\n });\n });\n\n it(\"includes both text and image resource content when both are present\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n { type: \"text\", text: \"Test message\" },\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"preserves images added while a previous submit is pending\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image-1\"], \"one.png\", { type: \"image/png\" }),\n );\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n act(() => {\n inFlightSubmit = result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image-2\"], \"two.png\", { type: \"image/png\" }),\n );\n });\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n\n await waitFor(() => {\n expect(result.current.images.length).toBe(1);\n expect(result.current.images[0]?.name).toBe(\"two.png\");\n });\n });\n });\n\n describe(\"Thread ID Management\", () => {\n it(\"initializes with placeholder threadId for optimistic UI\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n // Default state has placeholder thread for optimistic UI\n expect(result.current.threadId).toBe(\"placeholder\");\n });\n\n it(\"uses currentThreadId from stream state\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n expect(result.current.threadId).toBe(\"thread_stream\");\n expect(\n jest.mocked(useTamboSendMessage).mock.calls.map((call) => call[0]),\n ).toContain(\"thread_stream\");\n });\n\n it(\"uses stream state threadId when submitting messages\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n // Verify sendMessage was called with the stream state's threadId\n expect(mockMutateAsync).toHaveBeenCalledWith(\n expect.objectContaining({\n message: expect.objectContaining({\n content: [{ type: \"text\", text: \"Test message\" }],\n }),\n }),\n );\n });\n });\n\n describe(\"Image State\", () => {\n it(\"initializes with empty images array\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.images).toEqual([]);\n });\n\n it(\"exposes image management functions\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\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(\"Error handling\", () => {\n it(\"throws error when used outside provider\", () => {\n // Suppress console.error for this test\n const consoleSpy = jest\n .spyOn(console, \"error\")\n .mockImplementation(() => {});\n\n expect(() => {\n renderHook(() => useTamboThreadInput());\n }).toThrow(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n\n consoleSpy.mockRestore();\n });\n });\n\n describe(\"Thread Isolation\", () => {\n /**\n * Creates a wrapper that allows dynamically changing the thread ID.\n * This simulates switching between threads in the UI.\n */\n function createDynamicThreadWrapper(initialThreadId: string) {\n let currentThreadId = initialThreadId;\n const listeners = new Set<() => void>();\n\n const setThreadId = (newThreadId: string) => {\n currentThreadId = newThreadId;\n listeners.forEach((listener) => listener());\n };\n\n const Wrapper = ({ children }: { children: React.ReactNode }) => {\n const [threadId, setThreadIdState] = React.useState(currentThreadId);\n\n React.useEffect(() => {\n const listener = () => setThreadIdState(currentThreadId);\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }, []);\n\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider\n state={{ threadMap: {}, currentThreadId: threadId }}\n dispatch={noopDispatch}\n >\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n\n return { Wrapper, setThreadId };\n }\n\n it(\"isolates pending state per thread when switching threads\", async () => {\n // Create a deferred promise so we can control when the mutation resolves\n let resolveThreadA: (value: { threadId: string }) => void;\n const threadAPromise = new Promise<{ threadId: string }>((resolve) => {\n resolveThreadA = resolve;\n });\n\n mockMutateAsync.mockImplementation(async () => await threadAPromise);\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Verify we're on thread A\n expect(result.current.threadId).toBe(\"thread_A\");\n\n // Set input and start submission on thread A\n act(() => {\n result.current.setValue(\"Message for thread A\");\n });\n\n // Start the submission (don't await - we want it pending)\n let submitPromise: Promise<{ threadId: string | undefined }>;\n act(() => {\n submitPromise = result.current.submit();\n });\n\n // Thread A should now be pending\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B while thread A is still pending\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n // Wait for the thread switch to take effect\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should NOT be pending - its mutation state is independent\n // (The mutationKey includes threadId, so each thread has its own state)\n expect(result.current.isPending).toBe(false);\n\n // Now resolve thread A's submission\n act(() => {\n resolveThreadA!({ threadId: \"thread_A\" });\n });\n\n // Wait for promise to complete\n await act(async () => {\n await submitPromise!;\n });\n\n // Thread B should still not be pending\n expect(result.current.isPending).toBe(false);\n });\n\n it(\"multiple threads that never submitted should not be pending when another thread is\", async () => {\n // Create a promise that never resolves to keep thread A pending\n const neverResolvingPromise = new Promise<{ threadId: string }>(() => {});\n\n mockMutateAsync.mockImplementation(\n async () => await neverResolvingPromise,\n );\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Start submission on thread A\n act(() => {\n result.current.setValue(\"Message A\");\n });\n\n act(() => {\n void result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B (which has never submitted anything)\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should not be pending - it has never submitted\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread C (also never submitted)\n act(() => {\n setThreadId(\"thread_C\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_C\");\n });\n\n // Thread C should also not be pending\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread D (also never submitted)\n act(() => {\n setThreadId(\"thread_D\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_D\");\n });\n\n // Thread D should also not be pending\n // This confirms the fix works - without mutationKey, ALL threads would be pending\n expect(result.current.isPending).toBe(false);\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-v1-thread-input-provider.d.ts","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAKZ,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AASvD,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAkCX;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,sBAAsB,CACpB;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAChC,KAAK,EACL,aAAa,GAAG,SAAS,CAC1B,EACD,QAAQ,GAAG,aAAa,CACzB;IACC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvD;;;;OAIG;IACH,MAAM,EAAE,CACN,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAE/C,8BAA8B;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,0BAA0B;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,4BAA4B;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,8BAA8B;IAC9B,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,yDAExB,CAAC;AAEb;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBAgGvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE"}
1
+ {"version":3,"file":"tambo-v1-thread-input-provider.d.ts","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAKZ,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AASvD,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAkCX;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,sBAAsB,CACpB;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAChC,KAAK,EACL,aAAa,GAAG,SAAS,CAC1B,EACD,QAAQ,GAAG,aAAa,CACzB;IACC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvD;;;;OAIG;IACH,MAAM,EAAE,CACN,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAE/C,8BAA8B;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,0BAA0B;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,4BAA4B;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,8BAA8B;IAC9B,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,yDAExB,CAAC;AAEb;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBAkHvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE"}
@@ -117,6 +117,8 @@ function TamboThreadInputProvider({ children }) {
117
117
  "Ensure a valid userKey or userToken is provided.");
118
118
  }
119
119
  const trimmedValue = inputValue.trim();
120
+ const valueAtSubmitTime = inputValue;
121
+ const imageIdsAtSubmitTime = imageState.images.map((image) => image.id);
120
122
  // Check if we have content to send
121
123
  if (!trimmedValue && imageState.images.length === 0) {
122
124
  throw new Error(exports.INPUT_ERROR_MESSAGES.EMPTY);
@@ -128,23 +130,37 @@ function TamboThreadInputProvider({ children }) {
128
130
  for (const image of imageState.images) {
129
131
  content.push(stagedImageToResourceContent(image));
130
132
  }
131
- const result = await sendMessage.mutateAsync({
132
- message: {
133
- role: "user",
134
- content,
135
- },
136
- userMessageText: trimmedValue, // Pass text for optimistic display
137
- debug: options?.debug,
138
- toolChoice: options?.toolChoice,
139
- });
140
- // Clear input and images after successful submission
133
+ // Optimistically clear submitted text so users can start typing the next
134
+ // message immediately while the current request is pending.
141
135
  setInputValue("");
142
- imageState.clearImages();
143
- // Update stream context's currentThreadId if a new thread was created
144
- if (result.threadId && isNewThread) {
145
- dispatch({ type: "SET_CURRENT_THREAD", threadId: result.threadId });
136
+ try {
137
+ const result = await sendMessage.mutateAsync({
138
+ message: {
139
+ role: "user",
140
+ content,
141
+ },
142
+ userMessageText: trimmedValue, // Pass text for optimistic display
143
+ debug: options?.debug,
144
+ toolChoice: options?.toolChoice,
145
+ });
146
+ // Clear only submitted images so images added while pending are kept.
147
+ if (imageIdsAtSubmitTime.length > 0) {
148
+ imageIdsAtSubmitTime.forEach((id) => {
149
+ imageState.removeImage(id);
150
+ });
151
+ }
152
+ // Update stream context's currentThreadId if a new thread was created
153
+ if (result.threadId && isNewThread) {
154
+ dispatch({ type: "SET_CURRENT_THREAD", threadId: result.threadId });
155
+ }
156
+ return result;
157
+ }
158
+ catch (error) {
159
+ // If the user has not started typing a new message, restore the
160
+ // submitted value so failed sends don't silently lose input.
161
+ setInputValue((currentValue) => currentValue.length > 0 ? currentValue : valueAtSubmitTime);
162
+ throw error;
146
163
  }
147
- return result;
148
164
  },
149
165
  // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).
150
166
  [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified]);
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-v1-thread-input-provider.js","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * TamboThreadInputProvider - Shared Thread Input Context\n *\n * Provides shared input state across all components, enabling features like\n * suggestions to update the input field directly.\n *\n * This mirrors the TamboThreadInputProvider pattern from the legacy beta SDK.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useState,\n type PropsWithChildren,\n} from \"react\";\nimport {\n useMessageImages,\n type StagedImage,\n} from \"../../hooks/use-message-images\";\nimport {\n useTamboMutation,\n type UseTamboMutationResult,\n} from \"../../hooks/react-query-hooks\";\nimport { useTamboSendMessage } from \"../hooks/use-tambo-v1-send-message\";\nimport type { InputMessage } from \"../types/message\";\nimport type { ToolChoice } from \"../types/tool-choice\";\nimport { isPlaceholderThreadId } from \"../utils/event-accumulator\";\nimport { useTamboAuthState } from \"../hooks/use-tambo-v1-auth-state\";\nimport { useStreamDispatch, useStreamState } from \"./tambo-v1-stream-context\";\n\n// Error messages for various input-related error scenarios.\n// TODO: Reintroduce explicit `NETWORK` and `SERVER` keys once `submit()` maps\n// failures into a small, stable set of user-facing error codes (at minimum:\n// connectivity failures vs non-2xx responses).\nexport const INPUT_ERROR_MESSAGES = {\n EMPTY: \"Message cannot be empty\",\n VALIDATION: \"Invalid message format\",\n} as const;\n\nfunction stagedImageToResourceContent(\n image: StagedImage,\n): InputMessage[\"content\"][number] {\n if (!image.dataUrl.startsWith(\"data:\")) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const commaIndex = image.dataUrl.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const header = image.dataUrl.slice(\"data:\".length, commaIndex);\n const [mimeType, ...params] = header.split(\";\");\n const isBase64 = params.includes(\"base64\");\n\n if (mimeType !== image.type || !isBase64) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n return {\n type: \"resource\",\n resource: {\n name: image.name,\n mimeType: image.type,\n // Shared.Resource.blob expects base64-encoded data; this is the base64\n // payload from the `data:` URL.\n blob: image.dataUrl.slice(commaIndex + 1),\n },\n };\n}\n\n/**\n * Options for submitting a message\n */\nexport interface SubmitOptions {\n /**\n * Enable debug logging for the stream\n */\n debug?: boolean;\n\n /**\n * How the model should use tools. Defaults to \"auto\".\n * - \"auto\": Model decides whether to use tools\n * - \"required\": Model must use at least one tool\n * - \"none\": Model cannot use tools\n * - { name: \"toolName\" }: Model must use the specified tool\n */\n toolChoice?: ToolChoice;\n}\n\n/**\n * Context props for thread input state\n */\nexport interface TamboThreadInputContextProps extends Omit<\n UseTamboMutationResult<\n { threadId: string | undefined },\n Error,\n SubmitOptions | undefined\n >,\n \"mutate\" | \"mutateAsync\"\n> {\n /** Current value of the input field */\n value: string;\n\n /**\n * Function to update the input value\n * @param value - New value for the input field\n */\n setValue: React.Dispatch<React.SetStateAction<string>>;\n\n /**\n * Function to submit the current input value\n * @param options - Submission options\n * @returns Promise with the threadId\n */\n submit: (\n options?: SubmitOptions,\n ) => Promise<{ threadId: string | undefined }>;\n\n /** Currently staged images */\n images: StagedImage[];\n\n /** Add a single image */\n addImage: (file: File) => Promise<void>;\n\n /** Add multiple images */\n addImages: (files: File[]) => Promise<void>;\n\n /** Remove an image by id */\n removeImage: (id: string) => void;\n\n /** Clear all staged images */\n clearImages: () => void;\n\n /** Current thread ID being used for input (from stream state) */\n threadId: string | undefined;\n\n /** Whether the input should be disabled (pending submission or not authenticated) */\n isDisabled: boolean;\n}\n\n/**\n * Context for thread input state.\n * @internal\n */\nexport const TamboThreadInputContext = createContext<\n TamboThreadInputContextProps | undefined\n>(undefined);\n\n/**\n * Provider that manages shared thread input state across all components.\n *\n * This ensures that useTamboThreadInput, useTamboSuggestions, and other components\n * all share the same input state.\n * @param props - Provider props\n * @param props.children - Child components\n * @returns Thread input context provider\n */\nexport function TamboThreadInputProvider({ children }: PropsWithChildren) {\n const [inputValue, setInputValue] = useState(\"\");\n const imageState = useMessageImages();\n const streamState = useStreamState();\n const dispatch = useStreamDispatch();\n const authState = useTamboAuthState();\n\n // Use the current thread from stream state directly\n // Placeholder ID indicates a new thread should be created\n const currentThreadId = streamState.currentThreadId ?? undefined;\n const isNewThread = isPlaceholderThreadId(currentThreadId);\n const sendMessage = useTamboSendMessage(currentThreadId);\n\n const isIdentified = authState.status === \"identified\";\n\n const submitFn = useCallback(\n async (\n options?: SubmitOptions,\n ): Promise<{ threadId: string | undefined }> => {\n if (!isIdentified) {\n throw new Error(\n \"Cannot submit: authentication is not ready. \" +\n \"Ensure a valid userKey or userToken is provided.\",\n );\n }\n\n const trimmedValue = inputValue.trim();\n\n // Check if we have content to send\n if (!trimmedValue && imageState.images.length === 0) {\n throw new Error(INPUT_ERROR_MESSAGES.EMPTY);\n }\n\n const content: InputMessage[\"content\"] = [];\n\n if (trimmedValue) {\n content.push({ type: \"text\", text: trimmedValue });\n }\n\n for (const image of imageState.images) {\n content.push(stagedImageToResourceContent(image));\n }\n\n const result = await sendMessage.mutateAsync({\n message: {\n role: \"user\",\n content,\n },\n userMessageText: trimmedValue, // Pass text for optimistic display\n debug: options?.debug,\n toolChoice: options?.toolChoice,\n });\n\n // Clear input and images after successful submission\n setInputValue(\"\");\n imageState.clearImages();\n\n // Update stream context's currentThreadId if a new thread was created\n if (result.threadId && isNewThread) {\n dispatch({ type: \"SET_CURRENT_THREAD\", threadId: result.threadId });\n }\n\n return result;\n },\n // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).\n [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified],\n );\n\n const {\n mutateAsync: submitAsync,\n mutate: _unusedSubmit,\n ...mutationState\n } = useTamboMutation({\n mutationKey: [\"v1-thread-input\", currentThreadId],\n mutationFn: submitFn,\n });\n\n const contextValue: TamboThreadInputContextProps = {\n ...mutationState,\n value: inputValue,\n setValue: setInputValue,\n submit: submitAsync,\n images: imageState.images,\n addImage: imageState.addImage,\n addImages: imageState.addImages,\n removeImage: imageState.removeImage,\n clearImages: imageState.clearImages,\n threadId: currentThreadId,\n isDisabled: mutationState.isPending || !isIdentified,\n };\n\n return (\n <TamboThreadInputContext.Provider value={contextValue}>\n {children}\n </TamboThreadInputContext.Provider>\n );\n}\n\n/**\n * Hook to access the shared thread input state.\n *\n * All components using this hook share the same input state, enabling\n * features like suggestions to update the input field directly.\n * @returns The shared thread input context\n * @throws {Error} If used outside TamboThreadInputProvider\n * @example\n * ```tsx\n * function ChatInput() {\n * const { value, setValue, submit, isPending } = useTamboThreadInput();\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); submit(); }}>\n * <input\n * value={value}\n * onChange={(e) => setValue(e.target.value)}\n * disabled={isPending}\n * />\n * <button type=\"submit\" disabled={isPending}>Send</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useTamboThreadInput(): TamboThreadInputContextProps {\n const context = useContext(TamboThreadInputContext);\n if (!context) {\n throw new Error(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n }\n return context;\n}\n"]}
1
+ {"version":3,"file":"tambo-v1-thread-input-provider.js","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * TamboThreadInputProvider - Shared Thread Input Context\n *\n * Provides shared input state across all components, enabling features like\n * suggestions to update the input field directly.\n *\n * This mirrors the TamboThreadInputProvider pattern from the legacy beta SDK.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useState,\n type PropsWithChildren,\n} from \"react\";\nimport {\n useMessageImages,\n type StagedImage,\n} from \"../../hooks/use-message-images\";\nimport {\n useTamboMutation,\n type UseTamboMutationResult,\n} from \"../../hooks/react-query-hooks\";\nimport { useTamboSendMessage } from \"../hooks/use-tambo-v1-send-message\";\nimport type { InputMessage } from \"../types/message\";\nimport type { ToolChoice } from \"../types/tool-choice\";\nimport { isPlaceholderThreadId } from \"../utils/event-accumulator\";\nimport { useTamboAuthState } from \"../hooks/use-tambo-v1-auth-state\";\nimport { useStreamDispatch, useStreamState } from \"./tambo-v1-stream-context\";\n\n// Error messages for various input-related error scenarios.\n// TODO: Reintroduce explicit `NETWORK` and `SERVER` keys once `submit()` maps\n// failures into a small, stable set of user-facing error codes (at minimum:\n// connectivity failures vs non-2xx responses).\nexport const INPUT_ERROR_MESSAGES = {\n EMPTY: \"Message cannot be empty\",\n VALIDATION: \"Invalid message format\",\n} as const;\n\nfunction stagedImageToResourceContent(\n image: StagedImage,\n): InputMessage[\"content\"][number] {\n if (!image.dataUrl.startsWith(\"data:\")) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const commaIndex = image.dataUrl.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const header = image.dataUrl.slice(\"data:\".length, commaIndex);\n const [mimeType, ...params] = header.split(\";\");\n const isBase64 = params.includes(\"base64\");\n\n if (mimeType !== image.type || !isBase64) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n return {\n type: \"resource\",\n resource: {\n name: image.name,\n mimeType: image.type,\n // Shared.Resource.blob expects base64-encoded data; this is the base64\n // payload from the `data:` URL.\n blob: image.dataUrl.slice(commaIndex + 1),\n },\n };\n}\n\n/**\n * Options for submitting a message\n */\nexport interface SubmitOptions {\n /**\n * Enable debug logging for the stream\n */\n debug?: boolean;\n\n /**\n * How the model should use tools. Defaults to \"auto\".\n * - \"auto\": Model decides whether to use tools\n * - \"required\": Model must use at least one tool\n * - \"none\": Model cannot use tools\n * - { name: \"toolName\" }: Model must use the specified tool\n */\n toolChoice?: ToolChoice;\n}\n\n/**\n * Context props for thread input state\n */\nexport interface TamboThreadInputContextProps extends Omit<\n UseTamboMutationResult<\n { threadId: string | undefined },\n Error,\n SubmitOptions | undefined\n >,\n \"mutate\" | \"mutateAsync\"\n> {\n /** Current value of the input field */\n value: string;\n\n /**\n * Function to update the input value\n * @param value - New value for the input field\n */\n setValue: React.Dispatch<React.SetStateAction<string>>;\n\n /**\n * Function to submit the current input value\n * @param options - Submission options\n * @returns Promise with the threadId\n */\n submit: (\n options?: SubmitOptions,\n ) => Promise<{ threadId: string | undefined }>;\n\n /** Currently staged images */\n images: StagedImage[];\n\n /** Add a single image */\n addImage: (file: File) => Promise<void>;\n\n /** Add multiple images */\n addImages: (files: File[]) => Promise<void>;\n\n /** Remove an image by id */\n removeImage: (id: string) => void;\n\n /** Clear all staged images */\n clearImages: () => void;\n\n /** Current thread ID being used for input (from stream state) */\n threadId: string | undefined;\n\n /** Whether the input should be disabled (pending submission or not authenticated) */\n isDisabled: boolean;\n}\n\n/**\n * Context for thread input state.\n * @internal\n */\nexport const TamboThreadInputContext = createContext<\n TamboThreadInputContextProps | undefined\n>(undefined);\n\n/**\n * Provider that manages shared thread input state across all components.\n *\n * This ensures that useTamboThreadInput, useTamboSuggestions, and other components\n * all share the same input state.\n * @param props - Provider props\n * @param props.children - Child components\n * @returns Thread input context provider\n */\nexport function TamboThreadInputProvider({ children }: PropsWithChildren) {\n const [inputValue, setInputValue] = useState(\"\");\n const imageState = useMessageImages();\n const streamState = useStreamState();\n const dispatch = useStreamDispatch();\n const authState = useTamboAuthState();\n\n // Use the current thread from stream state directly\n // Placeholder ID indicates a new thread should be created\n const currentThreadId = streamState.currentThreadId ?? undefined;\n const isNewThread = isPlaceholderThreadId(currentThreadId);\n const sendMessage = useTamboSendMessage(currentThreadId);\n\n const isIdentified = authState.status === \"identified\";\n\n const submitFn = useCallback(\n async (\n options?: SubmitOptions,\n ): Promise<{ threadId: string | undefined }> => {\n if (!isIdentified) {\n throw new Error(\n \"Cannot submit: authentication is not ready. \" +\n \"Ensure a valid userKey or userToken is provided.\",\n );\n }\n\n const trimmedValue = inputValue.trim();\n const valueAtSubmitTime = inputValue;\n const imageIdsAtSubmitTime = imageState.images.map((image) => image.id);\n\n // Check if we have content to send\n if (!trimmedValue && imageState.images.length === 0) {\n throw new Error(INPUT_ERROR_MESSAGES.EMPTY);\n }\n\n const content: InputMessage[\"content\"] = [];\n\n if (trimmedValue) {\n content.push({ type: \"text\", text: trimmedValue });\n }\n\n for (const image of imageState.images) {\n content.push(stagedImageToResourceContent(image));\n }\n\n // Optimistically clear submitted text so users can start typing the next\n // message immediately while the current request is pending.\n setInputValue(\"\");\n\n try {\n const result = await sendMessage.mutateAsync({\n message: {\n role: \"user\",\n content,\n },\n userMessageText: trimmedValue, // Pass text for optimistic display\n debug: options?.debug,\n toolChoice: options?.toolChoice,\n });\n\n // Clear only submitted images so images added while pending are kept.\n if (imageIdsAtSubmitTime.length > 0) {\n imageIdsAtSubmitTime.forEach((id) => {\n imageState.removeImage(id);\n });\n }\n\n // Update stream context's currentThreadId if a new thread was created\n if (result.threadId && isNewThread) {\n dispatch({ type: \"SET_CURRENT_THREAD\", threadId: result.threadId });\n }\n\n return result;\n } catch (error) {\n // If the user has not started typing a new message, restore the\n // submitted value so failed sends don't silently lose input.\n setInputValue((currentValue) =>\n currentValue.length > 0 ? currentValue : valueAtSubmitTime,\n );\n throw error;\n }\n },\n // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).\n [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified],\n );\n\n const {\n mutateAsync: submitAsync,\n mutate: _unusedSubmit,\n ...mutationState\n } = useTamboMutation({\n mutationKey: [\"v1-thread-input\", currentThreadId],\n mutationFn: submitFn,\n });\n\n const contextValue: TamboThreadInputContextProps = {\n ...mutationState,\n value: inputValue,\n setValue: setInputValue,\n submit: submitAsync,\n images: imageState.images,\n addImage: imageState.addImage,\n addImages: imageState.addImages,\n removeImage: imageState.removeImage,\n clearImages: imageState.clearImages,\n threadId: currentThreadId,\n isDisabled: mutationState.isPending || !isIdentified,\n };\n\n return (\n <TamboThreadInputContext.Provider value={contextValue}>\n {children}\n </TamboThreadInputContext.Provider>\n );\n}\n\n/**\n * Hook to access the shared thread input state.\n *\n * All components using this hook share the same input state, enabling\n * features like suggestions to update the input field directly.\n * @returns The shared thread input context\n * @throws {Error} If used outside TamboThreadInputProvider\n * @example\n * ```tsx\n * function ChatInput() {\n * const { value, setValue, submit, isPending } = useTamboThreadInput();\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); submit(); }}>\n * <input\n * value={value}\n * onChange={(e) => setValue(e.target.value)}\n * disabled={isPending}\n * />\n * <button type=\"submit\" disabled={isPending}>Send</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useTamboThreadInput(): TamboThreadInputContextProps {\n const context = useContext(TamboThreadInputContext);\n if (!context) {\n throw new Error(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n }\n return context;\n}\n"]}
@@ -142,6 +142,70 @@ describe("useTamboThreadInput", () => {
142
142
  debug: undefined,
143
143
  });
144
144
  });
145
+ it("clears input immediately when submit starts", async () => {
146
+ let resolveSubmit;
147
+ const pendingSubmit = new Promise((resolve) => {
148
+ resolveSubmit = resolve;
149
+ });
150
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
151
+ const { result } = renderHook(() => useTamboThreadInput(), {
152
+ wrapper: createWrapper(),
153
+ });
154
+ act(() => {
155
+ result.current.setValue("message to send");
156
+ });
157
+ let inFlightSubmit;
158
+ await act(async () => {
159
+ inFlightSubmit = result.current.submit();
160
+ await Promise.resolve();
161
+ });
162
+ await waitFor(() => {
163
+ expect(result.current.value).toBe("");
164
+ });
165
+ act(() => {
166
+ resolveSubmit?.({ threadId: "thread_123" });
167
+ });
168
+ if (!inFlightSubmit) {
169
+ throw new Error("Submit promise was not created");
170
+ }
171
+ await act(async () => {
172
+ await inFlightSubmit;
173
+ });
174
+ });
175
+ it("preserves input edits made while a previous submit is pending", async () => {
176
+ let resolveSubmit;
177
+ const pendingSubmit = new Promise((resolve) => {
178
+ resolveSubmit = resolve;
179
+ });
180
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
181
+ const { result } = renderHook(() => useTamboThreadInput(), {
182
+ wrapper: createWrapper(),
183
+ });
184
+ act(() => {
185
+ result.current.setValue("first message");
186
+ });
187
+ let inFlightSubmit;
188
+ act(() => {
189
+ inFlightSubmit = result.current.submit();
190
+ });
191
+ await waitFor(() => {
192
+ expect(result.current.isPending).toBe(true);
193
+ });
194
+ act(() => {
195
+ result.current.setValue("second message");
196
+ });
197
+ expect(result.current.value).toBe("second message");
198
+ act(() => {
199
+ resolveSubmit?.({ threadId: "thread_123" });
200
+ });
201
+ if (!inFlightSubmit) {
202
+ throw new Error("Submit promise was not created");
203
+ }
204
+ await act(async () => {
205
+ await inFlightSubmit;
206
+ });
207
+ expect(result.current.value).toBe("second message");
208
+ });
145
209
  it("throws error when submitting empty message", async () => {
146
210
  const { result } = renderHook(() => useTamboThreadInput(), {
147
211
  wrapper: createWrapper(),
@@ -241,6 +305,45 @@ describe("useTamboThreadInput", () => {
241
305
  debug: undefined,
242
306
  });
243
307
  });
308
+ it("preserves images added while a previous submit is pending", async () => {
309
+ let resolveSubmit;
310
+ const pendingSubmit = new Promise((resolve) => {
311
+ resolveSubmit = resolve;
312
+ });
313
+ mockMutateAsync.mockImplementation(async () => await pendingSubmit);
314
+ const { result } = renderHook(() => useTamboThreadInput(), {
315
+ wrapper: createWrapper(),
316
+ });
317
+ await act(async () => {
318
+ await result.current.addImage(new File(["image-1"], "one.png", { type: "image/png" }));
319
+ });
320
+ act(() => {
321
+ result.current.setValue("Test message");
322
+ });
323
+ let inFlightSubmit;
324
+ act(() => {
325
+ inFlightSubmit = result.current.submit();
326
+ });
327
+ await waitFor(() => {
328
+ expect(result.current.isPending).toBe(true);
329
+ });
330
+ await act(async () => {
331
+ await result.current.addImage(new File(["image-2"], "two.png", { type: "image/png" }));
332
+ });
333
+ act(() => {
334
+ resolveSubmit?.({ threadId: "thread_123" });
335
+ });
336
+ if (!inFlightSubmit) {
337
+ throw new Error("Submit promise was not created");
338
+ }
339
+ await act(async () => {
340
+ await inFlightSubmit;
341
+ });
342
+ await waitFor(() => {
343
+ expect(result.current.images.length).toBe(1);
344
+ expect(result.current.images[0]?.name).toBe("two.png");
345
+ });
346
+ });
244
347
  });
245
348
  describe("Thread ID Management", () => {
246
349
  it("initializes with placeholder threadId for optimistic UI", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-v1-thread-input.test.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-thread-input.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport {\n TamboThreadInputProvider,\n useTamboThreadInput,\n} from \"../providers/tambo-v1-thread-input-provider\";\nimport { TamboStreamProvider } from \"../providers/tambo-v1-stream-context\";\nimport { useTamboSendMessage } from \"./use-tambo-v1-send-message\";\nimport type { StreamAction, StreamState } from \"../utils/event-accumulator\";\n\n// Mock useTamboSendMessage\njest.mock(\"./use-tambo-v1-send-message\", () => ({\n useTamboSendMessage: jest.fn(),\n}));\n\n// Mock useTamboQueryClient to avoid TamboClientProvider dependency\njest.mock(\"../../providers/tambo-client-provider\", () => ({\n useTamboQueryClient: jest.fn(() => new QueryClient()),\n useTamboClient: jest.fn(),\n}));\n\njest.mock(\"../providers/tambo-v1-provider\", () => {\n const actual = jest.requireActual(\"../providers/tambo-v1-provider\");\n return {\n ...actual,\n useTamboConfig: () => ({ userKey: undefined }),\n };\n});\n\njest.mock(\"./use-tambo-v1-auth-state\", () => ({\n useTamboAuthState: () => ({\n status: \"identified\",\n source: \"userKey\",\n }),\n}));\n\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: unknown) => void) | null,\n onerror: null as ((e: unknown) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n reader.onload?.({});\n }, 0);\n });\n\n return reader;\n};\n\nconst originalFileReader = (global as any).FileReader;\n\ndescribe(\"useTamboThreadInput\", () => {\n const mockMutateAsync = jest.fn();\n let queryClient: QueryClient;\n\n function createWrapper({ streamState }: { streamState?: StreamState } = {}) {\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return function Wrapper({ children }: { children: React.ReactNode }) {\n const streamProviderProps =\n streamState === undefined\n ? {}\n : { state: streamState, dispatch: noopDispatch };\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider {...streamProviderProps}>\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n }\n\n beforeEach(() => {\n jest.clearAllMocks();\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n mockMutateAsync.mockResolvedValue({ threadId: \"thread_123\" });\n jest.mocked(useTamboSendMessage).mockReturnValue({\n mutateAsync: mockMutateAsync,\n mutate: jest.fn(),\n isPending: false,\n isError: false,\n error: null,\n isSuccess: false,\n isIdle: true,\n isPaused: false,\n status: \"idle\",\n data: undefined,\n variables: undefined,\n failureCount: 0,\n failureReason: null,\n reset: jest.fn(),\n context: undefined,\n submittedAt: 0,\n } as any);\n });\n\n afterEach(() => {\n (global as any).FileReader = originalFileReader;\n });\n\n describe(\"State Management\", () => {\n it(\"initializes with empty value\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.value).toBe(\"\");\n });\n\n it(\"updates value via setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello world\");\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n\n it(\"supports functional updates for setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello\");\n });\n\n act(() => {\n result.current.setValue((prev) => `${prev} world`);\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n });\n\n describe(\"Submit Behavior\", () => {\n it(\"submits message and clears input on success\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n const response = await result.current.submit();\n expect(response.threadId).toBe(\"thread_123\");\n });\n\n // Input should be cleared\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n // Should have called mutateAsync with correct message format\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Test message\" }],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"throws error when submitting empty message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"throws error when submitting whitespace-only message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\" \");\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"passes debug option to mutation\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Debug message\");\n });\n\n await act(async () => {\n await result.current.submit({ debug: true });\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Debug message\" }],\n },\n userMessageText: \"Debug message\",\n debug: true,\n });\n });\n\n it(\"submits image-only messages as resource content\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"\",\n debug: undefined,\n });\n\n await waitFor(() => {\n expect(result.current.images).toEqual([]);\n });\n });\n\n it(\"includes both text and image resource content when both are present\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n { type: \"text\", text: \"Test message\" },\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n });\n\n describe(\"Thread ID Management\", () => {\n it(\"initializes with placeholder threadId for optimistic UI\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n // Default state has placeholder thread for optimistic UI\n expect(result.current.threadId).toBe(\"placeholder\");\n });\n\n it(\"uses currentThreadId from stream state\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n expect(result.current.threadId).toBe(\"thread_stream\");\n expect(\n jest.mocked(useTamboSendMessage).mock.calls.map((call) => call[0]),\n ).toContain(\"thread_stream\");\n });\n\n it(\"uses stream state threadId when submitting messages\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n // Verify sendMessage was called with the stream state's threadId\n expect(mockMutateAsync).toHaveBeenCalledWith(\n expect.objectContaining({\n message: expect.objectContaining({\n content: [{ type: \"text\", text: \"Test message\" }],\n }),\n }),\n );\n });\n });\n\n describe(\"Image State\", () => {\n it(\"initializes with empty images array\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.images).toEqual([]);\n });\n\n it(\"exposes image management functions\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\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(\"Error handling\", () => {\n it(\"throws error when used outside provider\", () => {\n // Suppress console.error for this test\n const consoleSpy = jest\n .spyOn(console, \"error\")\n .mockImplementation(() => {});\n\n expect(() => {\n renderHook(() => useTamboThreadInput());\n }).toThrow(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n\n consoleSpy.mockRestore();\n });\n });\n\n describe(\"Thread Isolation\", () => {\n /**\n * Creates a wrapper that allows dynamically changing the thread ID.\n * This simulates switching between threads in the UI.\n */\n function createDynamicThreadWrapper(initialThreadId: string) {\n let currentThreadId = initialThreadId;\n const listeners = new Set<() => void>();\n\n const setThreadId = (newThreadId: string) => {\n currentThreadId = newThreadId;\n listeners.forEach((listener) => listener());\n };\n\n const Wrapper = ({ children }: { children: React.ReactNode }) => {\n const [threadId, setThreadIdState] = React.useState(currentThreadId);\n\n React.useEffect(() => {\n const listener = () => setThreadIdState(currentThreadId);\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }, []);\n\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider\n state={{ threadMap: {}, currentThreadId: threadId }}\n dispatch={noopDispatch}\n >\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n\n return { Wrapper, setThreadId };\n }\n\n it(\"isolates pending state per thread when switching threads\", async () => {\n // Create a deferred promise so we can control when the mutation resolves\n let resolveThreadA: (value: { threadId: string }) => void;\n const threadAPromise = new Promise<{ threadId: string }>((resolve) => {\n resolveThreadA = resolve;\n });\n\n mockMutateAsync.mockImplementation(async () => await threadAPromise);\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Verify we're on thread A\n expect(result.current.threadId).toBe(\"thread_A\");\n\n // Set input and start submission on thread A\n act(() => {\n result.current.setValue(\"Message for thread A\");\n });\n\n // Start the submission (don't await - we want it pending)\n let submitPromise: Promise<{ threadId: string | undefined }>;\n act(() => {\n submitPromise = result.current.submit();\n });\n\n // Thread A should now be pending\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B while thread A is still pending\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n // Wait for the thread switch to take effect\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should NOT be pending - its mutation state is independent\n // (The mutationKey includes threadId, so each thread has its own state)\n expect(result.current.isPending).toBe(false);\n\n // Now resolve thread A's submission\n act(() => {\n resolveThreadA!({ threadId: \"thread_A\" });\n });\n\n // Wait for promise to complete\n await act(async () => {\n await submitPromise!;\n });\n\n // Thread B should still not be pending\n expect(result.current.isPending).toBe(false);\n });\n\n it(\"multiple threads that never submitted should not be pending when another thread is\", async () => {\n // Create a promise that never resolves to keep thread A pending\n const neverResolvingPromise = new Promise<{ threadId: string }>(() => {});\n\n mockMutateAsync.mockImplementation(\n async () => await neverResolvingPromise,\n );\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Start submission on thread A\n act(() => {\n result.current.setValue(\"Message A\");\n });\n\n act(() => {\n void result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B (which has never submitted anything)\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should not be pending - it has never submitted\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread C (also never submitted)\n act(() => {\n setThreadId(\"thread_C\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_C\");\n });\n\n // Thread C should also not be pending\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread D (also never submitted)\n act(() => {\n setThreadId(\"thread_D\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_D\");\n });\n\n // Thread D should also not be pending\n // This confirms the fix works - without mutationKey, ALL threads would be pending\n expect(result.current.isPending).toBe(false);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"use-tambo-v1-thread-input.test.js","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-thread-input.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport {\n TamboThreadInputProvider,\n useTamboThreadInput,\n} from \"../providers/tambo-v1-thread-input-provider\";\nimport { TamboStreamProvider } from \"../providers/tambo-v1-stream-context\";\nimport { useTamboSendMessage } from \"./use-tambo-v1-send-message\";\nimport type { StreamAction, StreamState } from \"../utils/event-accumulator\";\n\n// Mock useTamboSendMessage\njest.mock(\"./use-tambo-v1-send-message\", () => ({\n useTamboSendMessage: jest.fn(),\n}));\n\n// Mock useTamboQueryClient to avoid TamboClientProvider dependency\njest.mock(\"../../providers/tambo-client-provider\", () => ({\n useTamboQueryClient: jest.fn(() => new QueryClient()),\n useTamboClient: jest.fn(),\n}));\n\njest.mock(\"../providers/tambo-v1-provider\", () => {\n const actual = jest.requireActual(\"../providers/tambo-v1-provider\");\n return {\n ...actual,\n useTamboConfig: () => ({ userKey: undefined }),\n };\n});\n\njest.mock(\"./use-tambo-v1-auth-state\", () => ({\n useTamboAuthState: () => ({\n status: \"identified\",\n source: \"userKey\",\n }),\n}));\n\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: unknown) => void) | null,\n onerror: null as ((e: unknown) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n reader.onload?.({});\n }, 0);\n });\n\n return reader;\n};\n\nconst originalFileReader = (global as any).FileReader;\n\ndescribe(\"useTamboThreadInput\", () => {\n const mockMutateAsync = jest.fn();\n let queryClient: QueryClient;\n\n function createWrapper({ streamState }: { streamState?: StreamState } = {}) {\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return function Wrapper({ children }: { children: React.ReactNode }) {\n const streamProviderProps =\n streamState === undefined\n ? {}\n : { state: streamState, dispatch: noopDispatch };\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider {...streamProviderProps}>\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n }\n\n beforeEach(() => {\n jest.clearAllMocks();\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n mockMutateAsync.mockResolvedValue({ threadId: \"thread_123\" });\n jest.mocked(useTamboSendMessage).mockReturnValue({\n mutateAsync: mockMutateAsync,\n mutate: jest.fn(),\n isPending: false,\n isError: false,\n error: null,\n isSuccess: false,\n isIdle: true,\n isPaused: false,\n status: \"idle\",\n data: undefined,\n variables: undefined,\n failureCount: 0,\n failureReason: null,\n reset: jest.fn(),\n context: undefined,\n submittedAt: 0,\n } as any);\n });\n\n afterEach(() => {\n (global as any).FileReader = originalFileReader;\n });\n\n describe(\"State Management\", () => {\n it(\"initializes with empty value\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.value).toBe(\"\");\n });\n\n it(\"updates value via setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello world\");\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n\n it(\"supports functional updates for setValue\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Hello\");\n });\n\n act(() => {\n result.current.setValue((prev) => `${prev} world`);\n });\n\n expect(result.current.value).toBe(\"Hello world\");\n });\n });\n\n describe(\"Submit Behavior\", () => {\n it(\"submits message and clears input on success\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n const response = await result.current.submit();\n expect(response.threadId).toBe(\"thread_123\");\n });\n\n // Input should be cleared\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n // Should have called mutateAsync with correct message format\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Test message\" }],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"clears input immediately when submit starts\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"message to send\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n await act(async () => {\n inFlightSubmit = result.current.submit();\n await Promise.resolve();\n });\n\n await waitFor(() => {\n expect(result.current.value).toBe(\"\");\n });\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n });\n\n it(\"preserves input edits made while a previous submit is pending\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"first message\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n act(() => {\n inFlightSubmit = result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n act(() => {\n result.current.setValue(\"second message\");\n });\n expect(result.current.value).toBe(\"second message\");\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n\n expect(result.current.value).toBe(\"second message\");\n });\n\n it(\"throws error when submitting empty message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"throws error when submitting whitespace-only message\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\" \");\n });\n\n await expect(result.current.submit()).rejects.toThrow(\n \"Message cannot be empty\",\n );\n\n expect(mockMutateAsync).not.toHaveBeenCalled();\n });\n\n it(\"passes debug option to mutation\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Debug message\");\n });\n\n await act(async () => {\n await result.current.submit({ debug: true });\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: \"Debug message\" }],\n },\n userMessageText: \"Debug message\",\n debug: true,\n });\n });\n\n it(\"submits image-only messages as resource content\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"\",\n debug: undefined,\n });\n\n await waitFor(() => {\n expect(result.current.images).toEqual([]);\n });\n });\n\n it(\"includes both text and image resource content when both are present\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n );\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n expect(mockMutateAsync).toHaveBeenCalledWith({\n message: {\n role: \"user\",\n content: [\n { type: \"text\", text: \"Test message\" },\n {\n type: \"resource\",\n resource: {\n blob: \"mock-data\",\n mimeType: \"image/png\",\n name: \"photo.png\",\n },\n },\n ],\n },\n userMessageText: \"Test message\",\n debug: undefined,\n });\n });\n\n it(\"preserves images added while a previous submit is pending\", async () => {\n let resolveSubmit:\n | ((value: { threadId: string | undefined }) => void)\n | undefined;\n const pendingSubmit = new Promise<{ threadId: string | undefined }>(\n (resolve) => {\n resolveSubmit = resolve;\n },\n );\n mockMutateAsync.mockImplementation(async () => await pendingSubmit);\n\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image-1\"], \"one.png\", { type: \"image/png\" }),\n );\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n let inFlightSubmit: Promise<{ threadId: string | undefined }> | undefined;\n act(() => {\n inFlightSubmit = result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n await act(async () => {\n await result.current.addImage(\n new File([\"image-2\"], \"two.png\", { type: \"image/png\" }),\n );\n });\n\n act(() => {\n resolveSubmit?.({ threadId: \"thread_123\" });\n });\n\n if (!inFlightSubmit) {\n throw new Error(\"Submit promise was not created\");\n }\n await act(async () => {\n await inFlightSubmit;\n });\n\n await waitFor(() => {\n expect(result.current.images.length).toBe(1);\n expect(result.current.images[0]?.name).toBe(\"two.png\");\n });\n });\n });\n\n describe(\"Thread ID Management\", () => {\n it(\"initializes with placeholder threadId for optimistic UI\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n // Default state has placeholder thread for optimistic UI\n expect(result.current.threadId).toBe(\"placeholder\");\n });\n\n it(\"uses currentThreadId from stream state\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n expect(result.current.threadId).toBe(\"thread_stream\");\n expect(\n jest.mocked(useTamboSendMessage).mock.calls.map((call) => call[0]),\n ).toContain(\"thread_stream\");\n });\n\n it(\"uses stream state threadId when submitting messages\", async () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper({\n streamState: { threadMap: {}, currentThreadId: \"thread_stream\" },\n }),\n });\n\n act(() => {\n result.current.setValue(\"Test message\");\n });\n\n await act(async () => {\n await result.current.submit();\n });\n\n // Verify sendMessage was called with the stream state's threadId\n expect(mockMutateAsync).toHaveBeenCalledWith(\n expect.objectContaining({\n message: expect.objectContaining({\n content: [{ type: \"text\", text: \"Test message\" }],\n }),\n }),\n );\n });\n });\n\n describe(\"Image State\", () => {\n it(\"initializes with empty images array\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\n\n expect(result.current.images).toEqual([]);\n });\n\n it(\"exposes image management functions\", () => {\n const { result } = renderHook(() => useTamboThreadInput(), {\n wrapper: createWrapper(),\n });\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(\"Error handling\", () => {\n it(\"throws error when used outside provider\", () => {\n // Suppress console.error for this test\n const consoleSpy = jest\n .spyOn(console, \"error\")\n .mockImplementation(() => {});\n\n expect(() => {\n renderHook(() => useTamboThreadInput());\n }).toThrow(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n\n consoleSpy.mockRestore();\n });\n });\n\n describe(\"Thread Isolation\", () => {\n /**\n * Creates a wrapper that allows dynamically changing the thread ID.\n * This simulates switching between threads in the UI.\n */\n function createDynamicThreadWrapper(initialThreadId: string) {\n let currentThreadId = initialThreadId;\n const listeners = new Set<() => void>();\n\n const setThreadId = (newThreadId: string) => {\n currentThreadId = newThreadId;\n listeners.forEach((listener) => listener());\n };\n\n const Wrapper = ({ children }: { children: React.ReactNode }) => {\n const [threadId, setThreadIdState] = React.useState(currentThreadId);\n\n React.useEffect(() => {\n const listener = () => setThreadIdState(currentThreadId);\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }, []);\n\n const noopDispatch: React.Dispatch<StreamAction> = () => {};\n\n return (\n <QueryClientProvider client={queryClient}>\n <TamboStreamProvider\n state={{ threadMap: {}, currentThreadId: threadId }}\n dispatch={noopDispatch}\n >\n <TamboThreadInputProvider>{children}</TamboThreadInputProvider>\n </TamboStreamProvider>\n </QueryClientProvider>\n );\n };\n\n return { Wrapper, setThreadId };\n }\n\n it(\"isolates pending state per thread when switching threads\", async () => {\n // Create a deferred promise so we can control when the mutation resolves\n let resolveThreadA: (value: { threadId: string }) => void;\n const threadAPromise = new Promise<{ threadId: string }>((resolve) => {\n resolveThreadA = resolve;\n });\n\n mockMutateAsync.mockImplementation(async () => await threadAPromise);\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Verify we're on thread A\n expect(result.current.threadId).toBe(\"thread_A\");\n\n // Set input and start submission on thread A\n act(() => {\n result.current.setValue(\"Message for thread A\");\n });\n\n // Start the submission (don't await - we want it pending)\n let submitPromise: Promise<{ threadId: string | undefined }>;\n act(() => {\n submitPromise = result.current.submit();\n });\n\n // Thread A should now be pending\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B while thread A is still pending\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n // Wait for the thread switch to take effect\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should NOT be pending - its mutation state is independent\n // (The mutationKey includes threadId, so each thread has its own state)\n expect(result.current.isPending).toBe(false);\n\n // Now resolve thread A's submission\n act(() => {\n resolveThreadA!({ threadId: \"thread_A\" });\n });\n\n // Wait for promise to complete\n await act(async () => {\n await submitPromise!;\n });\n\n // Thread B should still not be pending\n expect(result.current.isPending).toBe(false);\n });\n\n it(\"multiple threads that never submitted should not be pending when another thread is\", async () => {\n // Create a promise that never resolves to keep thread A pending\n const neverResolvingPromise = new Promise<{ threadId: string }>(() => {});\n\n mockMutateAsync.mockImplementation(\n async () => await neverResolvingPromise,\n );\n\n const { Wrapper, setThreadId } = createDynamicThreadWrapper(\"thread_A\");\n const { result, rerender } = renderHook(() => useTamboThreadInput(), {\n wrapper: Wrapper,\n });\n\n // Start submission on thread A\n act(() => {\n result.current.setValue(\"Message A\");\n });\n\n act(() => {\n void result.current.submit();\n });\n\n await waitFor(() => {\n expect(result.current.isPending).toBe(true);\n });\n\n // Switch to thread B (which has never submitted anything)\n act(() => {\n setThreadId(\"thread_B\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_B\");\n });\n\n // Thread B should not be pending - it has never submitted\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread C (also never submitted)\n act(() => {\n setThreadId(\"thread_C\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_C\");\n });\n\n // Thread C should also not be pending\n expect(result.current.isPending).toBe(false);\n\n // Switch to thread D (also never submitted)\n act(() => {\n setThreadId(\"thread_D\");\n });\n rerender();\n\n await waitFor(() => {\n expect(result.current.threadId).toBe(\"thread_D\");\n });\n\n // Thread D should also not be pending\n // This confirms the fix works - without mutationKey, ALL threads would be pending\n expect(result.current.isPending).toBe(false);\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-v1-thread-input-provider.d.ts","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAKZ,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AASvD,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAkCX;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,sBAAsB,CACpB;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAChC,KAAK,EACL,aAAa,GAAG,SAAS,CAC1B,EACD,QAAQ,GAAG,aAAa,CACzB;IACC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvD;;;;OAIG;IACH,MAAM,EAAE,CACN,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAE/C,8BAA8B;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,0BAA0B;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,4BAA4B;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,8BAA8B;IAC9B,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,yDAExB,CAAC;AAEb;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBAgGvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE"}
1
+ {"version":3,"file":"tambo-v1-thread-input-provider.d.ts","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAKZ,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AASvD,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAkCX;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,sBAAsB,CACpB;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAChC,KAAK,EACL,aAAa,GAAG,SAAS,CAC1B,EACD,QAAQ,GAAG,aAAa,CACzB;IACC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvD;;;;OAIG;IACH,MAAM,EAAE,CACN,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAE/C,8BAA8B;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,0BAA0B;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,4BAA4B;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,8BAA8B;IAC9B,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,yDAExB,CAAC;AAEb;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBAkHvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE"}
@@ -79,6 +79,8 @@ export function TamboThreadInputProvider({ children }) {
79
79
  "Ensure a valid userKey or userToken is provided.");
80
80
  }
81
81
  const trimmedValue = inputValue.trim();
82
+ const valueAtSubmitTime = inputValue;
83
+ const imageIdsAtSubmitTime = imageState.images.map((image) => image.id);
82
84
  // Check if we have content to send
83
85
  if (!trimmedValue && imageState.images.length === 0) {
84
86
  throw new Error(INPUT_ERROR_MESSAGES.EMPTY);
@@ -90,23 +92,37 @@ export function TamboThreadInputProvider({ children }) {
90
92
  for (const image of imageState.images) {
91
93
  content.push(stagedImageToResourceContent(image));
92
94
  }
93
- const result = await sendMessage.mutateAsync({
94
- message: {
95
- role: "user",
96
- content,
97
- },
98
- userMessageText: trimmedValue, // Pass text for optimistic display
99
- debug: options?.debug,
100
- toolChoice: options?.toolChoice,
101
- });
102
- // Clear input and images after successful submission
95
+ // Optimistically clear submitted text so users can start typing the next
96
+ // message immediately while the current request is pending.
103
97
  setInputValue("");
104
- imageState.clearImages();
105
- // Update stream context's currentThreadId if a new thread was created
106
- if (result.threadId && isNewThread) {
107
- dispatch({ type: "SET_CURRENT_THREAD", threadId: result.threadId });
98
+ try {
99
+ const result = await sendMessage.mutateAsync({
100
+ message: {
101
+ role: "user",
102
+ content,
103
+ },
104
+ userMessageText: trimmedValue, // Pass text for optimistic display
105
+ debug: options?.debug,
106
+ toolChoice: options?.toolChoice,
107
+ });
108
+ // Clear only submitted images so images added while pending are kept.
109
+ if (imageIdsAtSubmitTime.length > 0) {
110
+ imageIdsAtSubmitTime.forEach((id) => {
111
+ imageState.removeImage(id);
112
+ });
113
+ }
114
+ // Update stream context's currentThreadId if a new thread was created
115
+ if (result.threadId && isNewThread) {
116
+ dispatch({ type: "SET_CURRENT_THREAD", threadId: result.threadId });
117
+ }
118
+ return result;
119
+ }
120
+ catch (error) {
121
+ // If the user has not started typing a new message, restore the
122
+ // submitted value so failed sends don't silently lose input.
123
+ setInputValue((currentValue) => currentValue.length > 0 ? currentValue : valueAtSubmitTime);
124
+ throw error;
108
125
  }
109
- return result;
110
126
  },
111
127
  // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).
112
128
  [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified]);
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-v1-thread-input-provider.js","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * TamboThreadInputProvider - Shared Thread Input Context\n *\n * Provides shared input state across all components, enabling features like\n * suggestions to update the input field directly.\n *\n * This mirrors the TamboThreadInputProvider pattern from the legacy beta SDK.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useState,\n type PropsWithChildren,\n} from \"react\";\nimport {\n useMessageImages,\n type StagedImage,\n} from \"../../hooks/use-message-images\";\nimport {\n useTamboMutation,\n type UseTamboMutationResult,\n} from \"../../hooks/react-query-hooks\";\nimport { useTamboSendMessage } from \"../hooks/use-tambo-v1-send-message\";\nimport type { InputMessage } from \"../types/message\";\nimport type { ToolChoice } from \"../types/tool-choice\";\nimport { isPlaceholderThreadId } from \"../utils/event-accumulator\";\nimport { useTamboAuthState } from \"../hooks/use-tambo-v1-auth-state\";\nimport { useStreamDispatch, useStreamState } from \"./tambo-v1-stream-context\";\n\n// Error messages for various input-related error scenarios.\n// TODO: Reintroduce explicit `NETWORK` and `SERVER` keys once `submit()` maps\n// failures into a small, stable set of user-facing error codes (at minimum:\n// connectivity failures vs non-2xx responses).\nexport const INPUT_ERROR_MESSAGES = {\n EMPTY: \"Message cannot be empty\",\n VALIDATION: \"Invalid message format\",\n} as const;\n\nfunction stagedImageToResourceContent(\n image: StagedImage,\n): InputMessage[\"content\"][number] {\n if (!image.dataUrl.startsWith(\"data:\")) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const commaIndex = image.dataUrl.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const header = image.dataUrl.slice(\"data:\".length, commaIndex);\n const [mimeType, ...params] = header.split(\";\");\n const isBase64 = params.includes(\"base64\");\n\n if (mimeType !== image.type || !isBase64) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n return {\n type: \"resource\",\n resource: {\n name: image.name,\n mimeType: image.type,\n // Shared.Resource.blob expects base64-encoded data; this is the base64\n // payload from the `data:` URL.\n blob: image.dataUrl.slice(commaIndex + 1),\n },\n };\n}\n\n/**\n * Options for submitting a message\n */\nexport interface SubmitOptions {\n /**\n * Enable debug logging for the stream\n */\n debug?: boolean;\n\n /**\n * How the model should use tools. Defaults to \"auto\".\n * - \"auto\": Model decides whether to use tools\n * - \"required\": Model must use at least one tool\n * - \"none\": Model cannot use tools\n * - { name: \"toolName\" }: Model must use the specified tool\n */\n toolChoice?: ToolChoice;\n}\n\n/**\n * Context props for thread input state\n */\nexport interface TamboThreadInputContextProps extends Omit<\n UseTamboMutationResult<\n { threadId: string | undefined },\n Error,\n SubmitOptions | undefined\n >,\n \"mutate\" | \"mutateAsync\"\n> {\n /** Current value of the input field */\n value: string;\n\n /**\n * Function to update the input value\n * @param value - New value for the input field\n */\n setValue: React.Dispatch<React.SetStateAction<string>>;\n\n /**\n * Function to submit the current input value\n * @param options - Submission options\n * @returns Promise with the threadId\n */\n submit: (\n options?: SubmitOptions,\n ) => Promise<{ threadId: string | undefined }>;\n\n /** Currently staged images */\n images: StagedImage[];\n\n /** Add a single image */\n addImage: (file: File) => Promise<void>;\n\n /** Add multiple images */\n addImages: (files: File[]) => Promise<void>;\n\n /** Remove an image by id */\n removeImage: (id: string) => void;\n\n /** Clear all staged images */\n clearImages: () => void;\n\n /** Current thread ID being used for input (from stream state) */\n threadId: string | undefined;\n\n /** Whether the input should be disabled (pending submission or not authenticated) */\n isDisabled: boolean;\n}\n\n/**\n * Context for thread input state.\n * @internal\n */\nexport const TamboThreadInputContext = createContext<\n TamboThreadInputContextProps | undefined\n>(undefined);\n\n/**\n * Provider that manages shared thread input state across all components.\n *\n * This ensures that useTamboThreadInput, useTamboSuggestions, and other components\n * all share the same input state.\n * @param props - Provider props\n * @param props.children - Child components\n * @returns Thread input context provider\n */\nexport function TamboThreadInputProvider({ children }: PropsWithChildren) {\n const [inputValue, setInputValue] = useState(\"\");\n const imageState = useMessageImages();\n const streamState = useStreamState();\n const dispatch = useStreamDispatch();\n const authState = useTamboAuthState();\n\n // Use the current thread from stream state directly\n // Placeholder ID indicates a new thread should be created\n const currentThreadId = streamState.currentThreadId ?? undefined;\n const isNewThread = isPlaceholderThreadId(currentThreadId);\n const sendMessage = useTamboSendMessage(currentThreadId);\n\n const isIdentified = authState.status === \"identified\";\n\n const submitFn = useCallback(\n async (\n options?: SubmitOptions,\n ): Promise<{ threadId: string | undefined }> => {\n if (!isIdentified) {\n throw new Error(\n \"Cannot submit: authentication is not ready. \" +\n \"Ensure a valid userKey or userToken is provided.\",\n );\n }\n\n const trimmedValue = inputValue.trim();\n\n // Check if we have content to send\n if (!trimmedValue && imageState.images.length === 0) {\n throw new Error(INPUT_ERROR_MESSAGES.EMPTY);\n }\n\n const content: InputMessage[\"content\"] = [];\n\n if (trimmedValue) {\n content.push({ type: \"text\", text: trimmedValue });\n }\n\n for (const image of imageState.images) {\n content.push(stagedImageToResourceContent(image));\n }\n\n const result = await sendMessage.mutateAsync({\n message: {\n role: \"user\",\n content,\n },\n userMessageText: trimmedValue, // Pass text for optimistic display\n debug: options?.debug,\n toolChoice: options?.toolChoice,\n });\n\n // Clear input and images after successful submission\n setInputValue(\"\");\n imageState.clearImages();\n\n // Update stream context's currentThreadId if a new thread was created\n if (result.threadId && isNewThread) {\n dispatch({ type: \"SET_CURRENT_THREAD\", threadId: result.threadId });\n }\n\n return result;\n },\n // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).\n [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified],\n );\n\n const {\n mutateAsync: submitAsync,\n mutate: _unusedSubmit,\n ...mutationState\n } = useTamboMutation({\n mutationKey: [\"v1-thread-input\", currentThreadId],\n mutationFn: submitFn,\n });\n\n const contextValue: TamboThreadInputContextProps = {\n ...mutationState,\n value: inputValue,\n setValue: setInputValue,\n submit: submitAsync,\n images: imageState.images,\n addImage: imageState.addImage,\n addImages: imageState.addImages,\n removeImage: imageState.removeImage,\n clearImages: imageState.clearImages,\n threadId: currentThreadId,\n isDisabled: mutationState.isPending || !isIdentified,\n };\n\n return (\n <TamboThreadInputContext.Provider value={contextValue}>\n {children}\n </TamboThreadInputContext.Provider>\n );\n}\n\n/**\n * Hook to access the shared thread input state.\n *\n * All components using this hook share the same input state, enabling\n * features like suggestions to update the input field directly.\n * @returns The shared thread input context\n * @throws {Error} If used outside TamboThreadInputProvider\n * @example\n * ```tsx\n * function ChatInput() {\n * const { value, setValue, submit, isPending } = useTamboThreadInput();\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); submit(); }}>\n * <input\n * value={value}\n * onChange={(e) => setValue(e.target.value)}\n * disabled={isPending}\n * />\n * <button type=\"submit\" disabled={isPending}>Send</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useTamboThreadInput(): TamboThreadInputContextProps {\n const context = useContext(TamboThreadInputContext);\n if (!context) {\n throw new Error(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n }\n return context;\n}\n"]}
1
+ {"version":3,"file":"tambo-v1-thread-input-provider.js","sourceRoot":"","sources":["../../../src/v1/providers/tambo-v1-thread-input-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * TamboThreadInputProvider - Shared Thread Input Context\n *\n * Provides shared input state across all components, enabling features like\n * suggestions to update the input field directly.\n *\n * This mirrors the TamboThreadInputProvider pattern from the legacy beta SDK.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useState,\n type PropsWithChildren,\n} from \"react\";\nimport {\n useMessageImages,\n type StagedImage,\n} from \"../../hooks/use-message-images\";\nimport {\n useTamboMutation,\n type UseTamboMutationResult,\n} from \"../../hooks/react-query-hooks\";\nimport { useTamboSendMessage } from \"../hooks/use-tambo-v1-send-message\";\nimport type { InputMessage } from \"../types/message\";\nimport type { ToolChoice } from \"../types/tool-choice\";\nimport { isPlaceholderThreadId } from \"../utils/event-accumulator\";\nimport { useTamboAuthState } from \"../hooks/use-tambo-v1-auth-state\";\nimport { useStreamDispatch, useStreamState } from \"./tambo-v1-stream-context\";\n\n// Error messages for various input-related error scenarios.\n// TODO: Reintroduce explicit `NETWORK` and `SERVER` keys once `submit()` maps\n// failures into a small, stable set of user-facing error codes (at minimum:\n// connectivity failures vs non-2xx responses).\nexport const INPUT_ERROR_MESSAGES = {\n EMPTY: \"Message cannot be empty\",\n VALIDATION: \"Invalid message format\",\n} as const;\n\nfunction stagedImageToResourceContent(\n image: StagedImage,\n): InputMessage[\"content\"][number] {\n if (!image.dataUrl.startsWith(\"data:\")) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const commaIndex = image.dataUrl.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n const header = image.dataUrl.slice(\"data:\".length, commaIndex);\n const [mimeType, ...params] = header.split(\";\");\n const isBase64 = params.includes(\"base64\");\n\n if (mimeType !== image.type || !isBase64) {\n throw new Error(INPUT_ERROR_MESSAGES.VALIDATION);\n }\n\n return {\n type: \"resource\",\n resource: {\n name: image.name,\n mimeType: image.type,\n // Shared.Resource.blob expects base64-encoded data; this is the base64\n // payload from the `data:` URL.\n blob: image.dataUrl.slice(commaIndex + 1),\n },\n };\n}\n\n/**\n * Options for submitting a message\n */\nexport interface SubmitOptions {\n /**\n * Enable debug logging for the stream\n */\n debug?: boolean;\n\n /**\n * How the model should use tools. Defaults to \"auto\".\n * - \"auto\": Model decides whether to use tools\n * - \"required\": Model must use at least one tool\n * - \"none\": Model cannot use tools\n * - { name: \"toolName\" }: Model must use the specified tool\n */\n toolChoice?: ToolChoice;\n}\n\n/**\n * Context props for thread input state\n */\nexport interface TamboThreadInputContextProps extends Omit<\n UseTamboMutationResult<\n { threadId: string | undefined },\n Error,\n SubmitOptions | undefined\n >,\n \"mutate\" | \"mutateAsync\"\n> {\n /** Current value of the input field */\n value: string;\n\n /**\n * Function to update the input value\n * @param value - New value for the input field\n */\n setValue: React.Dispatch<React.SetStateAction<string>>;\n\n /**\n * Function to submit the current input value\n * @param options - Submission options\n * @returns Promise with the threadId\n */\n submit: (\n options?: SubmitOptions,\n ) => Promise<{ threadId: string | undefined }>;\n\n /** Currently staged images */\n images: StagedImage[];\n\n /** Add a single image */\n addImage: (file: File) => Promise<void>;\n\n /** Add multiple images */\n addImages: (files: File[]) => Promise<void>;\n\n /** Remove an image by id */\n removeImage: (id: string) => void;\n\n /** Clear all staged images */\n clearImages: () => void;\n\n /** Current thread ID being used for input (from stream state) */\n threadId: string | undefined;\n\n /** Whether the input should be disabled (pending submission or not authenticated) */\n isDisabled: boolean;\n}\n\n/**\n * Context for thread input state.\n * @internal\n */\nexport const TamboThreadInputContext = createContext<\n TamboThreadInputContextProps | undefined\n>(undefined);\n\n/**\n * Provider that manages shared thread input state across all components.\n *\n * This ensures that useTamboThreadInput, useTamboSuggestions, and other components\n * all share the same input state.\n * @param props - Provider props\n * @param props.children - Child components\n * @returns Thread input context provider\n */\nexport function TamboThreadInputProvider({ children }: PropsWithChildren) {\n const [inputValue, setInputValue] = useState(\"\");\n const imageState = useMessageImages();\n const streamState = useStreamState();\n const dispatch = useStreamDispatch();\n const authState = useTamboAuthState();\n\n // Use the current thread from stream state directly\n // Placeholder ID indicates a new thread should be created\n const currentThreadId = streamState.currentThreadId ?? undefined;\n const isNewThread = isPlaceholderThreadId(currentThreadId);\n const sendMessage = useTamboSendMessage(currentThreadId);\n\n const isIdentified = authState.status === \"identified\";\n\n const submitFn = useCallback(\n async (\n options?: SubmitOptions,\n ): Promise<{ threadId: string | undefined }> => {\n if (!isIdentified) {\n throw new Error(\n \"Cannot submit: authentication is not ready. \" +\n \"Ensure a valid userKey or userToken is provided.\",\n );\n }\n\n const trimmedValue = inputValue.trim();\n const valueAtSubmitTime = inputValue;\n const imageIdsAtSubmitTime = imageState.images.map((image) => image.id);\n\n // Check if we have content to send\n if (!trimmedValue && imageState.images.length === 0) {\n throw new Error(INPUT_ERROR_MESSAGES.EMPTY);\n }\n\n const content: InputMessage[\"content\"] = [];\n\n if (trimmedValue) {\n content.push({ type: \"text\", text: trimmedValue });\n }\n\n for (const image of imageState.images) {\n content.push(stagedImageToResourceContent(image));\n }\n\n // Optimistically clear submitted text so users can start typing the next\n // message immediately while the current request is pending.\n setInputValue(\"\");\n\n try {\n const result = await sendMessage.mutateAsync({\n message: {\n role: \"user\",\n content,\n },\n userMessageText: trimmedValue, // Pass text for optimistic display\n debug: options?.debug,\n toolChoice: options?.toolChoice,\n });\n\n // Clear only submitted images so images added while pending are kept.\n if (imageIdsAtSubmitTime.length > 0) {\n imageIdsAtSubmitTime.forEach((id) => {\n imageState.removeImage(id);\n });\n }\n\n // Update stream context's currentThreadId if a new thread was created\n if (result.threadId && isNewThread) {\n dispatch({ type: \"SET_CURRENT_THREAD\", threadId: result.threadId });\n }\n\n return result;\n } catch (error) {\n // If the user has not started typing a new message, restore the\n // submitted value so failed sends don't silently lose input.\n setInputValue((currentValue) =>\n currentValue.length > 0 ? currentValue : valueAtSubmitTime,\n );\n throw error;\n }\n },\n // `stagedImageToResourceContent` is a pure module-level helper (not a hook value).\n [inputValue, imageState, sendMessage, isNewThread, dispatch, isIdentified],\n );\n\n const {\n mutateAsync: submitAsync,\n mutate: _unusedSubmit,\n ...mutationState\n } = useTamboMutation({\n mutationKey: [\"v1-thread-input\", currentThreadId],\n mutationFn: submitFn,\n });\n\n const contextValue: TamboThreadInputContextProps = {\n ...mutationState,\n value: inputValue,\n setValue: setInputValue,\n submit: submitAsync,\n images: imageState.images,\n addImage: imageState.addImage,\n addImages: imageState.addImages,\n removeImage: imageState.removeImage,\n clearImages: imageState.clearImages,\n threadId: currentThreadId,\n isDisabled: mutationState.isPending || !isIdentified,\n };\n\n return (\n <TamboThreadInputContext.Provider value={contextValue}>\n {children}\n </TamboThreadInputContext.Provider>\n );\n}\n\n/**\n * Hook to access the shared thread input state.\n *\n * All components using this hook share the same input state, enabling\n * features like suggestions to update the input field directly.\n * @returns The shared thread input context\n * @throws {Error} If used outside TamboThreadInputProvider\n * @example\n * ```tsx\n * function ChatInput() {\n * const { value, setValue, submit, isPending } = useTamboThreadInput();\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); submit(); }}>\n * <input\n * value={value}\n * onChange={(e) => setValue(e.target.value)}\n * disabled={isPending}\n * />\n * <button type=\"submit\" disabled={isPending}>Send</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useTamboThreadInput(): TamboThreadInputContextProps {\n const context = useContext(TamboThreadInputContext);\n if (!context) {\n throw new Error(\n \"useTamboThreadInput must be used within TamboThreadInputProvider\",\n );\n }\n return context;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tambo-ai/react",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "React client package for Tambo AI!",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,13 +14,13 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "exports": {
16
16
  ".": {
17
- "development": "./src/index.ts",
17
+ "@tambo-ai/source": "./src/index.ts",
18
18
  "import": "./esm/index.js",
19
19
  "require": "./dist/index.js",
20
20
  "types": "./dist/index.d.ts"
21
21
  },
22
22
  "./mcp": {
23
- "development": "./src/mcp/index.ts",
23
+ "@tambo-ai/source": "./src/mcp/index.ts",
24
24
  "import": "./esm/mcp/index.js",
25
25
  "require": "./dist/mcp/index.js",
26
26
  "types": "./dist/mcp/index.d.ts"
@@ -63,9 +63,7 @@
63
63
  "peerDependencies": {
64
64
  "@modelcontextprotocol/sdk": "^1.26.0",
65
65
  "@types/react": "^18.0.0 || ^19.0.0",
66
- "@types/react-dom": "^18.0.0 || ^19.0.0",
67
66
  "react": "^18.0.0 || ^19.0.0",
68
- "react-dom": "^18.0.0 || ^19.0.0",
69
67
  "zod": "^3.25.76 || ^4",
70
68
  "zod-to-json-schema": "^3.25.1"
71
69
  },