diff --git a/packages/core/src/agents/delegate-to-agent-tool.test.ts b/packages/core/src/agents/delegate-to-agent-tool.test.ts index 89cd1babdb..bc29869c0e 100644 --- a/packages/core/src/agents/delegate-to-agent-tool.test.ts +++ b/packages/core/src/agents/delegate-to-agent-tool.test.ts @@ -121,19 +121,22 @@ describe('DelegateToAgentTool', () => { ); }); - it('should throw helpful error when agent_name does not exist', async () => { - // We allow validation to pass now, checking happens in execute. - const invocation = tool.build({ - agent_name: 'non_existent_agent', - } as DelegateParams); - - await expect(() => - invocation.execute(new AbortController().signal), - ).rejects.toThrow( + it('should throw helpful error when agent_name does not exist', () => { + expect(() => + tool.build({ + agent_name: 'non_existent_agent', + } as DelegateParams), + ).toThrow( "Agent 'non_existent_agent' not found. Available agents are: 'test_agent' (A test agent), 'remote_agent' (A remote agent). Please choose a valid agent_name.", ); }); + it('should throw error when agent_name is missing', () => { + expect(() => tool.build({} as DelegateParams)).toThrow( + "Missing 'agent_name' parameter. Please specify the agent you want to delegate to.", + ); + }); + it('should validate correct arguments', async () => { const invocation = tool.build({ agent_name: 'test_agent', @@ -152,28 +155,24 @@ describe('DelegateToAgentTool', () => { ); }); - it('should throw helpful error for missing required argument', async () => { - const invocation = tool.build({ - agent_name: 'test_agent', - arg2: 123, - } as DelegateParams); - - await expect(() => - invocation.execute(new AbortController().signal), - ).rejects.toThrow( + it('should throw helpful error for missing required argument', () => { + expect(() => + tool.build({ + agent_name: 'test_agent', + arg2: 123, + } as DelegateParams), + ).toThrow( `Invalid arguments for agent 'test_agent': params must have required property 'arg1'. Input schema: ${JSON.stringify(mockAgentDef.inputConfig.inputSchema)}.`, ); }); - it('should throw helpful error for invalid argument type', async () => { - const invocation = tool.build({ - agent_name: 'test_agent', - arg1: 123, - } as DelegateParams); - - await expect(() => - invocation.execute(new AbortController().signal), - ).rejects.toThrow( + it('should throw helpful error for invalid argument type', () => { + expect(() => + tool.build({ + agent_name: 'test_agent', + arg1: 123, + } as DelegateParams), + ).toThrow( `Invalid arguments for agent 'test_agent': params/arg1 must be string. Input schema: ${JSON.stringify(mockAgentDef.inputConfig.inputSchema)}.`, ); }); diff --git a/packages/core/src/agents/delegate-to-agent-tool.ts b/packages/core/src/agents/delegate-to-agent-tool.ts index 064428940d..8b79e923a0 100644 --- a/packages/core/src/agents/delegate-to-agent-tool.ts +++ b/packages/core/src/agents/delegate-to-agent-tool.ts @@ -121,11 +121,33 @@ export class DelegateToAgentTool extends BaseDeclarativeTool< ); } - override validateToolParams(_params: DelegateParams): string | null { - // We override the default schema validation because the generic JSON schema validation - // produces poor error messages for discriminated unions (anyOf). - // Instead, we perform detailed, agent-specific validation in the `execute` method - // to provide rich error messages that help the LLM self-heal. + override validateToolParams(params: DelegateParams): string | null { + if (!params.agent_name) { + return "Missing 'agent_name' parameter. Please specify the agent you want to delegate to."; + } + + const definition = this.registry.getDefinition(params.agent_name); + if (!definition) { + const availableAgents = this.registry + .getAllDefinitions() + .map((def) => `'${def.name}' (${def.description})`) + .join(', '); + + return `Agent '${params.agent_name}' not found. Available agents are: ${availableAgents}. Please choose a valid agent_name.`; + } + + const { agent_name: _agent_name, ...agentArgs } = params; + + // Validate specific agent arguments here using SchemaValidator to generate helpful error messages. + const validationError = SchemaValidator.validate( + definition.inputConfig.inputSchema, + agentArgs, + ); + + if (validationError) { + return `Invalid arguments for agent '${definition.name}': ${validationError}. Input schema: ${JSON.stringify(definition.inputConfig.inputSchema)}.`; + } + return null; } @@ -173,8 +195,8 @@ class DelegateInvocation extends BaseToolInvocation< override async shouldConfirmExecute( abortSignal: AbortSignal, ): Promise { - const definition = this.registry.getDefinition(this.params.agent_name); - if (!definition || definition.kind !== 'remote') { + const definition = this.registry.getDefinition(this.params.agent_name)!; + if (definition.kind !== 'remote') { // Local agents should execute without confirmation. Inner tool calls will bubble up their own confirmations to the user. return false; } @@ -191,32 +213,9 @@ class DelegateInvocation extends BaseToolInvocation< signal: AbortSignal, updateOutput?: (output: string | AnsiOutput) => void, ): Promise { - const definition = this.registry.getDefinition(this.params.agent_name); - if (!definition) { - const availableAgents = this.registry - .getAllDefinitions() - .map((def) => `'${def.name}' (${def.description})`) - .join(', '); - - throw new Error( - `Agent '${this.params.agent_name}' not found. Available agents are: ${availableAgents}. Please choose a valid agent_name.`, - ); - } - + const definition = this.registry.getDefinition(this.params.agent_name)!; const { agent_name: _agent_name, ...agentArgs } = this.params; - // Validate specific agent arguments here using SchemaValidator to generate helpful error messages. - const validationError = SchemaValidator.validate( - definition.inputConfig.inputSchema, - agentArgs, - ); - - if (validationError) { - throw new Error( - `Invalid arguments for agent '${definition.name}': ${validationError}. Input schema: ${JSON.stringify(definition.inputConfig.inputSchema)}.`, - ); - } - const invocation = this.buildSubInvocation( definition, agentArgs as AgentInputs,