mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
refactor(core): consolidate execute() arguments into ExecuteOptions (#25101)
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
type ToolResult,
|
||||
BaseToolInvocation,
|
||||
type ToolCallConfirmationDetails,
|
||||
type ToolLiveOutput,
|
||||
type ExecuteOptions,
|
||||
} from '../tools/tools.js';
|
||||
import { type AgentLoopContext } from '../config/agent-loop-context.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
@@ -185,10 +185,8 @@ class DelegateInvocation extends BaseToolInvocation<
|
||||
return invocation.shouldConfirmExecute(abortSignal);
|
||||
}
|
||||
|
||||
async execute(
|
||||
signal: AbortSignal,
|
||||
updateOutput?: (output: ToolLiveOutput) => void,
|
||||
): Promise<ToolResult> {
|
||||
async execute(options: ExecuteOptions): Promise<ToolResult> {
|
||||
const { abortSignal: signal, updateOutput } = options;
|
||||
const hintedParams = this.withUserHints(this.mappedInputs);
|
||||
const invocation = this.buildChildInvocation(hintedParams);
|
||||
|
||||
@@ -204,7 +202,10 @@ class DelegateInvocation extends BaseToolInvocation<
|
||||
},
|
||||
async ({ metadata }) => {
|
||||
metadata.input = this.params;
|
||||
const result = await invocation.execute(signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: signal,
|
||||
updateOutput,
|
||||
});
|
||||
metadata.output = result;
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -99,7 +99,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Find the blue submit button',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
// Verify screenshot was captured
|
||||
expect(browserManager.callTool).toHaveBeenCalledWith(
|
||||
@@ -165,7 +167,7 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Find the search bar',
|
||||
});
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
const contentGenerator = config.getContentGenerator();
|
||||
expect(contentGenerator.generateContent).toHaveBeenCalledWith(
|
||||
@@ -194,7 +196,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Find the button',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain('Failed to capture screenshot');
|
||||
@@ -217,7 +221,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Check the layout',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain('Visual model returned no analysis');
|
||||
@@ -238,7 +244,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Find the red error',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain(
|
||||
@@ -261,7 +269,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Identify the element',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain(
|
||||
@@ -281,7 +291,9 @@ describe('analyzeScreenshot', () => {
|
||||
const invocation = tool.build({
|
||||
instruction: 'Find something',
|
||||
});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain('Visual analysis failed');
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Kind,
|
||||
type ToolResult,
|
||||
type ToolInvocation,
|
||||
type ExecuteOptions,
|
||||
} from '../../tools/tools.js';
|
||||
import { Environment } from '@google/genai';
|
||||
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
||||
@@ -80,7 +81,7 @@ class AnalyzeScreenshotInvocation extends BaseToolInvocation<
|
||||
return `Visual analysis: "${instruction}"`;
|
||||
}
|
||||
|
||||
async execute(signal: AbortSignal): Promise<ToolResult> {
|
||||
async execute({ abortSignal: signal }: ExecuteOptions): Promise<ToolResult> {
|
||||
try {
|
||||
const instruction = String(this.params['instruction'] ?? '');
|
||||
|
||||
|
||||
@@ -223,7 +223,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
const controller = new AbortController();
|
||||
const updateOutput: (output: ToolLiveOutput) => void = vi.fn();
|
||||
|
||||
const result = await invocation.execute(controller.signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: controller.signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(Array.isArray(result.llmContent)).toBe(true);
|
||||
expect((result.llmContent as Array<{ text: string }>)[0].text).toContain(
|
||||
@@ -242,7 +245,7 @@ describe('BrowserAgentInvocation', () => {
|
||||
const controller = new AbortController();
|
||||
// Should not throw even with no updateOutput
|
||||
await expect(
|
||||
invocation.execute(controller.signal),
|
||||
invocation.execute({ abortSignal: controller.signal }),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
@@ -256,7 +259,9 @@ describe('BrowserAgentInvocation', () => {
|
||||
);
|
||||
|
||||
const controller = new AbortController();
|
||||
const result = await invocation.execute(controller.signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(removeInputBlocker).toHaveBeenCalled();
|
||||
@@ -298,7 +303,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
await invocation.execute(new AbortController().signal, updateOutput);
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
const firstCall = updateOutput.mock.calls[0]?.[0] as SubagentProgress;
|
||||
expect(firstCall.isSubagentProgress).toBe(true);
|
||||
@@ -315,7 +323,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
await invocation.execute(new AbortController().signal, updateOutput);
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
const lastCall = updateOutput.mock.calls[
|
||||
updateOutput.mock.calls.length - 1
|
||||
@@ -334,10 +345,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
// Allow createBrowserAgentDefinition to resolve and onActivity to be registered
|
||||
await Promise.resolve();
|
||||
@@ -377,10 +388,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
// Allow createBrowserAgentDefinition to resolve and onActivity to be registered
|
||||
await Promise.resolve();
|
||||
@@ -424,10 +435,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -475,10 +486,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -519,10 +530,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -564,10 +575,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -604,10 +615,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -647,10 +658,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
const executePromise = invocation.execute(
|
||||
new AbortController().signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -703,7 +714,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockParams,
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal, vi.fn());
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput: vi.fn(),
|
||||
});
|
||||
|
||||
expect(recordBrowserAgentTaskOutcome).toHaveBeenCalledWith(
|
||||
mockConfig,
|
||||
@@ -731,7 +745,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
await invocation.execute(new AbortController().signal, updateOutput);
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(recordBrowserAgentTaskOutcome).toHaveBeenCalledWith(
|
||||
mockConfig,
|
||||
@@ -751,7 +768,10 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockParams,
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal, vi.fn());
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput: vi.fn(),
|
||||
});
|
||||
|
||||
expect(cleanupBrowserAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -807,7 +827,7 @@ describe('BrowserAgentInvocation', () => {
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
// Verify list_pages was called
|
||||
expect(mockBrowserManager.callTool).toHaveBeenCalledWith(
|
||||
|
||||
@@ -22,7 +22,7 @@ import { LocalAgentExecutor } from '../local-executor.js';
|
||||
import {
|
||||
BaseToolInvocation,
|
||||
type ToolResult,
|
||||
type ToolLiveOutput,
|
||||
type ExecuteOptions,
|
||||
} from '../../tools/tools.js';
|
||||
import { ToolErrorType } from '../../tools/tool-error.js';
|
||||
import {
|
||||
@@ -107,10 +107,8 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
* 3. Runs the agent via LocalAgentExecutor
|
||||
* 4. Cleans up browser resources
|
||||
*/
|
||||
async execute(
|
||||
signal: AbortSignal,
|
||||
updateOutput?: (output: ToolLiveOutput) => void,
|
||||
): Promise<ToolResult> {
|
||||
async execute(options: ExecuteOptions): Promise<ToolResult> {
|
||||
const { abortSignal: signal, updateOutput } = options;
|
||||
const invocationStartMs = Date.now();
|
||||
let browserManager;
|
||||
let recentActivity: SubagentActivityItem[] = [];
|
||||
|
||||
@@ -139,7 +139,7 @@ describe('mcpToolWrapper', () => {
|
||||
);
|
||||
|
||||
const invocation = tools[1].build({ uid: 'elem-123' });
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(mockBrowserManager.callTool).toHaveBeenCalledWith(
|
||||
'click',
|
||||
@@ -158,7 +158,9 @@ describe('mcpToolWrapper', () => {
|
||||
);
|
||||
|
||||
const invocation = tools[0].build({ verbose: true });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.llmContent).toBe('Tool result');
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -177,7 +179,9 @@ describe('mcpToolWrapper', () => {
|
||||
);
|
||||
|
||||
const invocation = tools[1].build({ uid: 'invalid' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error?.message).toBe('Element not found');
|
||||
@@ -195,7 +199,9 @@ describe('mcpToolWrapper', () => {
|
||||
);
|
||||
|
||||
const invocation = tools[0].build({});
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error?.message).toBe('Connection lost');
|
||||
@@ -212,7 +218,7 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const clickTool = tools.find((t) => t.name === 'click')!;
|
||||
const invocation = clickTool.build({ uid: 'elem-42' });
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
// callTool: suspend blocker + click + resume blocker
|
||||
expect(mockBrowserManager.callTool).toHaveBeenCalledTimes(3);
|
||||
@@ -257,7 +263,7 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const snapshotTool = tools.find((t) => t.name === 'take_snapshot')!;
|
||||
const invocation = snapshotTool.build({});
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
// callTool should only be called once for take_snapshot — no suspend/resume
|
||||
expect(mockBrowserManager.callTool).toHaveBeenCalledTimes(1);
|
||||
@@ -277,7 +283,7 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const clickTool = tools.find((t) => t.name === 'click')!;
|
||||
const invocation = clickTool.build({ uid: 'elem-42' });
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
// callTool should only be called once for click — no suspend/resume
|
||||
expect(mockBrowserManager.callTool).toHaveBeenCalledTimes(1);
|
||||
@@ -297,7 +303,9 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const clickTool = tools.find((t) => t.name === 'click')!;
|
||||
const invocation = clickTool.build({ uid: 'bad-elem' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
// Should return error, not throw
|
||||
expect(result.error).toBeDefined();
|
||||
@@ -328,7 +336,9 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const uploadTool = tools.find((t) => t.name === 'upload_file')!;
|
||||
const invocation = uploadTool.build({ path: 'test.txt' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.llmContent).toContain('File uploads are blocked');
|
||||
@@ -345,7 +355,9 @@ describe('mcpToolWrapper', () => {
|
||||
|
||||
const uploadTool = tools.find((t) => t.name === 'upload_file')!;
|
||||
const invocation = uploadTool.build({ path: 'test.txt' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.llmContent).toBe('Tool result');
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
type ToolInvocation,
|
||||
type ToolCallConfirmationDetails,
|
||||
type PolicyUpdateOptions,
|
||||
type ExecuteOptions,
|
||||
} from '../../tools/tools.js';
|
||||
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
||||
import {
|
||||
@@ -117,7 +118,7 @@ class McpToolInvocation extends BaseToolInvocation<
|
||||
return this.shouldDisableInput && INTERACTIVE_TOOLS.has(this.toolName);
|
||||
}
|
||||
|
||||
async execute(signal: AbortSignal): Promise<ToolResult> {
|
||||
async execute({ abortSignal: signal }: ExecuteOptions): Promise<ToolResult> {
|
||||
try {
|
||||
// Hard block for file uploads if configured
|
||||
if (this.blockFileUploads && this.toolName === 'upload_file') {
|
||||
|
||||
@@ -187,7 +187,10 @@ describe('LocalSubagentInvocation', () => {
|
||||
};
|
||||
mockExecutorInstance.run.mockResolvedValue(mockOutput);
|
||||
|
||||
const result = await invocation.execute(signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(MockLocalAgentExecutor.create).toHaveBeenCalledWith(
|
||||
testDefinition,
|
||||
@@ -224,7 +227,10 @@ describe('LocalSubagentInvocation', () => {
|
||||
};
|
||||
mockExecutorInstance.run.mockResolvedValue(mockOutput);
|
||||
|
||||
const result = await invocation.execute(signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
@@ -254,7 +260,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledTimes(4); // Initial + 2 updates + Final completion
|
||||
const lastCall = updateOutput.mock.calls[3][0] as SubagentProgress;
|
||||
@@ -293,7 +299,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
const calls = updateOutput.mock.calls;
|
||||
const lastCall = calls[calls.length - 1][0] as SubagentProgress;
|
||||
@@ -326,7 +332,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledTimes(4); // Initial + 2 updates + Final completion
|
||||
const lastCall = updateOutput.mock.calls[3][0] as SubagentProgress;
|
||||
@@ -360,7 +366,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
expect(updateOutput).toHaveBeenCalled();
|
||||
const lastCall = updateOutput.mock.calls[
|
||||
@@ -404,7 +410,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
};
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledTimes(4);
|
||||
const lastCall = updateOutput.mock.calls[3][0] as SubagentProgress;
|
||||
@@ -433,7 +439,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
});
|
||||
|
||||
// Execute without the optional callback
|
||||
const result = await invocation.execute(signal);
|
||||
const result = await invocation.execute({ abortSignal: signal });
|
||||
expect(result.error).toBeUndefined();
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
@@ -445,7 +451,10 @@ describe('LocalSubagentInvocation', () => {
|
||||
const error = new Error('Model failed during execution.');
|
||||
mockExecutorInstance.run.mockRejectedValue(error);
|
||||
|
||||
const result = await invocation.execute(signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.llmContent).toBe(
|
||||
@@ -466,7 +475,10 @@ describe('LocalSubagentInvocation', () => {
|
||||
const creationError = new Error('Failed to initialize tools.');
|
||||
MockLocalAgentExecutor.create.mockRejectedValue(creationError);
|
||||
|
||||
const result = await invocation.execute(signal, updateOutput);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(mockExecutorInstance.run).not.toHaveBeenCalled();
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -487,10 +499,10 @@ describe('LocalSubagentInvocation', () => {
|
||||
mockExecutorInstance.run.mockRejectedValue(abortError);
|
||||
|
||||
const controller = new AbortController();
|
||||
const executePromise = invocation.execute(
|
||||
controller.signal,
|
||||
const executePromise = invocation.execute({
|
||||
abortSignal: controller.signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
controller.abort();
|
||||
await expect(executePromise).rejects.toThrow('Aborted');
|
||||
|
||||
@@ -507,9 +519,9 @@ describe('LocalSubagentInvocation', () => {
|
||||
};
|
||||
mockExecutorInstance.run.mockResolvedValue(mockOutput);
|
||||
|
||||
await expect(invocation.execute(signal, updateOutput)).rejects.toThrow(
|
||||
'Operation cancelled by user',
|
||||
);
|
||||
await expect(
|
||||
invocation.execute({ abortSignal: signal, updateOutput }),
|
||||
).rejects.toThrow('Operation cancelled by user');
|
||||
});
|
||||
|
||||
it('should publish SUBAGENT_ACTIVITY events to the MessageBus', async () => {
|
||||
@@ -529,7 +541,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
|
||||
});
|
||||
|
||||
await invocation.execute(signal, updateOutput);
|
||||
await invocation.execute({ abortSignal: signal, updateOutput });
|
||||
|
||||
expect(mockMessageBus.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LocalAgentExecutor } from './local-executor.js';
|
||||
import {
|
||||
BaseToolInvocation,
|
||||
type ToolResult,
|
||||
type ToolLiveOutput,
|
||||
type ExecuteOptions,
|
||||
} from '../tools/tools.js';
|
||||
import {
|
||||
type LocalAgentDefinition,
|
||||
@@ -105,10 +105,8 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
* agent's thoughts, to the user interface.
|
||||
* @returns A `Promise` that resolves with the final `ToolResult`.
|
||||
*/
|
||||
async execute(
|
||||
signal: AbortSignal,
|
||||
updateOutput?: (output: ToolLiveOutput) => void,
|
||||
): Promise<ToolResult> {
|
||||
async execute(options: ExecuteOptions): Promise<ToolResult> {
|
||||
const { abortSignal: signal, updateOutput } = options;
|
||||
const recentActivity: SubagentActivityItem[] = [];
|
||||
let executor: LocalAgentExecutor<z.ZodUnknown> | undefined;
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
{},
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(mockClientManager.sendMessageStream).toHaveBeenCalledWith(
|
||||
'test-agent',
|
||||
@@ -185,7 +185,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(mockClientManager.loadAgent).toHaveBeenCalledWith(
|
||||
'test-agent',
|
||||
@@ -230,7 +230,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(A2AAuthProviderFactory.create).toHaveBeenCalledWith({
|
||||
authConfig: mockAuth,
|
||||
@@ -264,7 +264,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
@@ -293,7 +295,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal);
|
||||
await invocation.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(mockClientManager.loadAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -325,7 +327,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
);
|
||||
|
||||
// Execute first time
|
||||
const result1 = await invocation1.execute(new AbortController().signal);
|
||||
const result1 = await invocation1.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
expect(result1.returnDisplay).toMatchObject({
|
||||
result: 'Response 1',
|
||||
});
|
||||
@@ -357,7 +361,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
const result2 = await invocation2.execute(new AbortController().signal);
|
||||
const result2 = await invocation2.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
expect((result2.returnDisplay as SubagentProgress).result).toBe(
|
||||
'Response 2',
|
||||
);
|
||||
@@ -390,7 +396,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation3.execute(new AbortController().signal);
|
||||
await invocation3.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
// Fourth call: Should start new task (taskId undefined)
|
||||
mockClientManager.sendMessageStream.mockImplementationOnce(
|
||||
@@ -412,7 +418,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation4.execute(new AbortController().signal);
|
||||
await invocation4.execute({ abortSignal: new AbortController().signal });
|
||||
|
||||
expect(mockClientManager.sendMessageStream).toHaveBeenLastCalledWith(
|
||||
'test-agent',
|
||||
@@ -447,7 +453,10 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal, updateOutput);
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -495,7 +504,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(controller.signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
});
|
||||
@@ -517,7 +528,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: 'error',
|
||||
@@ -550,7 +563,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
// Just check that text is present, exact formatting depends on helper
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
@@ -593,10 +608,10 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(
|
||||
new AbortController().signal,
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
);
|
||||
});
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -670,7 +685,10 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
await invocation.execute(new AbortController().signal, updateOutput);
|
||||
await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
updateOutput,
|
||||
});
|
||||
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -738,7 +756,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
@@ -758,7 +778,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
@@ -787,7 +809,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
{ query: 'hi' },
|
||||
mockMessageBus,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
const result = await invocation.execute({
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
// Should contain both the partial output and the error message
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type ToolConfirmationOutcome,
|
||||
type ToolResult,
|
||||
type ToolCallConfirmationDetails,
|
||||
type ExecuteOptions,
|
||||
} from '../tools/tools.js';
|
||||
import {
|
||||
DEFAULT_QUERY_STRING,
|
||||
@@ -28,7 +29,6 @@ import type {
|
||||
import { extractIdsFromResponse, A2AResultReassembler } from './a2aUtils.js';
|
||||
import type { AuthenticationHandler } from '@a2a-js/sdk/client';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import type { AnsiOutput } from '../utils/terminalSerializer.js';
|
||||
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
||||
import { A2AAgentError } from './a2a-errors.js';
|
||||
|
||||
@@ -126,10 +126,8 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
};
|
||||
}
|
||||
|
||||
async execute(
|
||||
_signal: AbortSignal,
|
||||
updateOutput?: (output: string | AnsiOutput | SubagentProgress) => void,
|
||||
): Promise<ToolResult> {
|
||||
async execute(options: ExecuteOptions): Promise<ToolResult> {
|
||||
const { abortSignal: _signal, updateOutput } = options;
|
||||
// 1. Ensure the agent is loaded (cached by manager)
|
||||
// We assume the user has provided an access token via some mechanism (TODO),
|
||||
// or we rely on ADC.
|
||||
|
||||
Reference in New Issue
Block a user