mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
feat(core,cli): enforce mandatory MessageBus injection (Phase 3 Hard Migration) (#15776)
This commit is contained in:
@@ -13,6 +13,7 @@ import { LocalSubagentInvocation } from './local-invocation.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { MessageBusType } from '../confirmation-bus/types.js';
|
||||
import { DELEGATE_TO_AGENT_TOOL_NAME } from '../tools/tool-names.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
|
||||
vi.mock('./local-invocation.js', () => ({
|
||||
LocalSubagentInvocation: vi.fn().mockImplementation(() => ({
|
||||
@@ -58,11 +59,7 @@ describe('DelegateToAgentTool', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(registry as any).agents.set(mockAgentDef.name, mockAgentDef);
|
||||
|
||||
messageBus = {
|
||||
publish: vi.fn(),
|
||||
subscribe: vi.fn(),
|
||||
unsubscribe: vi.fn(),
|
||||
} as unknown as MessageBus;
|
||||
messageBus = createMockMessageBus();
|
||||
|
||||
tool = new DelegateToAgentTool(registry, config, messageBus);
|
||||
});
|
||||
@@ -155,7 +152,7 @@ describe('DelegateToAgentTool', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(registry as any).agents.set(invalidAgentDef.name, invalidAgentDef);
|
||||
|
||||
expect(() => new DelegateToAgentTool(registry, config)).toThrow(
|
||||
expect(() => new DelegateToAgentTool(registry, config, messageBus)).toThrow(
|
||||
"Agent 'invalid_agent' cannot have an input parameter named 'agent_name' as it is a reserved parameter for delegation.",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ export class DelegateToAgentTool extends BaseDeclarativeTool<
|
||||
constructor(
|
||||
private readonly registry: AgentRegistry,
|
||||
private readonly config: Config,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
const definitions = registry.getAllDefinitions();
|
||||
|
||||
@@ -119,15 +119,15 @@ export class DelegateToAgentTool extends BaseDeclarativeTool<
|
||||
registry.getToolDescription(),
|
||||
Kind.Think,
|
||||
zodToJsonSchema(schema),
|
||||
messageBus,
|
||||
/* isOutputMarkdown */ true,
|
||||
/* canUpdateOutput */ true,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: DelegateParams,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
_toolName?: string,
|
||||
_toolDisplayName?: string,
|
||||
): ToolInvocation<DelegateParams, ToolResult> {
|
||||
@@ -135,7 +135,7 @@ export class DelegateToAgentTool extends BaseDeclarativeTool<
|
||||
params,
|
||||
this.registry,
|
||||
this.config,
|
||||
messageBus ?? this.messageBus,
|
||||
messageBus,
|
||||
_toolName,
|
||||
_toolDisplayName,
|
||||
);
|
||||
@@ -150,7 +150,7 @@ class DelegateInvocation extends BaseToolInvocation<
|
||||
params: DelegateParams,
|
||||
private readonly registry: AgentRegistry,
|
||||
private readonly config: Config,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
_toolName?: string,
|
||||
_toolDisplayName?: string,
|
||||
) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { IntrospectionAgent } from './introspection-agent.js';
|
||||
import { GetInternalDocsTool } from '../tools/get-internal-docs.js';
|
||||
import { GET_INTERNAL_DOCS_TOOL_NAME } from '../tools/tool-names.js';
|
||||
import { GEMINI_MODEL_ALIAS_FLASH } from '../config/models.js';
|
||||
import type { LocalAgentDefinition } from './types.js';
|
||||
|
||||
@@ -32,9 +32,7 @@ describe('IntrospectionAgent', () => {
|
||||
expect(localAgent.modelConfig?.model).toBe(GEMINI_MODEL_ALIAS_FLASH);
|
||||
|
||||
const tools = localAgent.toolConfig?.tools || [];
|
||||
const hasInternalDocsTool = tools.some(
|
||||
(t) => t instanceof GetInternalDocsTool,
|
||||
);
|
||||
const hasInternalDocsTool = tools.includes(GET_INTERNAL_DOCS_TOOL_NAME);
|
||||
expect(hasInternalDocsTool).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { AgentDefinition } from './types.js';
|
||||
import { GetInternalDocsTool } from '../tools/get-internal-docs.js';
|
||||
import { GET_INTERNAL_DOCS_TOOL_NAME } from '../tools/tool-names.js';
|
||||
import { GEMINI_MODEL_ALIAS_FLASH } from '../config/models.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -60,7 +60,7 @@ export const IntrospectionAgent: AgentDefinition<
|
||||
},
|
||||
|
||||
toolConfig: {
|
||||
tools: [new GetInternalDocsTool()],
|
||||
tools: [GET_INTERNAL_DOCS_TOOL_NAME],
|
||||
},
|
||||
|
||||
promptConfig: {
|
||||
|
||||
@@ -269,8 +269,13 @@ describe('LocalAgentExecutor', () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
mockConfig = makeFakeConfig();
|
||||
parentToolRegistry = new ToolRegistry(mockConfig);
|
||||
parentToolRegistry.registerTool(new LSTool(mockConfig));
|
||||
parentToolRegistry = new ToolRegistry(
|
||||
mockConfig,
|
||||
mockConfig.getMessageBus(),
|
||||
);
|
||||
parentToolRegistry.registerTool(
|
||||
new LSTool(mockConfig, mockConfig.getMessageBus()),
|
||||
);
|
||||
parentToolRegistry.registerTool(
|
||||
new MockTool({ name: READ_FILE_TOOL_NAME }),
|
||||
);
|
||||
|
||||
@@ -99,7 +99,10 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
onActivity?: ActivityCallback,
|
||||
): Promise<LocalAgentExecutor<TOutput>> {
|
||||
// Create an isolated tool registry for this agent instance.
|
||||
const agentToolRegistry = new ToolRegistry(runtimeContext);
|
||||
const agentToolRegistry = new ToolRegistry(
|
||||
runtimeContext,
|
||||
runtimeContext.getMessageBus(),
|
||||
);
|
||||
const parentToolRegistry = runtimeContext.getToolRegistry();
|
||||
|
||||
if (definition.toolConfig) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ToolErrorType } from '../tools/tool-error.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { type z } from 'zod';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
|
||||
vi.mock('./local-executor.js');
|
||||
|
||||
@@ -39,10 +40,12 @@ const testDefinition: LocalAgentDefinition<z.ZodUnknown> = {
|
||||
|
||||
describe('LocalSubagentInvocation', () => {
|
||||
let mockExecutorInstance: Mocked<LocalAgentExecutor<z.ZodUnknown>>;
|
||||
let mockMessageBus: MessageBus;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockConfig = makeFakeConfig();
|
||||
mockMessageBus = createMockMessageBus();
|
||||
|
||||
mockExecutorInstance = {
|
||||
run: vi.fn(),
|
||||
@@ -55,7 +58,6 @@ describe('LocalSubagentInvocation', () => {
|
||||
});
|
||||
|
||||
it('should pass the messageBus to the parent constructor', () => {
|
||||
const mockMessageBus = {} as MessageBus;
|
||||
const params = { task: 'Analyze data' };
|
||||
const invocation = new LocalSubagentInvocation(
|
||||
testDefinition,
|
||||
@@ -76,6 +78,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
testDefinition,
|
||||
mockConfig,
|
||||
params,
|
||||
mockMessageBus,
|
||||
);
|
||||
const description = invocation.getDescription();
|
||||
expect(description).toBe(
|
||||
@@ -90,6 +93,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
testDefinition,
|
||||
mockConfig,
|
||||
params,
|
||||
mockMessageBus,
|
||||
);
|
||||
const description = invocation.getDescription();
|
||||
// Default INPUT_PREVIEW_MAX_LENGTH is 50
|
||||
@@ -112,6 +116,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
longNameDef,
|
||||
mockConfig,
|
||||
params,
|
||||
mockMessageBus,
|
||||
);
|
||||
const description = invocation.getDescription();
|
||||
// Default DESCRIPTION_MAX_LENGTH is 200
|
||||
@@ -137,6 +142,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
testDefinition,
|
||||
mockConfig,
|
||||
params,
|
||||
mockMessageBus,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
* @param definition The definition object that configures the agent.
|
||||
* @param config The global runtime configuration.
|
||||
* @param params The validated input parameters for the agent.
|
||||
* @param messageBus Optional message bus for policy enforcement.
|
||||
* @param messageBus Message bus for policy enforcement.
|
||||
*/
|
||||
constructor(
|
||||
private readonly definition: LocalAgentDefinition,
|
||||
private readonly config: Config,
|
||||
params: AgentInputs,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
_toolName?: string,
|
||||
_toolDisplayName?: string,
|
||||
) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import type { ToolCallConfirmationDetails } from '../tools/tools.js';
|
||||
import { RemoteAgentInvocation } from './remote-invocation.js';
|
||||
import type { RemoteAgentDefinition } from './types.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
|
||||
class TestableRemoteAgentInvocation extends RemoteAgentInvocation {
|
||||
override async getConfirmationDetails(
|
||||
@@ -29,8 +30,14 @@ describe('RemoteAgentInvocation', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mockMessageBus = createMockMessageBus();
|
||||
|
||||
it('should be instantiated with correct params', () => {
|
||||
const invocation = new RemoteAgentInvocation(mockDefinition, {});
|
||||
const invocation = new RemoteAgentInvocation(
|
||||
mockDefinition,
|
||||
{},
|
||||
mockMessageBus,
|
||||
);
|
||||
expect(invocation).toBeDefined();
|
||||
expect(invocation.getDescription()).toBe(
|
||||
'Calling remote agent Test Remote Agent',
|
||||
@@ -38,7 +45,11 @@ describe('RemoteAgentInvocation', () => {
|
||||
});
|
||||
|
||||
it('should return false for confirmation details (not yet implemented)', async () => {
|
||||
const invocation = new TestableRemoteAgentInvocation(mockDefinition, {});
|
||||
const invocation = new TestableRemoteAgentInvocation(
|
||||
mockDefinition,
|
||||
{},
|
||||
mockMessageBus,
|
||||
);
|
||||
const details = await invocation.getConfirmationDetails(
|
||||
new AbortController().signal,
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
constructor(
|
||||
private readonly definition: RemoteAgentDefinition,
|
||||
params: AgentInputs,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
_toolName?: string,
|
||||
_toolDisplayName?: string,
|
||||
) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { LocalAgentDefinition, AgentInputs } from './types.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { Kind } from '../tools/tools.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
|
||||
// Mock dependencies to isolate the SubagentToolWrapper class
|
||||
vi.mock('./local-invocation.js');
|
||||
@@ -25,6 +26,7 @@ const mockConvertInputConfigToJsonSchema = vi.mocked(
|
||||
|
||||
// Define reusable test data
|
||||
let mockConfig: Config;
|
||||
let mockMessageBus: MessageBus;
|
||||
|
||||
const mockDefinition: LocalAgentDefinition = {
|
||||
kind: 'local',
|
||||
@@ -59,6 +61,7 @@ describe('SubagentToolWrapper', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockConfig = makeFakeConfig();
|
||||
mockMessageBus = createMockMessageBus();
|
||||
// Provide a mock implementation for the schema conversion utility
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockConvertInputConfigToJsonSchema.mockReturnValue(mockSchema as any);
|
||||
@@ -66,7 +69,7 @@ describe('SubagentToolWrapper', () => {
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should call convertInputConfigToJsonSchema with the correct agent inputConfig', () => {
|
||||
new SubagentToolWrapper(mockDefinition, mockConfig);
|
||||
new SubagentToolWrapper(mockDefinition, mockConfig, mockMessageBus);
|
||||
|
||||
expect(convertInputConfigToJsonSchema).toHaveBeenCalledExactlyOnceWith(
|
||||
mockDefinition.inputConfig,
|
||||
@@ -74,7 +77,11 @@ describe('SubagentToolWrapper', () => {
|
||||
});
|
||||
|
||||
it('should correctly configure the tool properties from the agent definition', () => {
|
||||
const wrapper = new SubagentToolWrapper(mockDefinition, mockConfig);
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
expect(wrapper.name).toBe(mockDefinition.name);
|
||||
expect(wrapper.displayName).toBe(mockDefinition.displayName);
|
||||
@@ -92,12 +99,17 @@ describe('SubagentToolWrapper', () => {
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
definitionWithoutDisplayName,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
);
|
||||
expect(wrapper.displayName).toBe(definitionWithoutDisplayName.name);
|
||||
});
|
||||
|
||||
it('should generate a valid tool schema using the definition and converted schema', () => {
|
||||
const wrapper = new SubagentToolWrapper(mockDefinition, mockConfig);
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
);
|
||||
const schema = wrapper.schema;
|
||||
|
||||
expect(schema.name).toBe(mockDefinition.name);
|
||||
@@ -108,7 +120,11 @@ describe('SubagentToolWrapper', () => {
|
||||
|
||||
describe('createInvocation', () => {
|
||||
it('should create a LocalSubagentInvocation with the correct parameters', () => {
|
||||
const wrapper = new SubagentToolWrapper(mockDefinition, mockConfig);
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
);
|
||||
const params: AgentInputs = { goal: 'Test the invocation', priority: 1 };
|
||||
|
||||
// The public `build` method calls the protected `createInvocation` after validation
|
||||
@@ -119,18 +135,22 @@ describe('SubagentToolWrapper', () => {
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
params,
|
||||
undefined,
|
||||
mockMessageBus,
|
||||
mockDefinition.name,
|
||||
mockDefinition.displayName,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass the messageBus to the LocalSubagentInvocation constructor', () => {
|
||||
const mockMessageBus = {} as MessageBus;
|
||||
const specificMessageBus = {
|
||||
publish: vi.fn(),
|
||||
subscribe: vi.fn(),
|
||||
unsubscribe: vi.fn(),
|
||||
} as unknown as MessageBus;
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
specificMessageBus,
|
||||
);
|
||||
const params: AgentInputs = { goal: 'Test the invocation', priority: 1 };
|
||||
|
||||
@@ -140,14 +160,18 @@ describe('SubagentToolWrapper', () => {
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
params,
|
||||
mockMessageBus,
|
||||
specificMessageBus,
|
||||
mockDefinition.name,
|
||||
mockDefinition.displayName,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a validation error for invalid parameters before creating an invocation', () => {
|
||||
const wrapper = new SubagentToolWrapper(mockDefinition, mockConfig);
|
||||
const wrapper = new SubagentToolWrapper(
|
||||
mockDefinition,
|
||||
mockConfig,
|
||||
mockMessageBus,
|
||||
);
|
||||
// Missing the required 'goal' parameter
|
||||
const invalidParams = { priority: 1 };
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export class SubagentToolWrapper extends BaseDeclarativeTool<
|
||||
constructor(
|
||||
private readonly definition: AgentDefinition,
|
||||
private readonly config: Config,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
const parameterSchema = convertInputConfigToJsonSchema(
|
||||
definition.inputConfig,
|
||||
@@ -50,9 +50,9 @@ export class SubagentToolWrapper extends BaseDeclarativeTool<
|
||||
definition.description,
|
||||
Kind.Think,
|
||||
parameterSchema,
|
||||
messageBus,
|
||||
/* isOutputMarkdown */ true,
|
||||
/* canUpdateOutput */ true,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,12 +67,12 @@ export class SubagentToolWrapper extends BaseDeclarativeTool<
|
||||
*/
|
||||
protected createInvocation(
|
||||
params: AgentInputs,
|
||||
messageBus?: MessageBus,
|
||||
messageBus: MessageBus,
|
||||
_toolName?: string,
|
||||
_toolDisplayName?: string,
|
||||
): ToolInvocation<AgentInputs, ToolResult> {
|
||||
const definition = this.definition;
|
||||
const effectiveMessageBus = messageBus ?? this.messageBus;
|
||||
const effectiveMessageBus = messageBus;
|
||||
|
||||
if (definition.kind === 'remote') {
|
||||
return new RemoteAgentInvocation(
|
||||
|
||||
Reference in New Issue
Block a user