feat: Add enableSubagents configuration and wire up subagent registration (#9988)

This commit is contained in:
Abhi
2025-10-01 16:54:00 -04:00
committed by GitHub
parent 5b16771567
commit 331ae7dbdf
14 changed files with 352 additions and 39 deletions

View File

@@ -125,6 +125,17 @@ vi.mock('../ide/ide-client.js', () => ({
},
}));
vi.mock('../agents/registry.js', () => {
const AgentRegistryMock = vi.fn();
AgentRegistryMock.prototype.initialize = vi.fn();
AgentRegistryMock.prototype.getAllDefinitions = vi.fn(() => []);
return { AgentRegistry: AgentRegistryMock };
});
vi.mock('../agents/subagent-tool-wrapper.js', () => ({
SubagentToolWrapper: vi.fn(),
}));
import { BaseLlmClient } from '../core/baseLlmClient.js';
import { tokenLimit } from '../core/tokenLimits.js';
import { uiTelemetryService } from '../telemetry/index.js';
@@ -583,6 +594,31 @@ describe('Server Config (config.ts)', () => {
});
});
describe('EnableSubagents Configuration', () => {
it('should default enableSubagents to false when not provided', () => {
const config = new Config(baseParams);
expect(config.getEnableSubagents()).toBe(false);
});
it('should set enableSubagents to true when provided as true', () => {
const paramsWithSubagents: ConfigParameters = {
...baseParams,
enableSubagents: true,
};
const config = new Config(paramsWithSubagents);
expect(config.getEnableSubagents()).toBe(true);
});
it('should set enableSubagents to false when explicitly provided as false', () => {
const paramsWithSubagents: ConfigParameters = {
...baseParams,
enableSubagents: false,
};
const config = new Config(paramsWithSubagents);
expect(config.getEnableSubagents()).toBe(false);
});
});
describe('createToolRegistry', () => {
it('should register a tool if coreTools contains an argument-specific pattern', async () => {
const params: ConfigParameters = {
@@ -612,6 +648,78 @@ describe('Server Config (config.ts)', () => {
expect(wasReadFileToolRegistered).toBe(false);
});
it('should register subagents as tools when enableSubagents is true', async () => {
const params: ConfigParameters = {
...baseParams,
enableSubagents: true,
};
const config = new Config(params);
const mockAgentDefinitions = [
{ name: 'agent1', description: 'Agent 1', instructions: 'Inst 1' },
{ name: 'agent2', description: 'Agent 2', instructions: 'Inst 2' },
];
const AgentRegistryMock = (
(await vi.importMock('../agents/registry.js')) as {
AgentRegistry: Mock;
}
).AgentRegistry;
AgentRegistryMock.prototype.getAllDefinitions.mockReturnValue(
mockAgentDefinitions,
);
const SubagentToolWrapperMock = (
(await vi.importMock('../agents/subagent-tool-wrapper.js')) as {
SubagentToolWrapper: Mock;
}
).SubagentToolWrapper;
await config.initialize();
const registerToolMock = (
(await vi.importMock('../tools/tool-registry')) as {
ToolRegistry: { prototype: { registerTool: Mock } };
}
).ToolRegistry.prototype.registerTool;
expect(SubagentToolWrapperMock).toHaveBeenCalledTimes(2);
expect(SubagentToolWrapperMock).toHaveBeenCalledWith(
mockAgentDefinitions[0],
config,
undefined,
);
expect(SubagentToolWrapperMock).toHaveBeenCalledWith(
mockAgentDefinitions[1],
config,
undefined,
);
const calls = registerToolMock.mock.calls;
const registeredWrappers = calls.filter(
(call) => call[0] instanceof SubagentToolWrapperMock,
);
expect(registeredWrappers).toHaveLength(2);
});
it('should not register subagents as tools when enableSubagents is false', async () => {
const params: ConfigParameters = {
...baseParams,
enableSubagents: false,
};
const config = new Config(params);
const SubagentToolWrapperMock = (
(await vi.importMock('../agents/subagent-tool-wrapper.js')) as {
SubagentToolWrapper: Mock;
}
).SubagentToolWrapper;
await config.initialize();
expect(SubagentToolWrapperMock).not.toHaveBeenCalled();
});
describe('with minified tool class names', () => {
beforeEach(() => {
Object.defineProperty(

View File

@@ -76,6 +76,9 @@ import type { PolicyEngineConfig } from '../policy/types.js';
import type { UserTierId } from '../code_assist/types.js';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { AgentRegistry } from '../agents/registry.js';
import { SubagentToolWrapper } from '../agents/subagent-tool-wrapper.js';
export enum ApprovalMode {
DEFAULT = 'default',
AUTO_EDIT = 'autoEdit',
@@ -255,11 +258,13 @@ export interface ConfigParameters {
output?: OutputSettings;
useModelRouter?: boolean;
enableMessageBusIntegration?: boolean;
enableSubagents?: boolean;
}
export class Config {
private toolRegistry!: ToolRegistry;
private promptRegistry!: PromptRegistry;
private agentRegistry!: AgentRegistry;
private readonly sessionId: string;
private fileSystemService: FileSystemService;
private contentGeneratorConfig!: ContentGeneratorConfig;
@@ -345,6 +350,7 @@ export class Config {
private readonly outputSettings: OutputSettings;
private readonly useModelRouter: boolean;
private readonly enableMessageBusIntegration: boolean;
private readonly enableSubagents: boolean;
constructor(params: ConfigParameters) {
this.sessionId = params.sessionId;
@@ -433,6 +439,7 @@ export class Config {
this.useModelRouter = params.useModelRouter ?? false;
this.enableMessageBusIntegration =
params.enableMessageBusIntegration ?? false;
this.enableSubagents = params.enableSubagents ?? false;
this.extensionManagement = params.extensionManagement ?? true;
this.storage = new Storage(this.targetDir);
this.enablePromptCompletion = params.enablePromptCompletion ?? false;
@@ -474,6 +481,10 @@ export class Config {
await this.getGitService();
}
this.promptRegistry = new PromptRegistry();
this.agentRegistry = new AgentRegistry(this);
await this.agentRegistry.initialize();
this.toolRegistry = await this.createToolRegistry();
await this.geminiClient.initialize();
@@ -620,6 +631,10 @@ export class Config {
return this.workspaceContext;
}
getAgentRegistry(): AgentRegistry {
return this.agentRegistry;
}
getToolRegistry(): ToolRegistry {
return this.toolRegistry;
}
@@ -996,6 +1011,10 @@ export class Config {
return this.enableMessageBusIntegration;
}
getEnableSubagents(): boolean {
return this.enableSubagents;
}
async createToolRegistry(): Promise<ToolRegistry> {
const registry = new ToolRegistry(this, this.eventEmitter);
@@ -1087,6 +1106,41 @@ export class Config {
registerCoreTool(WriteTodosTool, this);
}
// Register Subagents as Tools
if (this.getEnableSubagents()) {
const agentDefinitions = this.agentRegistry.getAllDefinitions();
for (const definition of agentDefinitions) {
// We must respect the main allowed/exclude lists for agents too.
const excludeTools = this.getExcludeTools() || [];
const allowedTools = this.getAllowedTools();
const isExcluded = excludeTools.includes(definition.name);
const isAllowed =
!allowedTools || allowedTools.includes(definition.name);
if (isAllowed && !isExcluded) {
try {
const messageBusEnabled = this.getEnableMessageBusIntegration();
const wrapper = new SubagentToolWrapper(
definition,
this,
messageBusEnabled ? this.getMessageBus() : undefined,
);
registry.registerTool(wrapper);
} catch (error) {
console.error(
`Failed to wrap agent '${definition.name}' as a tool:`,
error,
);
}
} else if (this.getDebugMode()) {
console.log(
`[Config] Skipping registration of agent '${definition.name}' due to allow/exclude configuration.`,
);
}
}
}
await registry.discoverAllTools();
return registry;
}