11import { beforeEach , describe , expect , it , vi } from "vitest" ;
22import { Card } from "./cards" ;
3+ import type { Message } from "./message" ;
34import {
45 createMockAdapter ,
56 createMockState ,
@@ -9,7 +10,7 @@ import {
910import { Plan } from "./plan" ;
1011import { StreamingPlan } from "./streaming-plan" ;
1112import { ThreadImpl } from "./thread" ;
12- import type { Adapter , Message , ScheduledMessage , StreamChunk } from "./types" ;
13+ import type { Adapter , ScheduledMessage , StreamChunk } from "./types" ;
1314import { NotImplementedError } from "./types" ;
1415
1516describe ( "ThreadImpl" , ( ) => {
@@ -595,36 +596,56 @@ describe("ThreadImpl", () => {
595596 }
596597 } ) ;
597598
598- it ( "should pass stream options from current message context" , async ( ) => {
599+ it . each ( [
600+ {
601+ expectedTeamId : "T123" ,
602+ label : "team_id" ,
603+ raw : { team_id : "T123" , type : "app_mention" } ,
604+ } ,
605+ {
606+ expectedTeamId : "T234" ,
607+ label : "team string" ,
608+ raw : { team : "T234" , type : "message" } ,
609+ } ,
610+ {
611+ expectedTeamId : "T345" ,
612+ label : "team.id" ,
613+ raw : { team : { id : "T345" } , type : "block_actions" } ,
614+ } ,
615+ {
616+ expectedTeamId : "T456" ,
617+ label : "user.team_id fallback" ,
618+ raw : {
619+ type : "block_actions" ,
620+ user : { team_id : "T456" } ,
621+ } ,
622+ } ,
623+ ] ) ( "should pass stream options from Slack current message context via $label" , async ( {
624+ raw,
625+ expectedTeamId,
626+ } ) => {
599627 const mockStream = vi . fn ( ) . mockResolvedValue ( {
600628 id : "msg-stream" ,
601629 threadId : "t1" ,
602630 raw : "Hello" ,
603631 } ) ;
604632 mockAdapter . stream = mockStream ;
605633
606- // Create thread with current message context
607634 const threadWithContext = new ThreadImpl ( {
608635 id : "slack:C123:1234.5678" ,
609636 adapter : mockAdapter ,
610637 channelId : "C123" ,
611638 stateAdapter : mockState ,
612- currentMessage : {
613- id : "original-msg" ,
614- threadId : "slack:C123:1234.5678" ,
615- text : "test" ,
616- formatted : { type : "root" , children : [ ] } ,
617- raw : { team_id : "T123" } ,
639+ currentMessage : createTestMessage ( "original-msg" , "test" , {
640+ raw,
618641 author : {
619642 userId : "U456" ,
620643 userName : "user" ,
621644 fullName : "Test User" ,
622645 isBot : false ,
623646 isMe : false ,
624647 } ,
625- metadata : { dateSent : new Date ( ) , edited : false } ,
626- attachments : [ ] ,
627- } ,
648+ } ) ,
628649 } ) ;
629650
630651 const textStream = createTextStream ( [ "Hello" ] ) ;
@@ -635,11 +656,66 @@ describe("ThreadImpl", () => {
635656 expect . any ( Object ) ,
636657 expect . objectContaining ( {
637658 recipientUserId : "U456" ,
638- recipientTeamId : "T123" ,
659+ recipientTeamId : expectedTeamId ,
639660 } )
640661 ) ;
641662 } ) ;
642663
664+ it ( "should forward structured stream chunks to adapter.stream from an action-created thread" , async ( ) => {
665+ const mockStream = vi . fn ( ) . mockResolvedValue ( {
666+ id : "msg-stream" ,
667+ threadId : "t1" ,
668+ raw : "Hello" ,
669+ } ) ;
670+ mockAdapter . stream = mockStream ;
671+
672+ const threadWithActionContext = new ThreadImpl ( {
673+ id : "slack:C123:1234.5678" ,
674+ adapter : mockAdapter ,
675+ channelId : "C123" ,
676+ stateAdapter : mockState ,
677+ currentMessage : createTestMessage ( "action-msg" , "" , {
678+ raw : {
679+ team : { domain : "workspace" , id : "T123" } ,
680+ type : "block_actions" ,
681+ } ,
682+ author : {
683+ userId : "U456" ,
684+ userName : "user" ,
685+ fullName : "Test User" ,
686+ isBot : false ,
687+ isMe : false ,
688+ } ,
689+ } ) ,
690+ } ) ;
691+
692+ const taskChunk : StreamChunk = {
693+ id : "task-1" ,
694+ status : "pending" ,
695+ title : "Thinking" ,
696+ type : "task_update" ,
697+ } ;
698+ async function * structuredStream ( ) : AsyncIterable < string | StreamChunk > {
699+ yield "Picking option..." ;
700+ yield taskChunk ;
701+ }
702+
703+ await threadWithActionContext . post (
704+ structuredStream ( ) as unknown as AsyncIterable < string >
705+ ) ;
706+
707+ expect ( mockStream ) . toHaveBeenCalledTimes ( 1 ) ;
708+ const [ , passedStream ] = mockStream . mock . calls [ 0 ] ;
709+ const collected : Array < string | StreamChunk > = [ ] ;
710+ for await ( const chunk of passedStream as AsyncIterable <
711+ string | StreamChunk
712+ > ) {
713+ collected . push ( chunk ) ;
714+ }
715+ expect ( collected ) . toContain ( "Picking option..." ) ;
716+ expect ( collected ) . toContainEqual ( taskChunk ) ;
717+ } ) ;
718+
643719 it ( "should pass StreamingPlan PostableObject options to adapter.stream" , async ( ) => {
644720 const mockStream = vi . fn ( ) . mockResolvedValue ( {
645721 id : "msg-stream" ,
0 commit comments