@@ -46,9 +46,8 @@ import type {
4646 ResolvedModelConfig ,
4747} from '../services/modelConfigService.js' ;
4848import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js' ;
49- import { HookSystem } from '../hooks/hookSystem.js' ;
50- import type { DefaultHookOutput } from '../hooks/types.js' ;
5149import * as policyCatalog from '../availability/policyCatalog.js' ;
50+ import { partToString } from '../utils/partUtils.js' ;
5251
5352vi . mock ( '../services/chatCompressionService.js' ) ;
5453
@@ -137,15 +136,22 @@ vi.mock('../telemetry/uiTelemetry.js', () => ({
137136 } ,
138137} ) ) ;
139138vi . mock ( '../hooks/hookSystem.js' ) ;
140- vi . mock ( './clientHookTriggers.js' , ( ) => ( {
141- fireBeforeAgentHook : vi . fn ( ) ,
142- fireAfterAgentHook : vi . fn ( ) . mockResolvedValue ( {
143- decision : 'allow' ,
144- continue : false ,
145- suppressOutput : false ,
146- systemMessage : undefined ,
139+ const mockHookSystem = {
140+ fireBeforeAgentEvent : vi . fn ( ) . mockResolvedValue ( {
141+ success : true ,
142+ finalOutput : undefined ,
143+ allOutputs : [ ] ,
144+ errors : [ ] ,
145+ totalDuration : 0 ,
147146 } ) ,
148- } ) ) ;
147+ fireAfterAgentEvent : vi . fn ( ) . mockResolvedValue ( {
148+ success : true ,
149+ finalOutput : undefined ,
150+ allOutputs : [ ] ,
151+ errors : [ ] ,
152+ totalDuration : 0 ,
153+ } ) ,
154+ } ;
149155
150156/**
151157 * Array.fromAsync ponyfill, which will be available in es 2024.
@@ -286,9 +292,7 @@ describe('Gemini Client (client.ts)', () => {
286292 . fn ( )
287293 . mockReturnValue ( createAvailabilityServiceMock ( ) ) ,
288294 } as unknown as Config ;
289- mockConfig . getHookSystem = vi
290- . fn ( )
291- . mockReturnValue ( new HookSystem ( mockConfig ) ) ;
295+ mockConfig . getHookSystem = vi . fn ( ) . mockReturnValue ( mockHookSystem ) ;
292296
293297 client = new GeminiClient ( mockConfig ) ;
294298 await client . initialize ( ) ;
@@ -2688,9 +2692,6 @@ ${JSON.stringify(
26882692 const promptId = 'test-prompt-hook-1' ;
26892693 const request = { text : 'Hello Hooks' } ;
26902694 const signal = new AbortController ( ) . signal ;
2691- const { fireBeforeAgentHook, fireAfterAgentHook } = await import (
2692- './clientHookTriggers.js'
2693- ) ;
26942695
26952696 mockTurnRunFn . mockImplementation ( async function * (
26962697 this : MockTurnContext ,
@@ -2702,11 +2703,10 @@ ${JSON.stringify(
27022703 const stream = client . sendMessageStream ( request , signal , promptId ) ;
27032704 while ( ! ( await stream . next ( ) ) . done ) ;
27042705
2705- expect ( fireBeforeAgentHook ) . toHaveBeenCalledTimes ( 1 ) ;
2706- expect ( fireAfterAgentHook ) . toHaveBeenCalledTimes ( 1 ) ;
2707- expect ( fireAfterAgentHook ) . toHaveBeenCalledWith (
2708- expect . anything ( ) ,
2709- request ,
2706+ expect ( mockHookSystem . fireBeforeAgentEvent ) . toHaveBeenCalledTimes ( 1 ) ;
2707+ expect ( mockHookSystem . fireAfterAgentEvent ) . toHaveBeenCalledTimes ( 1 ) ;
2708+ expect ( mockHookSystem . fireAfterAgentEvent ) . toHaveBeenCalledWith (
2709+ partToString ( request ) ,
27102710 'Hook Response' ,
27112711 ) ;
27122712
@@ -2725,9 +2725,6 @@ ${JSON.stringify(
27252725 const promptId = 'test-prompt-hook-recursive' ;
27262726 const request = { text : 'Recursion Test' } ;
27272727 const signal = new AbortController ( ) . signal ;
2728- const { fireBeforeAgentHook, fireAfterAgentHook } = await import (
2729- './clientHookTriggers.js'
2730- ) ;
27312728
27322729 let callCount = 0 ;
27332730 mockTurnRunFn . mockImplementation ( async function * (
@@ -2743,15 +2740,14 @@ ${JSON.stringify(
27432740 while ( ! ( await stream . next ( ) ) . done ) ;
27442741
27452742 // BeforeAgent should fire ONLY once despite multiple internal turns
2746- expect ( fireBeforeAgentHook ) . toHaveBeenCalledTimes ( 1 ) ;
2743+ expect ( mockHookSystem . fireBeforeAgentEvent ) . toHaveBeenCalledTimes ( 1 ) ;
27472744
27482745 // AfterAgent should fire ONLY when the stack unwinds
2749- expect ( fireAfterAgentHook ) . toHaveBeenCalledTimes ( 1 ) ;
2746+ expect ( mockHookSystem . fireAfterAgentEvent ) . toHaveBeenCalledTimes ( 1 ) ;
27502747
27512748 // Check cumulative response (separated by newline)
2752- expect ( fireAfterAgentHook ) . toHaveBeenCalledWith (
2753- expect . anything ( ) ,
2754- request ,
2749+ expect ( mockHookSystem . fireAfterAgentEvent ) . toHaveBeenCalledWith (
2750+ partToString ( request ) ,
27552751 'Response 1\nResponse 2' ,
27562752 ) ;
27572753
@@ -2769,7 +2765,6 @@ ${JSON.stringify(
27692765 const promptId = 'test-prompt-hook-original-req' ;
27702766 const request = { text : 'Do something' } ;
27712767 const signal = new AbortController ( ) . signal ;
2772- const { fireAfterAgentHook } = await import ( './clientHookTriggers.js' ) ;
27732768
27742769 mockTurnRunFn . mockImplementation ( async function * (
27752770 this : MockTurnContext ,
@@ -2781,9 +2776,8 @@ ${JSON.stringify(
27812776 const stream = client . sendMessageStream ( request , signal , promptId ) ;
27822777 while ( ! ( await stream . next ( ) ) . done ) ;
27832778
2784- expect ( fireAfterAgentHook ) . toHaveBeenCalledWith (
2785- expect . anything ( ) ,
2786- request , // Should be 'Do something'
2779+ expect ( mockHookSystem . fireAfterAgentEvent ) . toHaveBeenCalledWith (
2780+ partToString ( request ) , // Should be 'Do something'
27872781 expect . stringContaining ( 'Ok' ) ,
27882782 ) ;
27892783 } ) ;
@@ -2817,11 +2811,17 @@ ${JSON.stringify(
28172811 } ) ;
28182812
28192813 it ( 'should stop execution in BeforeAgent when hook returns continue: false' , async ( ) => {
2820- const { fireBeforeAgentHook } = await import ( './clientHookTriggers.js' ) ;
2821- vi . mocked ( fireBeforeAgentHook ) . mockResolvedValue ( {
2822- shouldStopExecution : ( ) => true ,
2823- getEffectiveReason : ( ) => 'Stopped by hook' ,
2824- } as DefaultHookOutput ) ;
2814+ mockHookSystem . fireBeforeAgentEvent . mockResolvedValue ( {
2815+ success : true ,
2816+ finalOutput : {
2817+ shouldStopExecution : ( ) => true ,
2818+ getEffectiveReason : ( ) => 'Stopped by hook' ,
2819+ systemMessage : undefined ,
2820+ } ,
2821+ allOutputs : [ ] ,
2822+ errors : [ ] ,
2823+ totalDuration : 0 ,
2824+ } ) ;
28252825
28262826 const mockChat : Partial < GeminiChat > = {
28272827 addHistory : vi . fn ( ) ,
@@ -2850,12 +2850,18 @@ ${JSON.stringify(
28502850 } ) ;
28512851
28522852 it ( 'should block execution in BeforeAgent when hook returns decision: block' , async ( ) => {
2853- const { fireBeforeAgentHook } = await import ( './clientHookTriggers.js' ) ;
2854- vi . mocked ( fireBeforeAgentHook ) . mockResolvedValue ( {
2855- shouldStopExecution : ( ) => false ,
2856- isBlockingDecision : ( ) => true ,
2857- getEffectiveReason : ( ) => 'Blocked by hook' ,
2858- } as DefaultHookOutput ) ;
2853+ mockHookSystem . fireBeforeAgentEvent . mockResolvedValue ( {
2854+ success : true ,
2855+ finalOutput : {
2856+ shouldStopExecution : ( ) => false ,
2857+ isBlockingDecision : ( ) => true ,
2858+ getEffectiveReason : ( ) => 'Blocked by hook' ,
2859+ systemMessage : undefined ,
2860+ } ,
2861+ allOutputs : [ ] ,
2862+ errors : [ ] ,
2863+ totalDuration : 0 ,
2864+ } ) ;
28592865
28602866 const mockChat : Partial < GeminiChat > = {
28612867 addHistory : vi . fn ( ) ,
@@ -2883,11 +2889,17 @@ ${JSON.stringify(
28832889 } ) ;
28842890
28852891 it ( 'should stop execution in AfterAgent when hook returns continue: false' , async ( ) => {
2886- const { fireAfterAgentHook } = await import ( './clientHookTriggers.js' ) ;
2887- vi . mocked ( fireAfterAgentHook ) . mockResolvedValue ( {
2888- shouldStopExecution : ( ) => true ,
2889- getEffectiveReason : ( ) => 'Stopped after agent' ,
2890- } as DefaultHookOutput ) ;
2892+ mockHookSystem . fireAfterAgentEvent . mockResolvedValue ( {
2893+ success : true ,
2894+ finalOutput : {
2895+ shouldStopExecution : ( ) => true ,
2896+ getEffectiveReason : ( ) => 'Stopped after agent' ,
2897+ systemMessage : undefined ,
2898+ } ,
2899+ allOutputs : [ ] ,
2900+ errors : [ ] ,
2901+ totalDuration : 0 ,
2902+ } ) ;
28912903
28922904 mockTurnRunFn . mockImplementation ( async function * ( ) {
28932905 yield { type : GeminiEventType . Content , value : 'Hello' } ;
@@ -2909,17 +2921,30 @@ ${JSON.stringify(
29092921 } ) ;
29102922
29112923 it ( 'should yield AgentExecutionBlocked and recurse in AfterAgent when hook returns decision: block' , async ( ) => {
2912- const { fireAfterAgentHook } = await import ( './clientHookTriggers.js' ) ;
2913- vi . mocked ( fireAfterAgentHook )
2924+ mockHookSystem . fireAfterAgentEvent
29142925 . mockResolvedValueOnce ( {
2915- shouldStopExecution : ( ) => false ,
2916- isBlockingDecision : ( ) => true ,
2917- getEffectiveReason : ( ) => 'Please explain' ,
2918- } as DefaultHookOutput )
2926+ success : true ,
2927+ finalOutput : {
2928+ shouldStopExecution : ( ) => false ,
2929+ isBlockingDecision : ( ) => true ,
2930+ getEffectiveReason : ( ) => 'Please explain' ,
2931+ systemMessage : undefined ,
2932+ } ,
2933+ allOutputs : [ ] ,
2934+ errors : [ ] ,
2935+ totalDuration : 0 ,
2936+ } )
29192937 . mockResolvedValueOnce ( {
2920- shouldStopExecution : ( ) => false ,
2921- isBlockingDecision : ( ) => false ,
2922- } as DefaultHookOutput ) ;
2938+ success : true ,
2939+ finalOutput : {
2940+ shouldStopExecution : ( ) => false ,
2941+ isBlockingDecision : ( ) => false ,
2942+ systemMessage : undefined ,
2943+ } ,
2944+ allOutputs : [ ] ,
2945+ errors : [ ] ,
2946+ totalDuration : 0 ,
2947+ } ) ;
29232948
29242949 mockTurnRunFn . mockImplementation ( async function * ( ) {
29252950 yield { type : GeminiEventType . Content , value : 'Response' } ;
0 commit comments