feat(core, cli): Add support for agents in settings.json. (#16433)

This commit is contained in:
joshualitt
2026-01-13 12:16:02 -08:00
committed by GitHub
parent e931ebe581
commit 92e31e3c4a
8 changed files with 382 additions and 56 deletions

View File

@@ -619,6 +619,127 @@ describe('AgentRegistry', () => {
);
});
});
describe('overrides', () => {
it('should skip registration if agent is disabled in settings', async () => {
const config = makeFakeConfig({
agents: {
overrides: {
MockAgent: { disabled: true },
},
},
});
const registry = new TestableAgentRegistry(config);
await registry.testRegisterAgent(MOCK_AGENT_V1);
expect(registry.getDefinition('MockAgent')).toBeUndefined();
});
it('should skip remote agent registration if disabled in settings', async () => {
const config = makeFakeConfig({
agents: {
overrides: {
RemoteAgent: { disabled: true },
},
},
});
const registry = new TestableAgentRegistry(config);
const remoteAgent: AgentDefinition = {
kind: 'remote',
name: 'RemoteAgent',
description: 'A remote agent',
agentCardUrl: 'https://example.com/card',
inputConfig: { inputs: {} },
};
await registry.testRegisterAgent(remoteAgent);
expect(registry.getDefinition('RemoteAgent')).toBeUndefined();
});
it('should merge runConfig overrides', async () => {
const config = makeFakeConfig({
agents: {
overrides: {
MockAgent: {
runConfig: { maxTurns: 50 },
},
},
},
});
const registry = new TestableAgentRegistry(config);
await registry.testRegisterAgent(MOCK_AGENT_V1);
const def = registry.getDefinition('MockAgent') as LocalAgentDefinition;
expect(def.runConfig.max_turns).toBe(50);
expect(def.runConfig.max_time_minutes).toBe(
MOCK_AGENT_V1.runConfig.max_time_minutes,
);
});
it('should apply modelConfig overrides', async () => {
const config = makeFakeConfig({
agents: {
overrides: {
MockAgent: {
modelConfig: {
model: 'overridden-model',
generateContentConfig: {
temperature: 0.5,
},
},
},
},
},
});
const registry = new TestableAgentRegistry(config);
await registry.testRegisterAgent(MOCK_AGENT_V1);
const resolved = config.modelConfigService.getResolvedConfig({
model: getModelConfigAlias(MOCK_AGENT_V1),
});
expect(resolved.model).toBe('overridden-model');
expect(resolved.generateContentConfig.temperature).toBe(0.5);
// topP should still be MOCK_AGENT_V1.modelConfig.top_p (1) because we merged
expect(resolved.generateContentConfig.topP).toBe(1);
});
it('should deep merge generateContentConfig (e.g. thinkingConfig)', async () => {
const config = makeFakeConfig({
agents: {
overrides: {
MockAgent: {
modelConfig: {
generateContentConfig: {
thinkingConfig: {
thinkingBudget: 16384,
},
},
},
},
},
},
});
const registry = new TestableAgentRegistry(config);
await registry.testRegisterAgent(MOCK_AGENT_V1);
const resolved = config.modelConfigService.getResolvedConfig({
model: getModelConfigAlias(MOCK_AGENT_V1),
});
expect(resolved.generateContentConfig.thinkingConfig).toEqual({
includeThoughts: true, // Preserved from default
thinkingBudget: 16384, // Overridden
});
});
});
describe('getToolDescription', () => {
it('should return default message when no agents are registered', () => {
expect(registry.getToolDescription()).toContain(

View File

@@ -14,7 +14,6 @@ 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,
@@ -23,6 +22,10 @@ import {
isPreviewModel,
isAutoModel,
} from '../config/models.js';
import {
type ModelConfig,
ModelConfigService,
} from '../services/modelConfigService.js';
/**
* Returns the model config alias for a given agent definition.
@@ -226,49 +229,83 @@ export class AgentRegistry {
return;
}
const overrides =
this.config.getAgentsSettings().overrides?.[definition.name];
if (overrides?.disabled) {
if (this.config.getDebugMode()) {
debugLogger.log(
`[AgentRegistry] Skipping disabled agent '${definition.name}'`,
);
}
return;
}
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
}
this.agents.set(definition.name, definition);
// TODO(16443): Refactor definition merging logic into a helper.
// To do this, we need to align the definition of the internal `Definition`
// type with the one exported in settings.json.
const mergedDefinition = {
...definition,
runConfig: {
...definition.runConfig,
max_time_minutes:
overrides?.runConfig?.maxTimeMinutes ??
definition.runConfig.max_time_minutes,
max_turns:
overrides?.runConfig?.maxTurns ?? definition.runConfig.max_turns,
},
};
this.agents.set(mergedDefinition.name, mergedDefinition);
// Register model config. We always create a runtime alias. However,
// if the user is using `auto` as a model string then we also create
// runtime overrides to ensure the subagent generation settings are
// respected regardless of the final model string from routing.
// TODO(12916): Migrate sub-agents where possible to static configs.
const modelConfig = definition.modelConfig;
const modelConfig = mergedDefinition.modelConfig;
let model = modelConfig.model;
if (model === 'inherit') {
model = this.config.getModel();
}
const generateContentConfig: GenerateContentConfig = {
temperature: modelConfig.temp,
topP: modelConfig.top_p,
thinkingConfig: {
includeThoughts: true,
thinkingBudget: modelConfig.thinkingBudget ?? -1,
let agentModelConfig: ModelConfig = {
model,
generateContentConfig: {
temperature: modelConfig.temp,
topP: modelConfig.top_p,
thinkingConfig: {
includeThoughts: true,
thinkingBudget: modelConfig.thinkingBudget ?? -1,
},
},
};
// Apply standardized modelConfig overrides if present.
if (overrides?.modelConfig) {
agentModelConfig = ModelConfigService.merge(
agentModelConfig,
overrides.modelConfig,
);
}
this.config.modelConfigService.registerRuntimeModelConfig(
getModelConfigAlias(definition),
getModelConfigAlias(mergedDefinition),
{
modelConfig: {
model,
generateContentConfig,
},
modelConfig: agentModelConfig,
},
);
if (isAutoModel(model)) {
if (agentModelConfig.model && isAutoModel(agentModelConfig.model)) {
this.config.modelConfigService.registerRuntimeModelOverride({
match: {
overrideScope: definition.name,
overrideScope: mergedDefinition.name,
},
modelConfig: {
generateContentConfig,
generateContentConfig: agentModelConfig.generateContentConfig,
},
});
}
@@ -292,6 +329,17 @@ export class AgentRegistry {
return;
}
const overrides =
this.config.getAgentsSettings().overrides?.[definition.name];
if (overrides?.disabled) {
if (this.config.getDebugMode()) {
debugLogger.log(
`[AgentRegistry] Skipping disabled remote agent '${definition.name}'`,
);
}
return;
}
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
}