1- import { afterAll , describe , expect , it , vi } from 'vitest'
1+ import { afterAll , beforeEach , describe , expect , it , vi } from 'vitest'
22import { mergeToolParameters } from '@/tools/merge-params'
33import * as toolMetadata from '@/tools/metadata'
44import {
@@ -18,6 +18,17 @@ import {
1818} from '@/tools/params'
1919import type { HttpMethod , ParameterVisibility } from '@/tools/types'
2020
21+ const { mockBuildExecutorDelegationHeaders } = vi . hoisted ( ( ) => ( {
22+ mockBuildExecutorDelegationHeaders : vi
23+ . fn ( )
24+ . mockResolvedValue ( { Authorization : 'Bearer delegation-token' } ) ,
25+ } ) )
26+
27+ vi . mock ( '@/executor/utils/http' , ( ) => ( {
28+ buildExecutorDelegationHeaders : mockBuildExecutorDelegationHeaders ,
29+ buildAPIUrl : ( path : string ) => new URL ( path , 'http://localhost:3000' ) ,
30+ } ) )
31+
2132const mockToolConfig = {
2233 id : 'test_tool' ,
2334 name : 'Test Tool' ,
@@ -648,6 +659,104 @@ describe('Tool Parameters Utils', () => {
648659 } )
649660 } )
650661
662+ describe ( 'createLLMToolSchema - child workflow input enrichment' , ( ) => {
663+ const childWorkflowPayload = {
664+ data : {
665+ state : {
666+ blocks : {
667+ 'block-1' : {
668+ type : 'starter' ,
669+ subBlocks : {
670+ inputFormat : {
671+ value : [
672+ { name : 'email' , type : 'string' , description : 'Recipient address' } ,
673+ { name : 'attempts' , type : 'number' } ,
674+ ] ,
675+ } ,
676+ } ,
677+ } ,
678+ } ,
679+ } ,
680+ } ,
681+ }
682+
683+ const mockFetch = vi . fn ( )
684+
685+ beforeEach ( ( ) => {
686+ mockBuildExecutorDelegationHeaders . mockClear ( )
687+ mockFetch . mockReset ( )
688+ mockFetch . mockResolvedValue (
689+ new Response ( JSON . stringify ( childWorkflowPayload ) , {
690+ status : 200 ,
691+ headers : { 'Content-Type' : 'application/json' } ,
692+ } )
693+ )
694+ // The suite runs with `unstubGlobals`, which restores globals between tests.
695+ vi . stubGlobal ( 'fetch' , mockFetch )
696+ } )
697+
698+ it ( 'binds the delegation to the execution subject and the target workflow' , async ( ) => {
699+ const { schema } = await createLLMToolSchema (
700+ mockWorkflowExecutorConfig ,
701+ { workflowId : 'child-workflow' } ,
702+ {
703+ userId : 'user-1' ,
704+ workflowId : 'parent-workflow' ,
705+ executionId : 'execution-1' ,
706+ workspaceId : 'workspace-1' ,
707+ }
708+ )
709+
710+ expect ( mockBuildExecutorDelegationHeaders ) . toHaveBeenCalledWith ( {
711+ subjectUserId : 'user-1' ,
712+ workflowId : 'child-workflow' ,
713+ } )
714+ expect ( schema . properties . inputMapping . properties ) . toEqual ( {
715+ email : { type : 'string' , description : 'Recipient address' } ,
716+ attempts : { type : 'number' , description : 'Input field: attempts' } ,
717+ } )
718+ expect ( schema . properties . inputMapping . required ) . toEqual ( [ 'email' , 'attempts' ] )
719+ } )
720+
721+ it ( 'carries the executionId when the target is the running workflow' , async ( ) => {
722+ await createLLMToolSchema (
723+ mockWorkflowExecutorConfig ,
724+ { workflowId : 'parent-workflow' } ,
725+ { userId : 'user-1' , workflowId : 'parent-workflow' , executionId : 'execution-1' }
726+ )
727+
728+ expect ( mockBuildExecutorDelegationHeaders ) . toHaveBeenCalledWith ( {
729+ subjectUserId : 'user-1' ,
730+ workflowId : 'parent-workflow' ,
731+ executionId : 'execution-1' ,
732+ } )
733+ } )
734+
735+ it ( 'leaves inputMapping untyped and issues no request without an execution subject' , async ( ) => {
736+ const { schema } = await createLLMToolSchema (
737+ mockWorkflowExecutorConfig ,
738+ { workflowId : 'child-workflow' } ,
739+ { workflowId : 'parent-workflow' , executionId : 'execution-1' }
740+ )
741+
742+ expect ( mockBuildExecutorDelegationHeaders ) . not . toHaveBeenCalled ( )
743+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
744+ expect ( schema . properties . inputMapping . properties ) . toBeUndefined ( )
745+ } )
746+
747+ it ( 'leaves inputMapping untyped when the workflow read is rejected' , async ( ) => {
748+ mockFetch . mockResolvedValue ( new Response ( 'Unauthorized' , { status : 401 } ) )
749+
750+ const { schema } = await createLLMToolSchema (
751+ mockWorkflowExecutorConfig ,
752+ { workflowId : 'child-workflow' } ,
753+ { userId : 'user-1' , workflowId : 'parent-workflow' }
754+ )
755+
756+ expect ( schema . properties . inputMapping . properties ) . toBeUndefined ( )
757+ } )
758+ } )
759+
651760 describe ( 'mergeToolParameters - inputMapping deep merge' , ( ) => {
652761 it . concurrent ( 'should deep merge inputMapping when user provides empty object' , ( ) => {
653762 const userProvided = {
0 commit comments