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