|
33 | 33 | AttachmentsDeleteReq, |
34 | 34 | AttachmentUploadDescriptor, |
35 | 35 | ClientToolCallItem, |
| 36 | + CustomTask, |
36 | 37 | FeedbackKind, |
37 | 38 | FileAttachment, |
38 | 39 | ImageAttachment, |
|
44 | 45 | LockedStatus, |
45 | 46 | Page, |
46 | 47 | ProgressUpdateEvent, |
| 48 | + ThoughtTask, |
47 | 49 | Thread, |
48 | 50 | ThreadAddClientToolOutputParams, |
49 | 51 | ThreadAddUserMessageParams, |
|
79 | 81 | UserMessageTextContent, |
80 | 82 | WidgetItem, |
81 | 83 | WidgetRootUpdated, |
| 84 | + Workflow, |
| 85 | + WorkflowItem, |
| 86 | + WorkflowTaskAdded, |
82 | 87 | ) |
83 | 88 | from chatkit.widgets import Card, Text |
84 | 89 | from tests._types import RequestContext |
@@ -354,6 +359,65 @@ def generate_item_id( |
354 | 359 | ) |
355 | 360 |
|
356 | 361 |
|
| 362 | +async def test_workflow_task_not_duplicated_on_done_event(): |
| 363 | + """ |
| 364 | + Regression test to make sure pending item updates do not modify the |
| 365 | + origin item that was streamed. |
| 366 | + """ |
| 367 | + |
| 368 | + async def responder( |
| 369 | + thread: ThreadMetadata, input: UserMessageItem | None, context: Any |
| 370 | + ) -> AsyncIterator[ThreadStreamEvent]: |
| 371 | + workflow_item = WorkflowItem( |
| 372 | + id="workflow-item", |
| 373 | + created_at=datetime.now(), |
| 374 | + thread_id=thread.id, |
| 375 | + workflow=Workflow(type="custom", tasks=[]), |
| 376 | + ) |
| 377 | + |
| 378 | + yield ThreadItemAddedEvent(item=workflow_item) |
| 379 | + |
| 380 | + task = CustomTask(title="foo", content="bar") |
| 381 | + yield ThreadItemUpdatedEvent( |
| 382 | + item_id=workflow_item.id, |
| 383 | + update=WorkflowTaskAdded(task=task, task_index=0), |
| 384 | + ) |
| 385 | + |
| 386 | + workflow_item.workflow.tasks.append(task) |
| 387 | + yield ThreadItemDoneEvent(item=workflow_item) |
| 388 | + |
| 389 | + with make_server(responder) as server: |
| 390 | + events = await server.process_streaming( |
| 391 | + ThreadsCreateReq( |
| 392 | + params=ThreadCreateParams( |
| 393 | + input=UserMessageInput( |
| 394 | + content=[UserMessageTextContent(text="Hello")], |
| 395 | + attachments=[], |
| 396 | + inference_options=InferenceOptions(), |
| 397 | + ) |
| 398 | + ) |
| 399 | + ) |
| 400 | + ) |
| 401 | + |
| 402 | + thread = next(e.thread for e in events if e.type == "thread.created") |
| 403 | + workflow_done_event = next( |
| 404 | + e |
| 405 | + for e in events |
| 406 | + if isinstance(e, ThreadItemDoneEvent) and isinstance(e.item, WorkflowItem) |
| 407 | + ) |
| 408 | + workflow_done_item = cast(WorkflowItem, workflow_done_event.item) |
| 409 | + assert len(workflow_done_item.workflow.tasks) == 1 |
| 410 | + assert workflow_done_item.workflow.tasks[0].title == "foo" |
| 411 | + |
| 412 | + stored = await server.store.load_item( |
| 413 | + thread.id, workflow_done_item.id, DEFAULT_CONTEXT |
| 414 | + ) |
| 415 | + assert isinstance(stored, WorkflowItem) |
| 416 | + stored_workflow = cast(WorkflowItem, stored) |
| 417 | + assert len(stored_workflow.workflow.tasks) == 1 |
| 418 | + assert stored_workflow.workflow.tasks[0].title == "foo" |
| 419 | + |
| 420 | + |
357 | 421 | async def test_flows_context_to_responder(): |
358 | 422 | responder_context = None |
359 | 423 | add_feedback_context = None |
|
0 commit comments