Implement support for subagents as extensions. (#16473)

This commit is contained in:
Christian Gunderman
2026-01-13 19:09:22 +00:00
committed by GitHub
parent 0f7a136612
commit aa52462550
6 changed files with 223 additions and 3 deletions
+56 -2
View File
@@ -8,7 +8,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { AgentRegistry, getModelConfigAlias } from './registry.js';
import { makeFakeConfig } from '../test-utils/config.js';
import type { AgentDefinition, LocalAgentDefinition } from './types.js';
import type { Config } from '../config/config.js';
import type { Config, GeminiCLIExtension } from '../config/config.js';
import { debugLogger } from '../utils/debugLogger.js';
import { coreEvents, CoreEvent } from '../utils/events.js';
import { A2AClientManager } from './a2a-client-manager.js';
@@ -20,6 +20,7 @@ import {
PREVIEW_GEMINI_MODEL_AUTO,
} from '../config/models.js';
import * as tomlLoader from './agentLoader.js';
import { SimpleExtensionLoader } from '../utils/extensionLoader.js';
vi.mock('./agentLoader.js', () => ({
loadAgentsFromDirectory: vi
@@ -230,7 +231,7 @@ describe('AgentRegistry', () => {
expect(registry.getDefinition('cli_help')).toBeDefined();
});
it('should NOT register CLI help agent if disabled', async () => {
it('should register CLI help agent if disabled', async () => {
const config = makeFakeConfig({
cliHelpAgentSettings: { enabled: false },
});
@@ -240,6 +241,59 @@ describe('AgentRegistry', () => {
expect(registry.getDefinition('cli_help')).toBeUndefined();
});
it('should load agents from active extensions', async () => {
const extensionAgent = {
...MOCK_AGENT_V1,
name: 'extension-agent',
};
const extensions: GeminiCLIExtension[] = [
{
name: 'test-extension',
isActive: true,
agents: [extensionAgent],
version: '1.0.0',
path: '/path/to/extension',
contextFiles: [],
id: 'test-extension-id',
},
];
const mockConfig = makeFakeConfig({
extensionLoader: new SimpleExtensionLoader(extensions),
enableAgents: true,
});
const registry = new TestableAgentRegistry(mockConfig);
await registry.initialize();
expect(registry.getDefinition('extension-agent')).toEqual(extensionAgent);
});
it('should NOT load agents from inactive extensions', async () => {
const extensionAgent = {
...MOCK_AGENT_V1,
name: 'extension-agent',
};
const extensions: GeminiCLIExtension[] = [
{
name: 'test-extension',
isActive: false,
agents: [extensionAgent],
version: '1.0.0',
path: '/path/to/extension',
contextFiles: [],
id: 'test-extension-id',
},
];
const mockConfig = makeFakeConfig({
extensionLoader: new SimpleExtensionLoader(extensions),
});
const registry = new TestableAgentRegistry(mockConfig);
await registry.initialize();
expect(registry.getDefinition('extension-agent')).toBeUndefined();
});
});
describe('registration logic', () => {
+11 -1
View File
@@ -14,6 +14,7 @@ import { CliHelpAgent } from './cli-help-agent.js';
import { A2AClientManager } from './a2a-client-manager.js';
import { ADCHandler } from './remote-invocation.js';
import { type z } from 'zod';
import type { GenerateContentConfig } from '@google/genai';
import { debugLogger } from '../utils/debugLogger.js';
import {
DEFAULT_GEMINI_MODEL,
@@ -120,6 +121,15 @@ export class AgentRegistry {
);
}
// Load agents from extensions
for (const extension of this.config.getExtensions()) {
if (extension.isActive && extension.agents) {
await Promise.allSettled(
extension.agents.map((agent) => this.registerAgent(agent)),
);
}
}
if (this.config.getDebugMode()) {
debugLogger.log(
`[AgentRegistry] Loaded with ${this.agents.size} agents.`,
@@ -233,7 +243,7 @@ export class AgentRegistry {
model = this.config.getModel();
}
const generateContentConfig = {
const generateContentConfig: GenerateContentConfig = {
temperature: modelConfig.temp,
topP: modelConfig.top_p,
thinkingConfig: {
+2
View File
@@ -101,6 +101,7 @@ import { ExperimentFlags } from '../code_assist/experiments/flagNames.js';
import { debugLogger } from '../utils/debugLogger.js';
import { SkillManager, type SkillDefinition } from '../skills/skillManager.js';
import { startupProfiler } from '../telemetry/startupProfiler.js';
import type { AgentDefinition } from '../agents/types.js';
export interface AccessibilitySettings {
disableLoadingPhrases?: boolean;
@@ -178,6 +179,7 @@ export interface GeminiCLIExtension {
settings?: ExtensionSetting[];
resolvedSettings?: ResolvedExtensionSetting[];
skills?: SkillDefinition[];
agents?: AgentDefinition[];
}
export interface ExtensionInstallMetadata {
+1
View File
@@ -126,6 +126,7 @@ export * from './prompts/mcp-prompts.js';
// Export agent definitions
export * from './agents/types.js';
export * from './agents/agentLoader.js';
// Export specific tool logic
export * from './tools/read-file.js';