@@ -21,7 +21,7 @@ import { LLM, type LLMStream } from '../llm/llm.js';
2121import { type Tool , ToolContext , ToolFlag , Toolset , tool } from '../llm/tool_context.js' ;
2222import { Future , Task } from '../utils.js' ;
2323import { _getActivityTaskInfo } from './agent.js' ;
24- import { AgentActivity } from './agent_activity.js' ;
24+ import { AgentActivity , onEnterStorage } from './agent_activity.js' ;
2525import type { PreemptiveGenerationInfo } from './audio_recognition.js' ;
2626import type { AgentSessionEventTypes , UserInputTranscribedEvent } from './events.js' ;
2727import { SpeechHandle } from './speech_handle.js' ;
@@ -582,6 +582,77 @@ describe('AgentActivity - onPreemptiveGeneration guards', () => {
582582 } ) ;
583583} ) ;
584584
585+ describe ( 'AgentActivity - onEnter ignored tools' , ( ) => {
586+ const makeFn = ( name : string , flags = ToolFlag . NONE ) =>
587+ tool ( { name, description : `${ name } tool` , flags, execute : async ( ) => name } ) ;
588+
589+ function buildIgnoredToolsActivity ( ) {
590+ const activity = Object . create ( AgentActivity . prototype ) as AgentActivity ;
591+ Object . assign ( activity , {
592+ agent : { } ,
593+ agentSession : { } ,
594+ } ) ;
595+ return activity ;
596+ }
597+
598+ it ( 'returns bare and toolset-nested IGNORE_ON_ENTER tools only inside this onEnter' , ( ) => {
599+ const endCall = makeFn ( 'end_call' , ToolFlag . IGNORE_ON_ENTER ) ;
600+ const keep = makeFn ( 'keep' ) ;
601+ const bareIgnored = makeFn ( 'bare_ignored' , ToolFlag . IGNORE_ON_ENTER ) ;
602+ const bareKeep = makeFn ( 'bare_keep' ) ;
603+ const toolset = new Toolset ( { id : 'ts' , tools : [ endCall , keep ] } ) ;
604+ const toolCtx = new ToolContext ( [ toolset , bareIgnored , bareKeep ] ) ;
605+ const activity = buildIgnoredToolsActivity ( ) ;
606+
607+ expect ( activity . _onEnterIgnoredTools ( toolCtx ) ) . toEqual ( [ ] ) ;
608+
609+ onEnterStorage . run ( { session : activity . agentSession , agent : activity . agent } , ( ) => {
610+ expect (
611+ activity
612+ . _onEnterIgnoredTools ( toolCtx )
613+ . map ( ( t ) => t . id )
614+ . sort ( ) ,
615+ ) . toEqual ( [ 'bare_ignored' , 'end_call' ] ) ;
616+ } ) ;
617+
618+ onEnterStorage . run ( { session : activity . agentSession , agent : { } as never } , ( ) => {
619+ expect ( activity . _onEnterIgnoredTools ( toolCtx ) ) . toEqual ( [ ] ) ;
620+ } ) ;
621+ } ) ;
622+
623+ it ( 'hides ignored bare and toolset tools while preserving normal tools for nested replies' , async ( ) => {
624+ const endCall = makeFn ( 'end_call' , ToolFlag . IGNORE_ON_ENTER ) ;
625+ const keep = makeFn ( 'keep' ) ;
626+ const bareIgnored = makeFn ( 'bare_ignored' , ToolFlag . IGNORE_ON_ENTER ) ;
627+ const toolset = new Toolset ( { id : 'ts' , tools : [ endCall , keep ] } ) ;
628+ const activity = buildIgnoredToolsActivity ( ) ;
629+
630+ await onEnterStorage . run (
631+ { session : activity . agentSession , agent : activity . agent } ,
632+ async ( ) => {
633+ const greetingTools = new ToolContext ( [ toolset , bareIgnored ] ) ;
634+ greetingTools . _exclude ( activity . _onEnterIgnoredTools ( greetingTools ) ) ;
635+ expect ( greetingTools . flatten ( ) . map ( ( t ) => t . id ) ) . toEqual ( [ 'keep' ] ) ;
636+ expect ( greetingTools . toolsets ) . toEqual ( [ toolset ] ) ;
637+
638+ await Promise . resolve ( ) ;
639+
640+ const toolReplyTools = new ToolContext ( [ toolset , bareIgnored ] ) ;
641+ toolReplyTools . _exclude ( activity . _onEnterIgnoredTools ( toolReplyTools ) ) ;
642+ expect ( toolReplyTools . flatten ( ) . map ( ( t ) => t . id ) ) . toEqual ( [ 'keep' ] ) ;
643+ } ,
644+ ) ;
645+
646+ const restoredTools = new ToolContext ( [ toolset , bareIgnored ] ) ;
647+ expect (
648+ restoredTools
649+ . flatten ( )
650+ . map ( ( t ) => t . id )
651+ . sort ( ) ,
652+ ) . toEqual ( [ 'bare_ignored' , 'end_call' , 'keep' ] ) ;
653+ } ) ;
654+ } ) ;
655+
585656/**
586657 * Regression test for the dynamic-toolset push path.
587658 *
0 commit comments