@@ -741,3 +741,166 @@ describe("routed Responses-native V2 plaintext integration", () => {
741741 }
742742 } ) ;
743743} ) ;
744+
745+ function childMessageNamespace ( extra : Record < string , unknown > [ ] = [ ] ) : Record < string , unknown > {
746+ return {
747+ type : "namespace" ,
748+ name : "collaboration" ,
749+ description : "V2 child collaboration" ,
750+ tools : [ messageTool ( "send_message" ) , messageTool ( "followup_task" ) , ...extra ] ,
751+ } ;
752+ }
753+
754+ function routedChildBody ( stream : boolean , namespace = childMessageNamespace ( ) ) : Record < string , unknown > {
755+ return {
756+ model : "responses-native/v4" ,
757+ stream,
758+ input : [
759+ {
760+ type : "additional_tools" ,
761+ role : "developer" ,
762+ tools : [ { type : "custom" , name : "exec_command" } , namespace ] ,
763+ } ,
764+ { type : "message" , role : "user" , content : [ { type : "input_text" , text : "report back" } ] } ,
765+ ] ,
766+ } ;
767+ }
768+
769+ function childSendMessageSse ( ) : string {
770+ return completedSse ( "collaboration" ) . replaceAll ( '"name":"spawn_agent"' , '"name":"send_message"' ) ;
771+ }
772+
773+ function sseFunctionCalls ( text : string ) : Record < string , unknown > [ ] {
774+ return text
775+ . split ( / \r ? \n / )
776+ . filter ( line => line . startsWith ( "data: {" ) )
777+ . map ( line => JSON . parse ( line . slice ( 6 ) ) as Record < string , unknown > )
778+ . flatMap ( payload => {
779+ const direct = ( payload as { item ?: Record < string , unknown > } ) . item ;
780+ const output = ( payload as { response ?: { output ?: Record < string , unknown > [ ] } } ) . response ?. output ;
781+ return [ ...( direct ? [ direct ] : [ ] ) , ...( output ?? [ ] ) ] ;
782+ } )
783+ . filter ( item => item . type === "function_call" ) ;
784+ }
785+
786+ describe ( "routed V2 child with message-only collaboration tools" , ( ) => {
787+ test ( "marks streamed send_message without spawn_agent and suppresses usage-debug samples" , async ( ) => {
788+ globalThis . fetch = ( async ( ) => new Response ( childSendMessageSse ( ) , {
789+ status : 200 ,
790+ headers : { "content-type" : "text/event-stream" } ,
791+ } ) ) as typeof fetch ;
792+ const logCtx : RequestLogContext = { model : "" , provider : "" } ;
793+ const response = await handleResponses ( new Request ( "http://localhost/v1/responses" , {
794+ method : "POST" ,
795+ headers : { "content-type" : "application/json" } ,
796+ body : JSON . stringify ( routedChildBody ( true ) ) ,
797+ } ) , routedResponsesConfig ( "plaintext" ) , logCtx ) ;
798+
799+ expect ( response . status ) . toBe ( 200 ) ;
800+ const items = sseFunctionCalls ( await response . text ( ) ) ;
801+ expect ( items . length ) . toBeGreaterThanOrEqual ( 3 ) ;
802+ for ( const item of items ) {
803+ expect ( item ) . toMatchObject ( { namespace : "collaboration" , name : "send_message" , encrypted_function_args : [ ] } ) ;
804+ }
805+ expect ( logCtx . suppressUsageDebugBodySample ) . toBe ( true ) ;
806+ } ) ;
807+
808+ test ( "marks non-streamed followup_task without spawn_agent" , async ( ) => {
809+ globalThis . fetch = ( async ( ) => Response . json ( completedJson ( "collaboration" ) ) ) as typeof fetch ;
810+ const logCtx : RequestLogContext = { model : "" , provider : "" } ;
811+ const response = await handleResponses ( new Request ( "http://localhost/v1/responses" , {
812+ method : "POST" ,
813+ headers : { "content-type" : "application/json" } ,
814+ body : JSON . stringify ( routedChildBody ( false ) ) ,
815+ } ) , routedResponsesConfig ( "plaintext" ) , logCtx ) ;
816+
817+ expect ( response . status ) . toBe ( 200 ) ;
818+ const client = await response . json ( ) as { output : Record < string , unknown > [ ] } ;
819+ expect ( client . output [ 0 ] ) . toMatchObject ( {
820+ namespace : "collaboration" ,
821+ name : "followup_task" ,
822+ encrypted_function_args : [ ] ,
823+ } ) ;
824+ expect ( logCtx . suppressUsageDebugBodySample ) . toBe ( true ) ;
825+ } ) ;
826+
827+ test ( "keeps V1 companion tools excluded" , async ( ) => {
828+ globalThis . fetch = ( async ( ) => Response . json ( completedJson ( "collaboration" ) ) ) as typeof fetch ;
829+ const logCtx : RequestLogContext = { model : "" , provider : "" } ;
830+ const v1Namespace = childMessageNamespace ( [
831+ { type : "function" , name : "send_input" , parameters : { type : "object" , properties : { } } } ,
832+ ] ) ;
833+ const response = await handleResponses ( new Request ( "http://localhost/v1/responses" , {
834+ method : "POST" ,
835+ headers : { "content-type" : "application/json" } ,
836+ body : JSON . stringify ( routedChildBody ( false , v1Namespace ) ) ,
837+ } ) , routedResponsesConfig ( "plaintext" ) , logCtx ) ;
838+
839+ expect ( response . status ) . toBe ( 200 ) ;
840+ const client = await response . json ( ) as { output : Record < string , unknown > [ ] } ;
841+ expect ( client . output [ 0 ] ) . not . toHaveProperty ( "encrypted_function_args" ) ;
842+ expect ( logCtx . suppressUsageDebugBodySample ) . toBeUndefined ( ) ;
843+ } ) ;
844+ } ) ;
845+
846+ describe ( "native plaintext agent_message replay repair" , ( ) => {
847+ test ( "rewrites prose in agent_message encrypted slots and leaves Fernet untouched" , async ( ) => {
848+ const prose = "Worker finished: see notes, ready for review." ;
849+ const encryptedTask = fernetFixture ( ) ;
850+ let upstreamBody = "" ;
851+ globalThis . fetch = ( async ( _input , init ) => {
852+ upstreamBody = typeof init ?. body === "string" ? init . body : "" ;
853+ return Response . json ( completedJson ( V2_PLAINTEXT_COLLABORATION_NAMESPACE ) ) ;
854+ } ) as typeof fetch ;
855+ const body = nativeV2Body ( false ) ;
856+ body . input = [
857+ ...( body . input as unknown [ ] ) ,
858+ {
859+ type : "agent_message" ,
860+ id : "amsg_plain" ,
861+ author : "/root/media_worker" ,
862+ recipient : "/root" ,
863+ content : [
864+ { type : "input_text" , text : "Message from /root/media_worker:" } ,
865+ { type : "encrypted_content" , encrypted_content : prose } ,
866+ ] ,
867+ } ,
868+ {
869+ type : "agent_message" ,
870+ id : "amsg_fernet" ,
871+ author : "/root" ,
872+ recipient : "/root/worker" ,
873+ content : [ { type : "encrypted_content" , encrypted_content : encryptedTask } ] ,
874+ } ,
875+ ] ;
876+
877+ const response = await handleResponses ( new Request ( "http://localhost/v1/responses" , {
878+ method : "POST" ,
879+ headers : { "content-type" : "application/json" , authorization : "Bearer test" } ,
880+ body : JSON . stringify ( body ) ,
881+ } ) , nativeConfig ( "plaintext" ) , { model : "" , provider : "" } ) ;
882+ expect ( response . status ) . toBe ( 200 ) ;
883+ await response . text ( ) ;
884+
885+ const upstream = JSON . parse ( upstreamBody ) as { input : Record < string , unknown > [ ] } ;
886+ const plain = upstream . input . find ( item => item . id === "amsg_plain" ) ;
887+ expect ( plain ) . toEqual ( {
888+ type : "agent_message" ,
889+ id : "amsg_plain" ,
890+ author : "/root/media_worker" ,
891+ recipient : "/root" ,
892+ content : [
893+ { type : "input_text" , text : "Message from /root/media_worker:" } ,
894+ { type : "input_text" , text : prose } ,
895+ ] ,
896+ } ) ;
897+ const fernet = upstream . input . find ( item => item . id === "amsg_fernet" ) ;
898+ expect ( fernet ) . toEqual ( {
899+ type : "agent_message" ,
900+ id : "amsg_fernet" ,
901+ author : "/root" ,
902+ recipient : "/root/worker" ,
903+ content : [ { type : "encrypted_content" , encrypted_content : encryptedTask } ] ,
904+ } ) ;
905+ } ) ;
906+ } ) ;
0 commit comments