@@ -2973,6 +2973,130 @@ describe('Cost Field Handling', () => {
29732973 Object . assign ( tools , originalTools )
29742974 } )
29752975
2976+ it ( 'emits _serviceCost for copilot executions using a hosted key' , async ( ) => {
2977+ const mockTool = {
2978+ id : 'test_copilot_hosted_cost' ,
2979+ name : 'Test Copilot Hosted Cost' ,
2980+ description : 'A hosted-key tool invoked by the copilot' ,
2981+ version : '1.0.0' ,
2982+ params : {
2983+ apiKey : { type : 'string' , required : false } ,
2984+ } ,
2985+ hosting : {
2986+ envKeyPrefix : 'TEST_HOSTED_KEY' ,
2987+ apiKeyParam : 'apiKey' ,
2988+ byokProviderId : 'exa' ,
2989+ pricing : {
2990+ type : 'per_request' as const ,
2991+ cost : 0.005 ,
2992+ } ,
2993+ rateLimit : {
2994+ mode : 'per_request' as const ,
2995+ requestsPerMinute : 100 ,
2996+ } ,
2997+ } ,
2998+ request : {
2999+ url : '/api/test/copilot-cost' ,
3000+ method : 'POST' as const ,
3001+ headers : ( ) => ( { 'Content-Type' : 'application/json' } ) ,
3002+ } ,
3003+ transformResponse : vi . fn ( ) . mockResolvedValue ( {
3004+ success : true ,
3005+ output : { result : 'success' } ,
3006+ } ) ,
3007+ }
3008+
3009+ const originalTools = { ...tools }
3010+ ; ( tools as any ) . test_copilot_hosted_cost = mockTool
3011+
3012+ global . fetch = Object . assign (
3013+ vi . fn ( ) . mockImplementation ( async ( ) => ( {
3014+ ok : true ,
3015+ status : 200 ,
3016+ headers : new Headers ( ) ,
3017+ json : ( ) => Promise . resolve ( { success : true } ) ,
3018+ } ) ) ,
3019+ { preconnect : vi . fn ( ) }
3020+ ) as typeof fetch
3021+
3022+ // Copilot flow: no executionContext option; scope comes from _context,
3023+ // which the copilot tool-executor stamps with copilotToolExecution.
3024+ const result = await executeTool ( 'test_copilot_hosted_cost' , {
3025+ _context : {
3026+ userId : 'user-123' ,
3027+ workspaceId : 'workspace-456' ,
3028+ copilotToolExecution : true ,
3029+ } ,
3030+ } )
3031+
3032+ expect ( result . success ) . toBe ( true )
3033+ expect ( result . output . cost ) . toEqual ( { total : 0.005 } )
3034+ expect ( result . output . _serviceCost ) . toEqual ( { service : 'exa' , cost : 0.005 } )
3035+
3036+ Object . assign ( tools , originalTools )
3037+ } )
3038+
3039+ it ( 'does not emit _serviceCost for workflow executions using a hosted key' , async ( ) => {
3040+ const mockTool = {
3041+ id : 'test_workflow_hosted_cost' ,
3042+ name : 'Test Workflow Hosted Cost' ,
3043+ description : 'A hosted-key tool invoked by a workflow' ,
3044+ version : '1.0.0' ,
3045+ params : {
3046+ apiKey : { type : 'string' , required : false } ,
3047+ } ,
3048+ hosting : {
3049+ envKeyPrefix : 'TEST_HOSTED_KEY' ,
3050+ apiKeyParam : 'apiKey' ,
3051+ pricing : {
3052+ type : 'per_request' as const ,
3053+ cost : 0.005 ,
3054+ } ,
3055+ rateLimit : {
3056+ mode : 'per_request' as const ,
3057+ requestsPerMinute : 100 ,
3058+ } ,
3059+ } ,
3060+ request : {
3061+ url : '/api/test/workflow-cost' ,
3062+ method : 'POST' as const ,
3063+ headers : ( ) => ( { 'Content-Type' : 'application/json' } ) ,
3064+ } ,
3065+ transformResponse : vi . fn ( ) . mockResolvedValue ( {
3066+ success : true ,
3067+ output : { result : 'success' } ,
3068+ } ) ,
3069+ }
3070+
3071+ const originalTools = { ...tools }
3072+ ; ( tools as any ) . test_workflow_hosted_cost = mockTool
3073+
3074+ global . fetch = Object . assign (
3075+ vi . fn ( ) . mockImplementation ( async ( ) => ( {
3076+ ok : true ,
3077+ status : 200 ,
3078+ headers : new Headers ( ) ,
3079+ json : ( ) => Promise . resolve ( { success : true } ) ,
3080+ } ) ) ,
3081+ { preconnect : vi . fn ( ) }
3082+ ) as typeof fetch
3083+
3084+ const mockContext = createToolExecutionContext ( { userId : 'user-123' } as any )
3085+ const result = await executeTool (
3086+ 'test_workflow_hosted_cost' ,
3087+ { } ,
3088+ { executionContext : mockContext }
3089+ )
3090+
3091+ expect ( result . success ) . toBe ( true )
3092+ // Workflow executions bill through the execution ledger; emitting
3093+ // _serviceCost here would double-bill via Go's service-charge path.
3094+ expect ( result . output . cost ) . toEqual ( { total : 0.005 } )
3095+ expect ( result . output . _serviceCost ) . toBeUndefined ( )
3096+
3097+ Object . assign ( tools , originalTools )
3098+ } )
3099+
29763100 it ( 'should use custom pricing getCost function' , async ( ) => {
29773101 const mockGetCost = vi . fn ( ) . mockReturnValue ( {
29783102 cost : 0.015 ,
0 commit comments