@tambo-ai/react 1.0.1 → 1.0.3

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.
@@ -1,5 +1,5 @@
1
1
  import { EventType, } from "@ag-ui/core";
2
- import { createInitialState, createInitialThreadState, streamReducer, } from "./event-accumulator.js";
2
+ import { PLACEHOLDER_THREAD_ID, createInitialState, createInitialThreadState, streamReducer, } from "./event-accumulator.js";
3
3
  /**
4
4
  * Helper to extract a ToolUseContent from a message content array.
5
5
  * @param content - Content array from a message
@@ -54,11 +54,11 @@ describe("createInitialThreadState", () => {
54
54
  describe("createInitialState", () => {
55
55
  it("creates initial state with placeholder thread", () => {
56
56
  const state = createInitialState();
57
- expect(state.currentThreadId).toBe("placeholder");
58
- expect(state.threadMap.placeholder).toBeDefined();
59
- expect(state.threadMap.placeholder.thread.id).toBe("placeholder");
60
- expect(state.threadMap.placeholder.thread.messages).toEqual([]);
61
- expect(state.threadMap.placeholder.streaming.status).toBe("idle");
57
+ expect(state.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
58
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID]).toBeDefined();
59
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.id).toBe(PLACEHOLDER_THREAD_ID);
60
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toEqual([]);
61
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].streaming.status).toBe("idle");
62
62
  });
63
63
  });
64
64
  describe("streamReducer", () => {
@@ -136,6 +136,92 @@ describe("streamReducer", () => {
136
136
  expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Unknown custom event name"));
137
137
  });
138
138
  });
139
+ describe("START_NEW_THREAD action", () => {
140
+ it("creates a new thread and sets it as current", () => {
141
+ const state = createInitialState();
142
+ const result = streamReducer(state, {
143
+ type: "START_NEW_THREAD",
144
+ threadId: "new_thread",
145
+ });
146
+ expect(result.currentThreadId).toBe("new_thread");
147
+ expect(result.threadMap.new_thread).toBeDefined();
148
+ expect(result.threadMap.new_thread.thread.messages).toHaveLength(0);
149
+ });
150
+ it("overwrites existing placeholder thread on second call", () => {
151
+ // First call: start a new thread on the placeholder
152
+ let state = createInitialState();
153
+ const initialThread = {
154
+ messages: [
155
+ {
156
+ id: "seed_1",
157
+ role: "user",
158
+ content: [{ type: "text", text: "Hello" }],
159
+ },
160
+ ],
161
+ };
162
+ state = streamReducer(state, {
163
+ type: "START_NEW_THREAD",
164
+ threadId: PLACEHOLDER_THREAD_ID,
165
+ initialThread,
166
+ });
167
+ expect(state.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
168
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(1);
169
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages[0].id).toBe("seed_1");
170
+ // Simulate a completed conversation: add more messages to the placeholder
171
+ const userMsgStart = {
172
+ type: EventType.TEXT_MESSAGE_START,
173
+ messageId: "extra_msg",
174
+ role: "user",
175
+ };
176
+ const userMsgContent = {
177
+ type: EventType.TEXT_MESSAGE_CONTENT,
178
+ messageId: "extra_msg",
179
+ delta: "Follow-up",
180
+ };
181
+ state = streamReducer(state, {
182
+ type: "EVENT",
183
+ event: userMsgStart,
184
+ threadId: PLACEHOLDER_THREAD_ID,
185
+ });
186
+ state = streamReducer(state, {
187
+ type: "EVENT",
188
+ event: userMsgContent,
189
+ threadId: PLACEHOLDER_THREAD_ID,
190
+ });
191
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(2);
192
+ // Second call: start a new thread again — should reset to fresh state
193
+ state = streamReducer(state, {
194
+ type: "START_NEW_THREAD",
195
+ threadId: PLACEHOLDER_THREAD_ID,
196
+ initialThread,
197
+ });
198
+ // Should be reset to just the initial seed message, not the old messages
199
+ expect(state.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
200
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(1);
201
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages[0].id).toBe("seed_1");
202
+ });
203
+ it("overwrites existing placeholder even without initialThread", () => {
204
+ let state = createInitialState();
205
+ // Add a message to the placeholder
206
+ state = streamReducer(state, {
207
+ type: "EVENT",
208
+ event: {
209
+ type: EventType.TEXT_MESSAGE_START,
210
+ messageId: "msg_1",
211
+ role: "user",
212
+ },
213
+ threadId: PLACEHOLDER_THREAD_ID,
214
+ });
215
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(1);
216
+ // START_NEW_THREAD without initialThread should still reset
217
+ state = streamReducer(state, {
218
+ type: "START_NEW_THREAD",
219
+ threadId: PLACEHOLDER_THREAD_ID,
220
+ });
221
+ expect(state.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
222
+ expect(state.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(0);
223
+ });
224
+ });
139
225
  describe("RUN_STARTED event", () => {
140
226
  it("updates thread status to streaming", () => {
141
227
  const state = createTestStreamState("thread_1");
@@ -201,14 +287,14 @@ describe("streamReducer", () => {
201
287
  event: runStartedEvent,
202
288
  threadId: realThreadId,
203
289
  });
204
- expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
205
- expect(result.currentThreadId).toBe("placeholder");
290
+ expect(result.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(0);
291
+ expect(result.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
206
292
  });
207
293
  it("migrates messages from placeholder thread to real thread", () => {
208
294
  // Start with initial state (which has placeholder thread)
209
295
  const state = createInitialState();
210
296
  // Verify placeholder thread exists
211
- expect(state.currentThreadId).toBe("placeholder");
297
+ expect(state.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
212
298
  // Add a user message to the placeholder thread
213
299
  const userMsgStart = {
214
300
  type: EventType.TEXT_MESSAGE_START,
@@ -227,22 +313,23 @@ describe("streamReducer", () => {
227
313
  let stateWithUserMsg = streamReducer(state, {
228
314
  type: "EVENT",
229
315
  event: userMsgStart,
230
- threadId: "placeholder",
316
+ threadId: PLACEHOLDER_THREAD_ID,
231
317
  });
232
318
  stateWithUserMsg = streamReducer(stateWithUserMsg, {
233
319
  type: "EVENT",
234
320
  event: userMsgContent,
235
- threadId: "placeholder",
321
+ threadId: PLACEHOLDER_THREAD_ID,
236
322
  });
237
323
  stateWithUserMsg = streamReducer(stateWithUserMsg, {
238
324
  type: "EVENT",
239
325
  event: userMsgEnd,
240
- threadId: "placeholder",
326
+ threadId: PLACEHOLDER_THREAD_ID,
241
327
  });
242
328
  // Verify placeholder thread has the message
243
- expect(stateWithUserMsg.currentThreadId).toBe("placeholder");
244
- expect(stateWithUserMsg.threadMap.placeholder.thread.messages).toHaveLength(1);
245
- expect(stateWithUserMsg.threadMap.placeholder.thread.messages[0].content[0]).toEqual({
329
+ expect(stateWithUserMsg.currentThreadId).toBe(PLACEHOLDER_THREAD_ID);
330
+ expect(stateWithUserMsg.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(1);
331
+ expect(stateWithUserMsg.threadMap[PLACEHOLDER_THREAD_ID].thread.messages[0]
332
+ .content[0]).toEqual({
246
333
  type: "text",
247
334
  text: "Hello",
248
335
  });
@@ -260,8 +347,8 @@ describe("streamReducer", () => {
260
347
  threadId: realThreadId,
261
348
  });
262
349
  // Placeholder thread should be reset to empty (not removed)
263
- expect(finalState.threadMap.placeholder).toBeDefined();
264
- expect(finalState.threadMap.placeholder.thread.messages).toHaveLength(0);
350
+ expect(finalState.threadMap[PLACEHOLDER_THREAD_ID]).toBeDefined();
351
+ expect(finalState.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(0);
265
352
  // Real thread should have the migrated user message
266
353
  expect(finalState.threadMap[realThreadId]).toBeDefined();
267
354
  expect(finalState.threadMap[realThreadId].thread.messages).toHaveLength(1);
@@ -302,17 +389,17 @@ describe("streamReducer", () => {
302
389
  state = streamReducer(state, {
303
390
  type: "EVENT",
304
391
  event: userMsgStart,
305
- threadId: "placeholder",
392
+ threadId: PLACEHOLDER_THREAD_ID,
306
393
  });
307
394
  state = streamReducer(state, {
308
395
  type: "EVENT",
309
396
  event: userMsgContent,
310
- threadId: "placeholder",
397
+ threadId: PLACEHOLDER_THREAD_ID,
311
398
  });
312
399
  state = streamReducer(state, {
313
400
  type: "EVENT",
314
401
  event: userMsgEnd,
315
- threadId: "placeholder",
402
+ threadId: PLACEHOLDER_THREAD_ID,
316
403
  });
317
404
  const realThreadId = "thread_real_123";
318
405
  const runStartedEvent = {
@@ -326,7 +413,7 @@ describe("streamReducer", () => {
326
413
  event: runStartedEvent,
327
414
  threadId: realThreadId,
328
415
  });
329
- expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
416
+ expect(result.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(0);
330
417
  expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
331
418
  expect(result.currentThreadId).toBe("thread_1");
332
419
  });
@@ -351,17 +438,17 @@ describe("streamReducer", () => {
351
438
  let stateWithUserMsg = streamReducer(state, {
352
439
  type: "EVENT",
353
440
  event: userMsgStart,
354
- threadId: "placeholder",
441
+ threadId: PLACEHOLDER_THREAD_ID,
355
442
  });
356
443
  stateWithUserMsg = streamReducer(stateWithUserMsg, {
357
444
  type: "EVENT",
358
445
  event: userMsgContent,
359
- threadId: "placeholder",
446
+ threadId: PLACEHOLDER_THREAD_ID,
360
447
  });
361
448
  stateWithUserMsg = streamReducer(stateWithUserMsg, {
362
449
  type: "EVENT",
363
450
  event: userMsgEnd,
364
- threadId: "placeholder",
451
+ threadId: PLACEHOLDER_THREAD_ID,
365
452
  });
366
453
  const realThreadId = "thread_real_123";
367
454
  const runStartedEvent = {
@@ -373,9 +460,9 @@ describe("streamReducer", () => {
373
460
  const result = streamReducer(stateWithUserMsg, {
374
461
  type: "EVENT",
375
462
  event: runStartedEvent,
376
- threadId: "placeholder",
463
+ threadId: PLACEHOLDER_THREAD_ID,
377
464
  });
378
- expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
465
+ expect(result.threadMap[PLACEHOLDER_THREAD_ID].thread.messages).toHaveLength(0);
379
466
  expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
380
467
  expect(result.currentThreadId).toBe(realThreadId);
381
468
  });