@tambo-ai/react 0.69.0 → 0.70.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 +7 -7
- package/dist/hooks/use-tambo-threads.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -5
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +4 -5
- package/dist/mcp/index.js.map +1 -1
- package/dist/model/component-metadata.d.ts +88 -241
- package/dist/model/component-metadata.d.ts.map +1 -1
- package/dist/model/component-metadata.js.map +1 -1
- package/dist/model/mcp-server-info.d.ts +3 -3
- package/dist/model/mcp-server-info.js.map +1 -1
- package/dist/providers/hooks/use-tambo-session-token.test.js.map +1 -1
- package/dist/providers/tambo-component-provider.d.ts +2 -2
- package/dist/providers/tambo-component-provider.d.ts.map +1 -1
- package/dist/providers/tambo-component-provider.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.d.ts +1 -1
- package/dist/providers/tambo-registry-provider.d.ts +4 -4
- package/dist/providers/tambo-registry-provider.d.ts.map +1 -1
- package/dist/providers/tambo-registry-provider.js +11 -8
- package/dist/providers/tambo-registry-provider.js.map +1 -1
- package/dist/providers/tambo-registry-provider.test.js +31 -0
- package/dist/providers/tambo-registry-provider.test.js.map +1 -1
- package/dist/providers/tambo-registry-schema-compat.test.js +42 -52
- package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
- package/dist/providers/tambo-stubs.d.ts +2 -2
- package/dist/providers/tambo-stubs.d.ts.map +1 -1
- package/dist/providers/tambo-stubs.js.map +1 -1
- package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-provider.js +110 -142
- package/dist/providers/tambo-thread-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider.test.js +362 -445
- package/dist/providers/tambo-thread-provider.test.js.map +1 -1
- package/dist/schema/index.d.ts +1 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -5
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/schema.d.ts +7 -24
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +34 -105
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/schema.test.js +26 -124
- package/dist/schema/schema.test.js.map +1 -1
- package/dist/testing/tools.d.ts +2 -12
- package/dist/testing/tools.d.ts.map +1 -1
- package/dist/testing/tools.js +1 -20
- package/dist/testing/tools.js.map +1 -1
- package/dist/testing/types.d.ts +2 -2
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/types.js.map +1 -1
- package/dist/util/registry-validators.d.ts +2 -2
- package/dist/util/registry-validators.d.ts.map +1 -1
- package/dist/util/registry-validators.js +37 -17
- package/dist/util/registry-validators.js.map +1 -1
- package/dist/util/registry-validators.test.js +64 -25
- package/dist/util/registry-validators.test.js.map +1 -1
- package/dist/util/registry.d.ts +4 -10
- package/dist/util/registry.d.ts.map +1 -1
- package/dist/util/registry.js +6 -22
- package/dist/util/registry.js.map +1 -1
- package/dist/util/registry.test.js +1 -47
- package/dist/util/registry.test.js.map +1 -1
- package/dist/util/tool-caller.d.ts +2 -2
- package/dist/util/tool-caller.d.ts.map +1 -1
- package/dist/util/tool-caller.js +5 -12
- package/dist/util/tool-caller.js.map +1 -1
- package/dist/v1/index.d.ts +35 -0
- package/dist/v1/index.d.ts.map +1 -0
- package/dist/v1/index.js +47 -0
- package/dist/v1/index.js.map +1 -0
- package/dist/v1/types/component.d.ts +47 -0
- package/dist/v1/types/component.d.ts.map +1 -0
- package/dist/v1/types/component.js +11 -0
- package/dist/v1/types/component.js.map +1 -0
- package/dist/v1/types/event.d.ts +63 -0
- package/dist/v1/types/event.d.ts.map +1 -0
- package/dist/v1/types/event.js +9 -0
- package/dist/v1/types/event.js.map +1 -0
- package/dist/v1/types/message.d.ts +39 -0
- package/dist/v1/types/message.d.ts.map +1 -0
- package/dist/v1/types/message.js +10 -0
- package/dist/v1/types/message.js.map +1 -0
- package/dist/v1/types/thread.d.ts +54 -0
- package/dist/v1/types/thread.d.ts.map +1 -0
- package/dist/v1/types/thread.js +9 -0
- package/dist/v1/types/thread.js.map +1 -0
- package/dist/v1/types/tool.d.ts +52 -0
- package/dist/v1/types/tool.d.ts.map +1 -0
- package/dist/v1/types/tool.js +11 -0
- package/dist/v1/types/tool.js.map +1 -0
- package/esm/hooks/use-tambo-threads.test.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js.map +1 -1
- package/esm/mcp/index.d.ts +4 -5
- package/esm/mcp/index.d.ts.map +1 -1
- package/esm/mcp/index.js +4 -5
- package/esm/mcp/index.js.map +1 -1
- package/esm/model/component-metadata.d.ts +88 -241
- package/esm/model/component-metadata.d.ts.map +1 -1
- package/esm/model/component-metadata.js.map +1 -1
- package/esm/model/mcp-server-info.d.ts +3 -3
- package/esm/model/mcp-server-info.js.map +1 -1
- package/esm/providers/hooks/use-tambo-session-token.test.js.map +1 -1
- package/esm/providers/tambo-component-provider.d.ts +2 -2
- package/esm/providers/tambo-component-provider.d.ts.map +1 -1
- package/esm/providers/tambo-component-provider.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.d.ts +1 -1
- package/esm/providers/tambo-registry-provider.d.ts +4 -4
- package/esm/providers/tambo-registry-provider.d.ts.map +1 -1
- package/esm/providers/tambo-registry-provider.js +11 -8
- package/esm/providers/tambo-registry-provider.js.map +1 -1
- package/esm/providers/tambo-registry-provider.test.js +31 -0
- package/esm/providers/tambo-registry-provider.test.js.map +1 -1
- package/esm/providers/tambo-registry-schema-compat.test.js +42 -52
- package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
- package/esm/providers/tambo-stubs.d.ts +2 -2
- package/esm/providers/tambo-stubs.d.ts.map +1 -1
- package/esm/providers/tambo-stubs.js.map +1 -1
- package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-provider.js +110 -142
- package/esm/providers/tambo-thread-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider.test.js +329 -445
- package/esm/providers/tambo-thread-provider.test.js.map +1 -1
- package/esm/schema/index.d.ts +1 -2
- package/esm/schema/index.d.ts.map +1 -1
- package/esm/schema/index.js +1 -2
- package/esm/schema/index.js.map +1 -1
- package/esm/schema/schema.d.ts +7 -24
- package/esm/schema/schema.d.ts.map +1 -1
- package/esm/schema/schema.js +34 -103
- package/esm/schema/schema.js.map +1 -1
- package/esm/schema/schema.test.js +27 -125
- package/esm/schema/schema.test.js.map +1 -1
- package/esm/testing/tools.d.ts +2 -12
- package/esm/testing/tools.d.ts.map +1 -1
- package/esm/testing/tools.js +2 -20
- package/esm/testing/tools.js.map +1 -1
- package/esm/testing/types.d.ts +2 -2
- package/esm/testing/types.d.ts.map +1 -1
- package/esm/testing/types.js.map +1 -1
- package/esm/util/registry-validators.d.ts +2 -2
- package/esm/util/registry-validators.d.ts.map +1 -1
- package/esm/util/registry-validators.js +38 -18
- package/esm/util/registry-validators.js.map +1 -1
- package/esm/util/registry-validators.test.js +64 -25
- package/esm/util/registry-validators.test.js.map +1 -1
- package/esm/util/registry.d.ts +4 -10
- package/esm/util/registry.d.ts.map +1 -1
- package/esm/util/registry.js +7 -22
- package/esm/util/registry.js.map +1 -1
- package/esm/util/registry.test.js +3 -49
- package/esm/util/registry.test.js.map +1 -1
- package/esm/util/tool-caller.d.ts +2 -2
- package/esm/util/tool-caller.d.ts.map +1 -1
- package/esm/util/tool-caller.js +5 -12
- package/esm/util/tool-caller.js.map +1 -1
- package/esm/v1/index.d.ts +35 -0
- package/esm/v1/index.d.ts.map +1 -0
- package/esm/v1/index.js +46 -0
- package/esm/v1/index.js.map +1 -0
- package/esm/v1/types/component.d.ts +47 -0
- package/esm/v1/types/component.d.ts.map +1 -0
- package/esm/v1/types/component.js +10 -0
- package/esm/v1/types/component.js.map +1 -0
- package/esm/v1/types/event.d.ts +63 -0
- package/esm/v1/types/event.d.ts.map +1 -0
- package/esm/v1/types/event.js +8 -0
- package/esm/v1/types/event.js.map +1 -0
- package/esm/v1/types/message.d.ts +39 -0
- package/esm/v1/types/message.d.ts.map +1 -0
- package/esm/v1/types/message.js +9 -0
- package/esm/v1/types/message.js.map +1 -0
- package/esm/v1/types/thread.d.ts +54 -0
- package/esm/v1/types/thread.d.ts.map +1 -0
- package/esm/v1/types/thread.js +8 -0
- package/esm/v1/types/thread.js.map +1 -0
- package/esm/v1/types/tool.d.ts +52 -0
- package/esm/v1/types/tool.d.ts.map +1 -0
- package/esm/v1/types/tool.js +10 -0
- package/esm/v1/types/tool.js.map +1 -0
- package/package.json +18 -8
- package/dist/schema/zod.d.ts +0 -57
- package/dist/schema/zod.d.ts.map +0 -1
- package/dist/schema/zod.js +0 -191
- package/dist/schema/zod.js.map +0 -1
- package/dist/schema/zod.test.d.ts +0 -2
- package/dist/schema/zod.test.d.ts.map +0 -1
- package/dist/schema/zod.test.js +0 -663
- package/dist/schema/zod.test.js.map +0 -1
- package/esm/schema/zod.d.ts +0 -57
- package/esm/schema/zod.d.ts.map +0 -1
- package/esm/schema/zod.js +0 -180
- package/esm/schema/zod.js.map +0 -1
- package/esm/schema/zod.test.d.ts +0 -2
- package/esm/schema/zod.test.d.ts.map +0 -1
- package/esm/schema/zod.test.js +0 -628
- package/esm/schema/zod.test.js.map +0 -1
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const typescript_sdk_1 = require("@tambo-ai/typescript-sdk");
|
|
39
|
+
const typescript_sdk_1 = __importStar(require("@tambo-ai/typescript-sdk"));
|
|
7
40
|
const react_1 = require("@testing-library/react");
|
|
8
41
|
const react_2 = __importDefault(require("react"));
|
|
9
42
|
const v4_1 = require("zod/v4");
|
|
@@ -28,9 +61,14 @@ jest.mock("./tambo-client-provider", () => {
|
|
|
28
61
|
TamboClientContext: react_2.default.createContext(undefined),
|
|
29
62
|
};
|
|
30
63
|
});
|
|
31
|
-
jest.mock("@tambo-ai/typescript-sdk", () =>
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
jest.mock("@tambo-ai/typescript-sdk", () => {
|
|
65
|
+
const actual = jest.requireActual("@tambo-ai/typescript-sdk");
|
|
66
|
+
return {
|
|
67
|
+
__esModule: true,
|
|
68
|
+
...actual,
|
|
69
|
+
advanceStream: jest.fn(),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
34
72
|
// Mock the getCustomContext
|
|
35
73
|
jest.mock("../util/registry", () => ({
|
|
36
74
|
...jest.requireActual("../util/registry"),
|
|
@@ -73,26 +111,9 @@ const createMockAdvanceResponse = (overrides = {}) => ({
|
|
|
73
111
|
});
|
|
74
112
|
describe("TamboThreadProvider", () => {
|
|
75
113
|
const mockThread = createMockThread();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
retrieve: jest.fn(),
|
|
81
|
-
advance: jest.fn(),
|
|
82
|
-
advanceByID: jest.fn(),
|
|
83
|
-
generateName: jest.fn(),
|
|
84
|
-
};
|
|
85
|
-
const mockProjectsApi = {
|
|
86
|
-
getCurrent: jest.fn(),
|
|
87
|
-
};
|
|
88
|
-
const mockBeta = {
|
|
89
|
-
threads: mockThreadsApi,
|
|
90
|
-
projects: mockProjectsApi,
|
|
91
|
-
};
|
|
92
|
-
const mockTamboAI = {
|
|
93
|
-
apiKey: "",
|
|
94
|
-
beta: mockBeta,
|
|
95
|
-
};
|
|
114
|
+
let mockTamboAI;
|
|
115
|
+
let mockThreadsApi;
|
|
116
|
+
let mockProjectsApi;
|
|
96
117
|
let mockQueryClient;
|
|
97
118
|
const mockRegistry = [
|
|
98
119
|
{
|
|
@@ -144,8 +165,19 @@ describe("TamboThreadProvider", () => {
|
|
|
144
165
|
};
|
|
145
166
|
// Default wrapper for most tests
|
|
146
167
|
const Wrapper = createWrapper();
|
|
168
|
+
afterEach(() => {
|
|
169
|
+
jest.restoreAllMocks();
|
|
170
|
+
});
|
|
147
171
|
beforeEach(() => {
|
|
148
172
|
jest.clearAllMocks();
|
|
173
|
+
mockTamboAI = new typescript_sdk_1.default({
|
|
174
|
+
apiKey: "",
|
|
175
|
+
fetch: () => {
|
|
176
|
+
throw new Error("Unexpected network call in test");
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
mockThreadsApi = mockTamboAI.beta.threads;
|
|
180
|
+
mockProjectsApi = mockTamboAI.beta.projects;
|
|
149
181
|
// Setup mock query client
|
|
150
182
|
mockQueryClient = {
|
|
151
183
|
invalidateQueries: jest.fn().mockResolvedValue(undefined),
|
|
@@ -154,21 +186,22 @@ describe("TamboThreadProvider", () => {
|
|
|
154
186
|
jest
|
|
155
187
|
.mocked(tambo_client_provider_1.useTamboQueryClient)
|
|
156
188
|
.mockReturnValue(mockQueryClient);
|
|
157
|
-
jest.
|
|
189
|
+
jest.spyOn(mockThreadsApi, "retrieve").mockResolvedValue(mockThread);
|
|
158
190
|
jest
|
|
159
|
-
.
|
|
191
|
+
.spyOn(mockThreadsApi.messages, "create")
|
|
160
192
|
.mockResolvedValue(createMockMessage());
|
|
161
193
|
jest
|
|
162
|
-
.
|
|
194
|
+
.spyOn(mockThreadsApi, "advance")
|
|
163
195
|
.mockResolvedValue(createMockAdvanceResponse());
|
|
164
196
|
jest
|
|
165
|
-
.
|
|
197
|
+
.spyOn(mockThreadsApi, "advanceByID")
|
|
166
198
|
.mockResolvedValue(createMockAdvanceResponse());
|
|
167
|
-
jest.
|
|
199
|
+
jest.spyOn(mockThreadsApi, "generateName").mockResolvedValue({
|
|
168
200
|
...mockThread,
|
|
169
201
|
name: "Generated Thread Name",
|
|
170
202
|
});
|
|
171
|
-
jest.
|
|
203
|
+
jest.spyOn(mockThreadsApi, "update").mockResolvedValue({});
|
|
204
|
+
jest.spyOn(mockProjectsApi, "getCurrent").mockResolvedValue({
|
|
172
205
|
id: "test-project-id",
|
|
173
206
|
name: "Test Project",
|
|
174
207
|
isTokenRequired: false,
|
|
@@ -236,7 +269,7 @@ describe("TamboThreadProvider", () => {
|
|
|
236
269
|
});
|
|
237
270
|
});
|
|
238
271
|
it("should send a message and update thread state", async () => {
|
|
239
|
-
const
|
|
272
|
+
const mockStreamResponse = {
|
|
240
273
|
responseMessageDto: {
|
|
241
274
|
id: "response-1",
|
|
242
275
|
content: [{ type: "text", text: "Response" }],
|
|
@@ -249,14 +282,17 @@ describe("TamboThreadProvider", () => {
|
|
|
249
282
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
250
283
|
mcpAccessToken: "test-mcp-access-token",
|
|
251
284
|
};
|
|
252
|
-
|
|
253
|
-
.
|
|
254
|
-
|
|
285
|
+
const mockAsyncIterator = {
|
|
286
|
+
[Symbol.asyncIterator]: async function* () {
|
|
287
|
+
yield mockStreamResponse;
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
255
291
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), { wrapper: Wrapper });
|
|
256
292
|
await (0, react_1.act)(async () => {
|
|
257
293
|
await result.current.sendThreadMessage("Hello", {
|
|
258
294
|
threadId: "test-thread-1",
|
|
259
|
-
streamResponse:
|
|
295
|
+
streamResponse: true,
|
|
260
296
|
additionalContext: {
|
|
261
297
|
custom: {
|
|
262
298
|
message: "additional instructions",
|
|
@@ -264,7 +300,7 @@ describe("TamboThreadProvider", () => {
|
|
|
264
300
|
},
|
|
265
301
|
});
|
|
266
302
|
});
|
|
267
|
-
expect(
|
|
303
|
+
expect(typescript_sdk_1.advanceStream).toHaveBeenCalledWith(expect.anything(), {
|
|
268
304
|
messageToAppend: {
|
|
269
305
|
content: [{ type: "text", text: "Hello" }],
|
|
270
306
|
role: "user",
|
|
@@ -278,7 +314,7 @@ describe("TamboThreadProvider", () => {
|
|
|
278
314
|
contextKey: undefined,
|
|
279
315
|
clientTools: [],
|
|
280
316
|
toolCallCounts: {},
|
|
281
|
-
});
|
|
317
|
+
}, "test-thread-1");
|
|
282
318
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
283
319
|
});
|
|
284
320
|
it("should handle streaming responses", async () => {
|
|
@@ -313,7 +349,7 @@ describe("TamboThreadProvider", () => {
|
|
|
313
349
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
314
350
|
});
|
|
315
351
|
it("should handle tool calls during message processing.", async () => {
|
|
316
|
-
const
|
|
352
|
+
const mockToolCallChunk = {
|
|
317
353
|
responseMessageDto: {
|
|
318
354
|
id: "tool-call-1",
|
|
319
355
|
content: [{ type: "text", text: "Tool response" }],
|
|
@@ -329,10 +365,7 @@ describe("TamboThreadProvider", () => {
|
|
|
329
365
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
330
366
|
mcpAccessToken: "test-mcp-access-token",
|
|
331
367
|
};
|
|
332
|
-
|
|
333
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
334
|
-
.mockResolvedValueOnce(mockToolCallResponse)
|
|
335
|
-
.mockResolvedValueOnce({
|
|
368
|
+
const mockFinalChunk = {
|
|
336
369
|
responseMessageDto: {
|
|
337
370
|
id: "advance-response2",
|
|
338
371
|
content: [{ type: "text", text: "response 2" }],
|
|
@@ -343,12 +376,26 @@ describe("TamboThreadProvider", () => {
|
|
|
343
376
|
},
|
|
344
377
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
345
378
|
mcpAccessToken: "test-mcp-access-token",
|
|
346
|
-
}
|
|
379
|
+
};
|
|
380
|
+
const mockAsyncIterator = {
|
|
381
|
+
[Symbol.asyncIterator]: async function* () {
|
|
382
|
+
yield mockToolCallChunk;
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
const mockAsyncIterator2 = {
|
|
386
|
+
[Symbol.asyncIterator]: async function* () {
|
|
387
|
+
yield mockFinalChunk;
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
jest
|
|
391
|
+
.mocked(typescript_sdk_1.advanceStream)
|
|
392
|
+
.mockResolvedValueOnce(mockAsyncIterator)
|
|
393
|
+
.mockResolvedValueOnce(mockAsyncIterator2);
|
|
347
394
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), { wrapper: Wrapper });
|
|
348
395
|
await (0, react_1.act)(async () => {
|
|
349
396
|
await result.current.sendThreadMessage("Use tool", {
|
|
350
397
|
threadId: "test-thread-1",
|
|
351
|
-
streamResponse:
|
|
398
|
+
streamResponse: true,
|
|
352
399
|
});
|
|
353
400
|
});
|
|
354
401
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
@@ -361,7 +408,7 @@ describe("TamboThreadProvider", () => {
|
|
|
361
408
|
const mockOnCallUnregisteredTool = jest
|
|
362
409
|
.fn()
|
|
363
410
|
.mockResolvedValue("unregistered-tool-result");
|
|
364
|
-
const
|
|
411
|
+
const mockUnregisteredToolCallChunk = {
|
|
365
412
|
responseMessageDto: {
|
|
366
413
|
id: "unregistered-tool-call-1",
|
|
367
414
|
content: [{ type: "text", text: "Unregistered tool response" }],
|
|
@@ -379,10 +426,7 @@ describe("TamboThreadProvider", () => {
|
|
|
379
426
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
380
427
|
mcpAccessToken: "test-mcp-access-token",
|
|
381
428
|
};
|
|
382
|
-
|
|
383
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
384
|
-
.mockResolvedValueOnce(mockUnregisteredToolCallResponse)
|
|
385
|
-
.mockResolvedValueOnce({
|
|
429
|
+
const mockFinalChunk = {
|
|
386
430
|
responseMessageDto: {
|
|
387
431
|
id: "advance-response2",
|
|
388
432
|
content: [{ type: "text", text: "response 2" }],
|
|
@@ -393,7 +437,21 @@ describe("TamboThreadProvider", () => {
|
|
|
393
437
|
},
|
|
394
438
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
395
439
|
mcpAccessToken: "test-mcp-access-token",
|
|
396
|
-
}
|
|
440
|
+
};
|
|
441
|
+
const mockAsyncIterator = {
|
|
442
|
+
[Symbol.asyncIterator]: async function* () {
|
|
443
|
+
yield mockUnregisteredToolCallChunk;
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
const mockAsyncIterator2 = {
|
|
447
|
+
[Symbol.asyncIterator]: async function* () {
|
|
448
|
+
yield mockFinalChunk;
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
jest
|
|
452
|
+
.mocked(typescript_sdk_1.advanceStream)
|
|
453
|
+
.mockResolvedValueOnce(mockAsyncIterator)
|
|
454
|
+
.mockResolvedValueOnce(mockAsyncIterator2);
|
|
397
455
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
398
456
|
wrapper: createWrapper({
|
|
399
457
|
onCallUnregisteredTool: mockOnCallUnregisteredTool,
|
|
@@ -402,14 +460,14 @@ describe("TamboThreadProvider", () => {
|
|
|
402
460
|
await (0, react_1.act)(async () => {
|
|
403
461
|
await result.current.sendThreadMessage("Use unregistered tool", {
|
|
404
462
|
threadId: "test-thread-1",
|
|
405
|
-
streamResponse:
|
|
463
|
+
streamResponse: true,
|
|
406
464
|
});
|
|
407
465
|
});
|
|
408
466
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
409
467
|
expect(mockOnCallUnregisteredTool).toHaveBeenCalledWith("unregistered-tool", [{ parameterName: "input", parameterValue: "test-input" }]);
|
|
410
468
|
});
|
|
411
469
|
it("should handle unregistered tool calls without onCallUnregisteredTool", async () => {
|
|
412
|
-
const
|
|
470
|
+
const mockUnregisteredToolCallChunk = {
|
|
413
471
|
responseMessageDto: {
|
|
414
472
|
id: "unregistered-tool-call-1",
|
|
415
473
|
content: [{ type: "text", text: "Unregistered tool response" }],
|
|
@@ -427,10 +485,7 @@ describe("TamboThreadProvider", () => {
|
|
|
427
485
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
428
486
|
mcpAccessToken: "test-mcp-access-token",
|
|
429
487
|
};
|
|
430
|
-
|
|
431
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
432
|
-
.mockResolvedValueOnce(mockUnregisteredToolCallResponse)
|
|
433
|
-
.mockResolvedValueOnce({
|
|
488
|
+
const mockFinalChunk = {
|
|
434
489
|
responseMessageDto: {
|
|
435
490
|
id: "advance-response2",
|
|
436
491
|
content: [{ type: "text", text: "response 2" }],
|
|
@@ -441,12 +496,26 @@ describe("TamboThreadProvider", () => {
|
|
|
441
496
|
},
|
|
442
497
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
443
498
|
mcpAccessToken: "test-mcp-access-token",
|
|
444
|
-
}
|
|
499
|
+
};
|
|
500
|
+
const mockAsyncIterator = {
|
|
501
|
+
[Symbol.asyncIterator]: async function* () {
|
|
502
|
+
yield mockUnregisteredToolCallChunk;
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
const mockAsyncIterator2 = {
|
|
506
|
+
[Symbol.asyncIterator]: async function* () {
|
|
507
|
+
yield mockFinalChunk;
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
jest
|
|
511
|
+
.mocked(typescript_sdk_1.advanceStream)
|
|
512
|
+
.mockResolvedValueOnce(mockAsyncIterator)
|
|
513
|
+
.mockResolvedValueOnce(mockAsyncIterator2);
|
|
445
514
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), { wrapper: Wrapper });
|
|
446
515
|
await (0, react_1.act)(async () => {
|
|
447
516
|
await result.current.sendThreadMessage("Use unregistered tool", {
|
|
448
517
|
threadId: "test-thread-1",
|
|
449
|
-
streamResponse:
|
|
518
|
+
streamResponse: true,
|
|
450
519
|
});
|
|
451
520
|
});
|
|
452
521
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
@@ -507,77 +576,16 @@ describe("TamboThreadProvider", () => {
|
|
|
507
576
|
expect(mockThreadsApi.advance).not.toHaveBeenCalled();
|
|
508
577
|
expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
|
|
509
578
|
});
|
|
510
|
-
it("should
|
|
511
|
-
// Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
|
|
579
|
+
it("should throw error when streamResponse=false (non-streaming not supported)", async () => {
|
|
512
580
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
513
581
|
wrapper: createWrapper({ streaming: true }),
|
|
514
582
|
});
|
|
515
583
|
await (0, react_1.act)(async () => {
|
|
516
|
-
await result.current.sendThreadMessage("Hello non-streaming", {
|
|
584
|
+
await expect(result.current.sendThreadMessage("Hello non-streaming", {
|
|
517
585
|
threadId: "test-thread-1",
|
|
518
586
|
streamResponse: false,
|
|
519
|
-
|
|
520
|
-
custom: {
|
|
521
|
-
message: "additional instructions",
|
|
522
|
-
},
|
|
523
|
-
},
|
|
524
|
-
});
|
|
587
|
+
})).rejects.toThrow();
|
|
525
588
|
});
|
|
526
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenCalledWith("test-thread-1", {
|
|
527
|
-
messageToAppend: {
|
|
528
|
-
content: [{ type: "text", text: "Hello non-streaming" }],
|
|
529
|
-
role: "user",
|
|
530
|
-
additionalContext: {
|
|
531
|
-
custom: {
|
|
532
|
-
message: "additional instructions",
|
|
533
|
-
},
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
availableComponents: (0, tools_1.serializeRegistry)(mockRegistry),
|
|
537
|
-
contextKey: undefined,
|
|
538
|
-
clientTools: [],
|
|
539
|
-
forceToolChoice: undefined,
|
|
540
|
-
toolCallCounts: {},
|
|
541
|
-
});
|
|
542
|
-
// Should not call advance or advanceStream
|
|
543
|
-
expect(mockThreadsApi.advance).not.toHaveBeenCalled();
|
|
544
|
-
expect(typescript_sdk_1.advanceStream).not.toHaveBeenCalled();
|
|
545
|
-
});
|
|
546
|
-
it("should call advanceById when streamResponse is undefined and provider streaming=false", async () => {
|
|
547
|
-
// Use wrapper with streaming=false to test that undefined streamResponse respects provider setting
|
|
548
|
-
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
549
|
-
wrapper: createWrapper({ streaming: false }),
|
|
550
|
-
});
|
|
551
|
-
await (0, react_1.act)(async () => {
|
|
552
|
-
await result.current.sendThreadMessage("Hello default", {
|
|
553
|
-
threadId: "test-thread-1",
|
|
554
|
-
// streamResponse is undefined, should use provider's streaming=false
|
|
555
|
-
additionalContext: {
|
|
556
|
-
custom: {
|
|
557
|
-
message: "additional instructions",
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
});
|
|
562
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenCalledWith("test-thread-1", {
|
|
563
|
-
messageToAppend: {
|
|
564
|
-
content: [{ type: "text", text: "Hello default" }],
|
|
565
|
-
role: "user",
|
|
566
|
-
additionalContext: {
|
|
567
|
-
custom: {
|
|
568
|
-
message: "additional instructions",
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
},
|
|
572
|
-
availableComponents: (0, tools_1.serializeRegistry)(mockRegistry),
|
|
573
|
-
contextKey: undefined,
|
|
574
|
-
clientTools: [],
|
|
575
|
-
forceToolChoice: undefined,
|
|
576
|
-
toolCallCounts: {},
|
|
577
|
-
});
|
|
578
|
-
// Should not call advance or advanceStream
|
|
579
|
-
expect(mockThreadsApi.advance).not.toHaveBeenCalled();
|
|
580
|
-
expect(typescript_sdk_1.advanceStream).not.toHaveBeenCalled();
|
|
581
589
|
});
|
|
582
590
|
it("should call advanceStream when streamResponse is undefined and provider streaming=true (default)", async () => {
|
|
583
591
|
const mockStreamResponse = {
|
|
@@ -633,44 +641,6 @@ describe("TamboThreadProvider", () => {
|
|
|
633
641
|
expect(mockThreadsApi.advance).not.toHaveBeenCalled();
|
|
634
642
|
expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
|
|
635
643
|
});
|
|
636
|
-
it("should call advance when streamResponse=false for placeholder thread", async () => {
|
|
637
|
-
// Use wrapper with streaming=true to show that explicit streamResponse=false overrides provider setting
|
|
638
|
-
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
639
|
-
wrapper: createWrapper({ streaming: true }),
|
|
640
|
-
});
|
|
641
|
-
// Start with placeholder thread (which is the default state)
|
|
642
|
-
expect(result.current.thread.id).toBe("placeholder");
|
|
643
|
-
await (0, react_1.act)(async () => {
|
|
644
|
-
await result.current.sendThreadMessage("Hello new thread", {
|
|
645
|
-
threadId: "placeholder",
|
|
646
|
-
streamResponse: false,
|
|
647
|
-
additionalContext: {
|
|
648
|
-
custom: {
|
|
649
|
-
message: "additional instructions",
|
|
650
|
-
},
|
|
651
|
-
},
|
|
652
|
-
});
|
|
653
|
-
});
|
|
654
|
-
expect(mockThreadsApi.advance).toHaveBeenCalledWith({
|
|
655
|
-
messageToAppend: {
|
|
656
|
-
content: [{ type: "text", text: "Hello new thread" }],
|
|
657
|
-
role: "user",
|
|
658
|
-
additionalContext: {
|
|
659
|
-
custom: {
|
|
660
|
-
message: "additional instructions",
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
|
-
availableComponents: (0, tools_1.serializeRegistry)(mockRegistry),
|
|
665
|
-
contextKey: undefined,
|
|
666
|
-
clientTools: [],
|
|
667
|
-
forceToolChoice: undefined,
|
|
668
|
-
toolCallCounts: {},
|
|
669
|
-
});
|
|
670
|
-
// Should not call advanceById or advanceStream
|
|
671
|
-
expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
|
|
672
|
-
expect(typescript_sdk_1.advanceStream).not.toHaveBeenCalled();
|
|
673
|
-
});
|
|
674
644
|
it("should call advanceStream when streamResponse=true for placeholder thread", async () => {
|
|
675
645
|
const mockStreamResponse = {
|
|
676
646
|
responseMessageDto: {
|
|
@@ -727,26 +697,96 @@ describe("TamboThreadProvider", () => {
|
|
|
727
697
|
expect(mockThreadsApi.advance).not.toHaveBeenCalled();
|
|
728
698
|
expect(mockThreadsApi.advanceByID).not.toHaveBeenCalled();
|
|
729
699
|
});
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
//
|
|
735
|
-
|
|
700
|
+
it("should handle multiple sequential messages during streaming (server tool scenario)", async () => {
|
|
701
|
+
// This test verifies the fix for the bug where the second message doesn't render
|
|
702
|
+
// during server tool response streaming. The scenario:
|
|
703
|
+
// 1. First message: "I will call the tool..." with statusMessage
|
|
704
|
+
// 2. Second message: The tool result response streaming in
|
|
705
|
+
// First message - tool announcement (server tools don't have componentName set during streaming)
|
|
706
|
+
const mockFirstMessage = {
|
|
707
|
+
responseMessageDto: {
|
|
708
|
+
id: "msg-first",
|
|
709
|
+
content: [{ type: "text", text: "I will search the docs..." }],
|
|
710
|
+
role: "assistant",
|
|
711
|
+
threadId: "test-thread-1",
|
|
712
|
+
component: {
|
|
713
|
+
componentName: "",
|
|
714
|
+
componentState: {},
|
|
715
|
+
message: "",
|
|
716
|
+
props: {},
|
|
717
|
+
statusMessage: "searching the Tambo docs...",
|
|
718
|
+
},
|
|
719
|
+
componentState: {},
|
|
720
|
+
createdAt: new Date().toISOString(),
|
|
721
|
+
},
|
|
722
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
723
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
724
|
+
};
|
|
725
|
+
// Second message - tool result (different ID!)
|
|
726
|
+
const mockSecondMessageChunk1 = {
|
|
727
|
+
responseMessageDto: {
|
|
728
|
+
id: "msg-second",
|
|
729
|
+
content: [{ type: "text", text: "Here's what I found..." }],
|
|
730
|
+
role: "assistant",
|
|
731
|
+
threadId: "test-thread-1",
|
|
732
|
+
componentState: {},
|
|
733
|
+
createdAt: new Date().toISOString(),
|
|
734
|
+
},
|
|
735
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
736
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
737
|
+
};
|
|
738
|
+
const mockSecondMessageChunk2 = {
|
|
739
|
+
responseMessageDto: {
|
|
740
|
+
id: "msg-second",
|
|
741
|
+
content: [
|
|
742
|
+
{
|
|
743
|
+
type: "text",
|
|
744
|
+
text: "Here's what I found in the documentation about that topic.",
|
|
745
|
+
},
|
|
746
|
+
],
|
|
747
|
+
role: "assistant",
|
|
748
|
+
threadId: "test-thread-1",
|
|
749
|
+
componentState: {},
|
|
750
|
+
createdAt: new Date().toISOString(),
|
|
751
|
+
},
|
|
752
|
+
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
753
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
754
|
+
};
|
|
755
|
+
const mockAsyncIterator = {
|
|
756
|
+
[Symbol.asyncIterator]: async function* () {
|
|
757
|
+
yield mockFirstMessage;
|
|
758
|
+
yield mockSecondMessageChunk1;
|
|
759
|
+
yield mockSecondMessageChunk2;
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
736
763
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
737
|
-
wrapper:
|
|
764
|
+
wrapper: createWrapper({ streaming: true }),
|
|
738
765
|
});
|
|
739
|
-
// Expect the error to be thrown
|
|
740
766
|
await (0, react_1.act)(async () => {
|
|
741
|
-
await result.current.
|
|
742
|
-
await expect(result.current.sendThreadMessage("Hello", {
|
|
767
|
+
await result.current.sendThreadMessage("Search the docs", {
|
|
743
768
|
threadId: "test-thread-1",
|
|
744
|
-
streamResponse:
|
|
745
|
-
})
|
|
769
|
+
streamResponse: true,
|
|
770
|
+
});
|
|
746
771
|
});
|
|
747
|
-
//
|
|
748
|
-
expect(result.current.
|
|
772
|
+
// Thread should have 3 messages: user message + 2 assistant messages
|
|
773
|
+
expect(result.current.thread.messages).toHaveLength(3);
|
|
774
|
+
// Filter to assistant messages only
|
|
775
|
+
const assistantMessages = result.current.thread.messages.filter((m) => m.role === "assistant");
|
|
776
|
+
expect(assistantMessages).toHaveLength(2);
|
|
777
|
+
// First assistant message should have the tool status
|
|
778
|
+
const firstMsg = result.current.thread.messages.find((m) => m.id === "msg-first");
|
|
779
|
+
expect(firstMsg).toBeDefined();
|
|
780
|
+
expect(firstMsg?.content[0]?.text).toContain("search the docs");
|
|
781
|
+
// Second assistant message should have the final content
|
|
782
|
+
const secondMsg = result.current.thread.messages.find((m) => m.id === "msg-second");
|
|
783
|
+
expect(secondMsg).toBeDefined();
|
|
784
|
+
expect(secondMsg?.content[0]?.text).toContain("what I found in the documentation");
|
|
785
|
+
// Generation should be complete
|
|
786
|
+
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.COMPLETE);
|
|
749
787
|
});
|
|
788
|
+
});
|
|
789
|
+
describe("error handling", () => {
|
|
750
790
|
it("should set generation stage to ERROR when streaming sendThreadMessage fails", async () => {
|
|
751
791
|
const testError = new Error("Streaming API call failed");
|
|
752
792
|
// Mock advanceStream to throw an error
|
|
@@ -765,24 +805,85 @@ describe("TamboThreadProvider", () => {
|
|
|
765
805
|
// Verify generation stage is set to ERROR
|
|
766
806
|
expect(result.current.generationStage).toBe(generate_component_response_1.GenerationStage.ERROR);
|
|
767
807
|
});
|
|
768
|
-
it("should
|
|
769
|
-
const testError = new Error("
|
|
770
|
-
|
|
771
|
-
jest.mocked(mockThreadsApi.advance).mockRejectedValue(testError);
|
|
808
|
+
it("should rollback optimistic user message when sendThreadMessage fails", async () => {
|
|
809
|
+
const testError = new Error("API call failed");
|
|
810
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockRejectedValue(testError);
|
|
772
811
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
773
812
|
wrapper: Wrapper,
|
|
774
813
|
});
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
814
|
+
await (0, react_1.act)(async () => {
|
|
815
|
+
await result.current.switchCurrentThread("test-thread-1");
|
|
816
|
+
});
|
|
817
|
+
const initialMessageCount = result.current.thread.messages.length;
|
|
778
818
|
await (0, react_1.act)(async () => {
|
|
779
819
|
await expect(result.current.sendThreadMessage("Hello", {
|
|
780
|
-
threadId: "
|
|
781
|
-
streamResponse:
|
|
782
|
-
})).rejects.toThrow("
|
|
820
|
+
threadId: "test-thread-1",
|
|
821
|
+
streamResponse: true,
|
|
822
|
+
})).rejects.toThrow("API call failed");
|
|
783
823
|
});
|
|
784
|
-
// Verify
|
|
785
|
-
expect(result.current.
|
|
824
|
+
// Verify user message was rolled back
|
|
825
|
+
expect(result.current.thread.messages.length).toBe(initialMessageCount);
|
|
826
|
+
});
|
|
827
|
+
it("should rollback optimistic message when addThreadMessage fails", async () => {
|
|
828
|
+
const testError = new Error("Create message failed");
|
|
829
|
+
jest.mocked(mockThreadsApi.messages.create).mockRejectedValue(testError);
|
|
830
|
+
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
831
|
+
wrapper: Wrapper,
|
|
832
|
+
});
|
|
833
|
+
await (0, react_1.act)(async () => {
|
|
834
|
+
await result.current.switchCurrentThread("test-thread-1");
|
|
835
|
+
});
|
|
836
|
+
const initialMessageCount = result.current.thread.messages.length;
|
|
837
|
+
const newMessage = createMockMessage({ threadId: "test-thread-1" });
|
|
838
|
+
await (0, react_1.act)(async () => {
|
|
839
|
+
await expect(result.current.addThreadMessage(newMessage, true)).rejects.toThrow("Create message failed");
|
|
840
|
+
});
|
|
841
|
+
// Verify message was rolled back
|
|
842
|
+
expect(result.current.thread.messages.length).toBe(initialMessageCount);
|
|
843
|
+
});
|
|
844
|
+
it("should rollback optimistic update when updateThreadMessage fails", async () => {
|
|
845
|
+
const testError = new Error("Update message failed");
|
|
846
|
+
jest.mocked(mockThreadsApi.messages.create).mockRejectedValue(testError);
|
|
847
|
+
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
848
|
+
wrapper: Wrapper,
|
|
849
|
+
});
|
|
850
|
+
await (0, react_1.act)(async () => {
|
|
851
|
+
await result.current.switchCurrentThread("test-thread-1");
|
|
852
|
+
});
|
|
853
|
+
const existingMessage = createMockMessage({
|
|
854
|
+
id: "existing-msg",
|
|
855
|
+
threadId: "test-thread-1",
|
|
856
|
+
content: [{ type: "text", text: "Old content" }],
|
|
857
|
+
});
|
|
858
|
+
await (0, react_1.act)(async () => {
|
|
859
|
+
await result.current.addThreadMessage(existingMessage, false);
|
|
860
|
+
});
|
|
861
|
+
const initialMessageCount = result.current.thread.messages.length;
|
|
862
|
+
await (0, react_1.act)(async () => {
|
|
863
|
+
await expect(result.current.updateThreadMessage("existing-msg", {
|
|
864
|
+
threadId: "test-thread-1",
|
|
865
|
+
content: [{ type: "text", text: "New content" }],
|
|
866
|
+
role: "assistant",
|
|
867
|
+
}, true)).rejects.toThrow("Update message failed");
|
|
868
|
+
});
|
|
869
|
+
// Verify message was rolled back
|
|
870
|
+
expect(result.current.thread.messages.length).toBe(initialMessageCount - 1);
|
|
871
|
+
});
|
|
872
|
+
it("should rollback optimistic name update when updateThreadName fails", async () => {
|
|
873
|
+
const testError = new Error("Update name failed");
|
|
874
|
+
jest.mocked(mockThreadsApi.update).mockRejectedValue(testError);
|
|
875
|
+
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
876
|
+
wrapper: Wrapper,
|
|
877
|
+
});
|
|
878
|
+
await (0, react_1.act)(async () => {
|
|
879
|
+
await result.current.switchCurrentThread("test-thread-1");
|
|
880
|
+
});
|
|
881
|
+
const initialName = result.current.thread.name;
|
|
882
|
+
await (0, react_1.act)(async () => {
|
|
883
|
+
await expect(result.current.updateThreadName("New Name", "test-thread-1")).rejects.toThrow("Update name failed");
|
|
884
|
+
});
|
|
885
|
+
// Verify name was rolled back
|
|
886
|
+
expect(result.current.thread.name).toBe(initialName);
|
|
786
887
|
});
|
|
787
888
|
});
|
|
788
889
|
describe("refetch threads list behavior", () => {
|
|
@@ -790,8 +891,8 @@ describe("TamboThreadProvider", () => {
|
|
|
790
891
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
791
892
|
wrapper: Wrapper,
|
|
792
893
|
});
|
|
793
|
-
// Mock the
|
|
794
|
-
const
|
|
894
|
+
// Mock the stream response to return a new thread ID
|
|
895
|
+
const mockStreamResponse = {
|
|
795
896
|
responseMessageDto: {
|
|
796
897
|
id: "response-1",
|
|
797
898
|
content: [{ type: "text", text: "Response" }],
|
|
@@ -804,16 +905,19 @@ describe("TamboThreadProvider", () => {
|
|
|
804
905
|
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
805
906
|
mcpAccessToken: "test-mcp-access-token",
|
|
806
907
|
};
|
|
807
|
-
|
|
808
|
-
.
|
|
809
|
-
|
|
908
|
+
const mockAsyncIterator = {
|
|
909
|
+
[Symbol.asyncIterator]: async function* () {
|
|
910
|
+
yield mockStreamResponse;
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
810
914
|
// Start with placeholder thread
|
|
811
915
|
expect(result.current.thread.id).toBe("placeholder");
|
|
812
916
|
// Send a message which will create a new thread with contextKey
|
|
813
917
|
await (0, react_1.act)(async () => {
|
|
814
918
|
await result.current.sendThreadMessage("Hello", {
|
|
815
919
|
threadId: "placeholder",
|
|
816
|
-
streamResponse:
|
|
920
|
+
streamResponse: true,
|
|
817
921
|
contextKey: "test-context-key",
|
|
818
922
|
});
|
|
819
923
|
});
|
|
@@ -851,93 +955,6 @@ describe("TamboThreadProvider", () => {
|
|
|
851
955
|
});
|
|
852
956
|
});
|
|
853
957
|
describe("transformToContent", () => {
|
|
854
|
-
it("should use custom transformToContent when provided (non-streaming)", async () => {
|
|
855
|
-
const mockTransformToContent = jest.fn().mockReturnValue([
|
|
856
|
-
{ type: "text", text: "Custom transformed content" },
|
|
857
|
-
{
|
|
858
|
-
type: "image_url",
|
|
859
|
-
image_url: { url: "https://example.com/image.png" },
|
|
860
|
-
},
|
|
861
|
-
]);
|
|
862
|
-
const customToolRegistry = [
|
|
863
|
-
{
|
|
864
|
-
name: "TestComponent",
|
|
865
|
-
component: () => react_2.default.createElement("div", null, "Test"),
|
|
866
|
-
description: "Test",
|
|
867
|
-
propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
|
|
868
|
-
associatedTools: [
|
|
869
|
-
{
|
|
870
|
-
name: "custom-tool",
|
|
871
|
-
tool: jest.fn().mockResolvedValue({ data: "tool result" }),
|
|
872
|
-
description: "Tool with custom transform",
|
|
873
|
-
inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
|
|
874
|
-
outputSchema: v4_1.z.object({ data: v4_1.z.string() }),
|
|
875
|
-
transformToContent: mockTransformToContent,
|
|
876
|
-
},
|
|
877
|
-
],
|
|
878
|
-
},
|
|
879
|
-
];
|
|
880
|
-
const mockToolCallResponse = {
|
|
881
|
-
responseMessageDto: {
|
|
882
|
-
id: "tool-call-1",
|
|
883
|
-
content: [{ type: "text", text: "Tool response" }],
|
|
884
|
-
role: "tool",
|
|
885
|
-
threadId: "test-thread-1",
|
|
886
|
-
toolCallRequest: {
|
|
887
|
-
toolName: "custom-tool",
|
|
888
|
-
parameters: [{ parameterName: "input", parameterValue: "test" }],
|
|
889
|
-
},
|
|
890
|
-
componentState: {},
|
|
891
|
-
createdAt: new Date().toISOString(),
|
|
892
|
-
},
|
|
893
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
894
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
895
|
-
};
|
|
896
|
-
jest
|
|
897
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
898
|
-
.mockResolvedValueOnce(mockToolCallResponse)
|
|
899
|
-
.mockResolvedValueOnce({
|
|
900
|
-
responseMessageDto: {
|
|
901
|
-
id: "final-response",
|
|
902
|
-
content: [{ type: "text", text: "Final response" }],
|
|
903
|
-
role: "assistant",
|
|
904
|
-
threadId: "test-thread-1",
|
|
905
|
-
componentState: {},
|
|
906
|
-
createdAt: new Date().toISOString(),
|
|
907
|
-
},
|
|
908
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
909
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
910
|
-
});
|
|
911
|
-
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
912
|
-
wrapper: createWrapper({ components: customToolRegistry }),
|
|
913
|
-
});
|
|
914
|
-
await (0, react_1.act)(async () => {
|
|
915
|
-
await result.current.sendThreadMessage("Use custom tool", {
|
|
916
|
-
threadId: "test-thread-1",
|
|
917
|
-
streamResponse: false,
|
|
918
|
-
});
|
|
919
|
-
});
|
|
920
|
-
// Verify the tool was called with single object arg (new inputSchema interface)
|
|
921
|
-
expect(customToolRegistry[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
|
|
922
|
-
// Verify transformToContent was called with the tool result
|
|
923
|
-
expect(mockTransformToContent).toHaveBeenCalledWith({
|
|
924
|
-
data: "tool result",
|
|
925
|
-
});
|
|
926
|
-
// Verify the second advance call included the transformed content
|
|
927
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenCalledTimes(2);
|
|
928
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
|
|
929
|
-
messageToAppend: expect.objectContaining({
|
|
930
|
-
content: [
|
|
931
|
-
{ type: "text", text: "Custom transformed content" },
|
|
932
|
-
{
|
|
933
|
-
type: "image_url",
|
|
934
|
-
image_url: { url: "https://example.com/image.png" },
|
|
935
|
-
},
|
|
936
|
-
],
|
|
937
|
-
role: "tool",
|
|
938
|
-
}),
|
|
939
|
-
}));
|
|
940
|
-
});
|
|
941
958
|
it("should use custom async transformToContent when provided (streaming)", async () => {
|
|
942
959
|
const mockTransformToContent = jest
|
|
943
960
|
.fn()
|
|
@@ -1034,169 +1051,6 @@ describe("TamboThreadProvider", () => {
|
|
|
1034
1051
|
}),
|
|
1035
1052
|
}), "test-thread-1");
|
|
1036
1053
|
});
|
|
1037
|
-
it("should fallback to stringified text when transformToContent is not provided", async () => {
|
|
1038
|
-
const toolWithoutTransform = [
|
|
1039
|
-
{
|
|
1040
|
-
name: "TestComponent",
|
|
1041
|
-
component: () => react_2.default.createElement("div", null, "Test"),
|
|
1042
|
-
description: "Test",
|
|
1043
|
-
propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
|
|
1044
|
-
associatedTools: [
|
|
1045
|
-
{
|
|
1046
|
-
name: "no-transform-tool",
|
|
1047
|
-
tool: jest
|
|
1048
|
-
.fn()
|
|
1049
|
-
.mockResolvedValue({ complex: "data", nested: { value: 42 } }),
|
|
1050
|
-
description: "Tool without custom transform",
|
|
1051
|
-
inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
|
|
1052
|
-
outputSchema: v4_1.z.object({
|
|
1053
|
-
complex: v4_1.z.string(),
|
|
1054
|
-
nested: v4_1.z.object({ value: v4_1.z.number() }),
|
|
1055
|
-
}),
|
|
1056
|
-
// No transformToContent provided
|
|
1057
|
-
},
|
|
1058
|
-
],
|
|
1059
|
-
},
|
|
1060
|
-
];
|
|
1061
|
-
const mockToolCallResponse = {
|
|
1062
|
-
responseMessageDto: {
|
|
1063
|
-
id: "tool-call-1",
|
|
1064
|
-
content: [{ type: "text", text: "Tool call" }],
|
|
1065
|
-
role: "tool",
|
|
1066
|
-
threadId: "test-thread-1",
|
|
1067
|
-
toolCallRequest: {
|
|
1068
|
-
toolName: "no-transform-tool",
|
|
1069
|
-
parameters: [{ parameterName: "input", parameterValue: "test" }],
|
|
1070
|
-
},
|
|
1071
|
-
componentState: {},
|
|
1072
|
-
createdAt: new Date().toISOString(),
|
|
1073
|
-
},
|
|
1074
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1075
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
1076
|
-
};
|
|
1077
|
-
jest
|
|
1078
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
1079
|
-
.mockResolvedValueOnce(mockToolCallResponse)
|
|
1080
|
-
.mockResolvedValueOnce({
|
|
1081
|
-
responseMessageDto: {
|
|
1082
|
-
id: "final-response",
|
|
1083
|
-
content: [{ type: "text", text: "Final response" }],
|
|
1084
|
-
role: "assistant",
|
|
1085
|
-
threadId: "test-thread-1",
|
|
1086
|
-
componentState: {},
|
|
1087
|
-
createdAt: new Date().toISOString(),
|
|
1088
|
-
},
|
|
1089
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1090
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
1091
|
-
});
|
|
1092
|
-
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
1093
|
-
wrapper: createWrapper({ components: toolWithoutTransform }),
|
|
1094
|
-
});
|
|
1095
|
-
await (0, react_1.act)(async () => {
|
|
1096
|
-
await result.current.sendThreadMessage("Use tool without transform", {
|
|
1097
|
-
threadId: "test-thread-1",
|
|
1098
|
-
streamResponse: false,
|
|
1099
|
-
});
|
|
1100
|
-
});
|
|
1101
|
-
// Verify the tool was called with single object arg (new inputSchema interface)
|
|
1102
|
-
expect(toolWithoutTransform[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
|
|
1103
|
-
// Verify the second advance call used stringified content
|
|
1104
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
|
|
1105
|
-
messageToAppend: expect.objectContaining({
|
|
1106
|
-
content: [
|
|
1107
|
-
{
|
|
1108
|
-
type: "text",
|
|
1109
|
-
text: '{"complex":"data","nested":{"value":42}}',
|
|
1110
|
-
},
|
|
1111
|
-
],
|
|
1112
|
-
role: "tool",
|
|
1113
|
-
}),
|
|
1114
|
-
}));
|
|
1115
|
-
});
|
|
1116
|
-
it("should always return text for error responses even with transformToContent", async () => {
|
|
1117
|
-
const mockTransformToContent = jest.fn().mockReturnValue([
|
|
1118
|
-
{
|
|
1119
|
-
type: "image_url",
|
|
1120
|
-
image_url: { url: "https://example.com/error.png" },
|
|
1121
|
-
},
|
|
1122
|
-
]);
|
|
1123
|
-
const toolWithTransform = [
|
|
1124
|
-
{
|
|
1125
|
-
name: "TestComponent",
|
|
1126
|
-
component: () => react_2.default.createElement("div", null, "Test"),
|
|
1127
|
-
description: "Test",
|
|
1128
|
-
propsSchema: v4_1.z.object({ test: v4_1.z.string() }),
|
|
1129
|
-
associatedTools: [
|
|
1130
|
-
{
|
|
1131
|
-
name: "error-tool",
|
|
1132
|
-
tool: jest
|
|
1133
|
-
.fn()
|
|
1134
|
-
.mockRejectedValue(new Error("Tool execution failed")),
|
|
1135
|
-
description: "Tool that errors",
|
|
1136
|
-
inputSchema: v4_1.z.object({ input: v4_1.z.string() }),
|
|
1137
|
-
outputSchema: v4_1.z.string(),
|
|
1138
|
-
transformToContent: mockTransformToContent,
|
|
1139
|
-
},
|
|
1140
|
-
],
|
|
1141
|
-
},
|
|
1142
|
-
];
|
|
1143
|
-
const mockToolCallResponse = {
|
|
1144
|
-
responseMessageDto: {
|
|
1145
|
-
id: "tool-call-1",
|
|
1146
|
-
content: [{ type: "text", text: "Tool call" }],
|
|
1147
|
-
role: "tool",
|
|
1148
|
-
threadId: "test-thread-1",
|
|
1149
|
-
toolCallRequest: {
|
|
1150
|
-
toolName: "error-tool",
|
|
1151
|
-
parameters: [{ parameterName: "input", parameterValue: "test" }],
|
|
1152
|
-
},
|
|
1153
|
-
componentState: {},
|
|
1154
|
-
createdAt: new Date().toISOString(),
|
|
1155
|
-
},
|
|
1156
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1157
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
1158
|
-
};
|
|
1159
|
-
jest
|
|
1160
|
-
.mocked(mockThreadsApi.advanceByID)
|
|
1161
|
-
.mockResolvedValueOnce(mockToolCallResponse)
|
|
1162
|
-
.mockResolvedValueOnce({
|
|
1163
|
-
responseMessageDto: {
|
|
1164
|
-
id: "final-response",
|
|
1165
|
-
content: [{ type: "text", text: "Final response" }],
|
|
1166
|
-
role: "assistant",
|
|
1167
|
-
threadId: "test-thread-1",
|
|
1168
|
-
componentState: {},
|
|
1169
|
-
createdAt: new Date().toISOString(),
|
|
1170
|
-
},
|
|
1171
|
-
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1172
|
-
mcpAccessToken: "test-mcp-access-token",
|
|
1173
|
-
});
|
|
1174
|
-
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
1175
|
-
wrapper: createWrapper({ components: toolWithTransform }),
|
|
1176
|
-
});
|
|
1177
|
-
await (0, react_1.act)(async () => {
|
|
1178
|
-
await result.current.sendThreadMessage("Use error tool", {
|
|
1179
|
-
threadId: "test-thread-1",
|
|
1180
|
-
streamResponse: false,
|
|
1181
|
-
});
|
|
1182
|
-
});
|
|
1183
|
-
// Verify the tool was called with single object arg (new inputSchema interface)
|
|
1184
|
-
expect(toolWithTransform[0]?.associatedTools?.[0]?.tool).toHaveBeenCalledWith({ input: "test" });
|
|
1185
|
-
// Verify transformToContent was NOT called for error responses
|
|
1186
|
-
expect(mockTransformToContent).not.toHaveBeenCalled();
|
|
1187
|
-
// Verify the second advance call used text content with the error message
|
|
1188
|
-
expect(mockThreadsApi.advanceByID).toHaveBeenLastCalledWith("test-thread-1", expect.objectContaining({
|
|
1189
|
-
messageToAppend: expect.objectContaining({
|
|
1190
|
-
content: [
|
|
1191
|
-
expect.objectContaining({
|
|
1192
|
-
type: "text",
|
|
1193
|
-
// Error message should be in text format
|
|
1194
|
-
}),
|
|
1195
|
-
],
|
|
1196
|
-
role: "tool",
|
|
1197
|
-
}),
|
|
1198
|
-
}));
|
|
1199
|
-
});
|
|
1200
1054
|
});
|
|
1201
1055
|
describe("tamboStreamableHint streaming behavior", () => {
|
|
1202
1056
|
it("should call streamable tool during streaming when tamboStreamableHint is true", async () => {
|
|
@@ -1529,6 +1383,25 @@ describe("TamboThreadProvider", () => {
|
|
|
1529
1383
|
});
|
|
1530
1384
|
describe("auto-generate thread name", () => {
|
|
1531
1385
|
it("should auto-generate thread name after reaching threshold", async () => {
|
|
1386
|
+
const mockStreamResponse = {
|
|
1387
|
+
responseMessageDto: {
|
|
1388
|
+
id: "response-1",
|
|
1389
|
+
content: [{ type: "text", text: "Response" }],
|
|
1390
|
+
role: "assistant",
|
|
1391
|
+
threadId: "test-thread-1",
|
|
1392
|
+
component: undefined,
|
|
1393
|
+
componentState: {},
|
|
1394
|
+
createdAt: new Date().toISOString(),
|
|
1395
|
+
},
|
|
1396
|
+
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1397
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
1398
|
+
};
|
|
1399
|
+
const mockAsyncIterator = {
|
|
1400
|
+
[Symbol.asyncIterator]: async function* () {
|
|
1401
|
+
yield mockStreamResponse;
|
|
1402
|
+
},
|
|
1403
|
+
};
|
|
1404
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
1532
1405
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
1533
1406
|
wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
|
|
1534
1407
|
});
|
|
@@ -1560,13 +1433,34 @@ describe("TamboThreadProvider", () => {
|
|
|
1560
1433
|
}), false);
|
|
1561
1434
|
});
|
|
1562
1435
|
await (0, react_1.act)(async () => {
|
|
1563
|
-
await result.current.sendThreadMessage("Test message"
|
|
1436
|
+
await result.current.sendThreadMessage("Test message", {
|
|
1437
|
+
streamResponse: true,
|
|
1438
|
+
});
|
|
1564
1439
|
});
|
|
1565
1440
|
expect(mockThreadsApi.generateName).toHaveBeenCalledWith("test-thread-1");
|
|
1566
1441
|
expect(result.current.thread.name).toBe("Generated Thread Name");
|
|
1567
1442
|
expect(mockQueryClient.setQueryData).toHaveBeenCalledWith(["threads", "test-project-id", undefined], expect.any(Function));
|
|
1568
1443
|
});
|
|
1569
1444
|
it("should NOT auto-generate when autoGenerateThreadName is false", async () => {
|
|
1445
|
+
const mockStreamResponse = {
|
|
1446
|
+
responseMessageDto: {
|
|
1447
|
+
id: "response-1",
|
|
1448
|
+
content: [{ type: "text", text: "Response" }],
|
|
1449
|
+
role: "assistant",
|
|
1450
|
+
threadId: "test-thread-1",
|
|
1451
|
+
component: undefined,
|
|
1452
|
+
componentState: {},
|
|
1453
|
+
createdAt: new Date().toISOString(),
|
|
1454
|
+
},
|
|
1455
|
+
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1456
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
1457
|
+
};
|
|
1458
|
+
const mockAsyncIterator = {
|
|
1459
|
+
[Symbol.asyncIterator]: async function* () {
|
|
1460
|
+
yield mockStreamResponse;
|
|
1461
|
+
},
|
|
1462
|
+
};
|
|
1463
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
1570
1464
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
1571
1465
|
wrapper: createWrapper({
|
|
1572
1466
|
autoGenerateThreadName: false,
|
|
@@ -1598,12 +1492,33 @@ describe("TamboThreadProvider", () => {
|
|
|
1598
1492
|
}), false);
|
|
1599
1493
|
});
|
|
1600
1494
|
await (0, react_1.act)(async () => {
|
|
1601
|
-
await result.current.sendThreadMessage("Test message"
|
|
1495
|
+
await result.current.sendThreadMessage("Test message", {
|
|
1496
|
+
streamResponse: true,
|
|
1497
|
+
});
|
|
1602
1498
|
});
|
|
1603
1499
|
// Should NOT generate name because feature is disabled
|
|
1604
1500
|
expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
|
|
1605
1501
|
});
|
|
1606
1502
|
it("should NOT auto-generate when thread already has a name", async () => {
|
|
1503
|
+
const mockStreamResponse = {
|
|
1504
|
+
responseMessageDto: {
|
|
1505
|
+
id: "response-1",
|
|
1506
|
+
content: [{ type: "text", text: "Response" }],
|
|
1507
|
+
role: "assistant",
|
|
1508
|
+
threadId: "test-thread-1",
|
|
1509
|
+
component: undefined,
|
|
1510
|
+
componentState: {},
|
|
1511
|
+
createdAt: new Date().toISOString(),
|
|
1512
|
+
},
|
|
1513
|
+
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
1514
|
+
mcpAccessToken: "test-mcp-access-token",
|
|
1515
|
+
};
|
|
1516
|
+
const mockAsyncIterator = {
|
|
1517
|
+
[Symbol.asyncIterator]: async function* () {
|
|
1518
|
+
yield mockStreamResponse;
|
|
1519
|
+
},
|
|
1520
|
+
};
|
|
1521
|
+
jest.mocked(typescript_sdk_1.advanceStream).mockResolvedValue(mockAsyncIterator);
|
|
1607
1522
|
const { result } = (0, react_1.renderHook)(() => (0, tambo_thread_provider_1.useTamboThread)(), {
|
|
1608
1523
|
wrapper: createWrapper({ autoGenerateNameThreshold: 2 }),
|
|
1609
1524
|
});
|
|
@@ -1637,7 +1552,9 @@ describe("TamboThreadProvider", () => {
|
|
|
1637
1552
|
expect(result.current.thread.messages).toHaveLength(2);
|
|
1638
1553
|
// Send another message to reach threshold (3 messages total)
|
|
1639
1554
|
await (0, react_1.act)(async () => {
|
|
1640
|
-
await result.current.sendThreadMessage("Test message"
|
|
1555
|
+
await result.current.sendThreadMessage("Test message", {
|
|
1556
|
+
streamResponse: true,
|
|
1557
|
+
});
|
|
1641
1558
|
});
|
|
1642
1559
|
// Should NOT generate name because thread already has one
|
|
1643
1560
|
expect(mockThreadsApi.generateName).not.toHaveBeenCalled();
|