mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 21:14:35 -07:00
feat(core, cli): Add support for agents in settings.json. (#16433)
This commit is contained in:
@@ -557,6 +557,14 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
used.
|
used.
|
||||||
- **Default:** `[]`
|
- **Default:** `[]`
|
||||||
|
|
||||||
|
#### `agents`
|
||||||
|
|
||||||
|
- **`agents.overrides`** (object):
|
||||||
|
- **Description:** Override settings for specific agents, e.g. to disable the
|
||||||
|
agent, set a custom model config, or run config.
|
||||||
|
- **Default:** `{}`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
#### `context`
|
#### `context`
|
||||||
|
|
||||||
- **`context.fileName`** (string | string[]):
|
- **`context.fileName`** (string | string[]):
|
||||||
|
|||||||
@@ -660,6 +660,7 @@ export async function loadCliConfig(
|
|||||||
mcpServers: mcpEnabled ? settings.mcpServers : {},
|
mcpServers: mcpEnabled ? settings.mcpServers : {},
|
||||||
mcpEnabled,
|
mcpEnabled,
|
||||||
extensionsEnabled,
|
extensionsEnabled,
|
||||||
|
agents: settings.agents,
|
||||||
allowedMcpServers: mcpEnabled
|
allowedMcpServers: mcpEnabled
|
||||||
? (argv.allowedMcpServerNames ?? settings.mcp?.allowed)
|
? (argv.allowedMcpServerNames ?? settings.mcp?.allowed)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -785,6 +785,32 @@ const SETTINGS_SCHEMA = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
agents: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Agents',
|
||||||
|
category: 'Advanced',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: {},
|
||||||
|
description: 'Settings for subagents.',
|
||||||
|
showInDialog: false,
|
||||||
|
properties: {
|
||||||
|
overrides: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Agent Overrides',
|
||||||
|
category: 'Advanced',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: {},
|
||||||
|
description:
|
||||||
|
'Override settings for specific agents, e.g. to disable the agent, set a custom model config, or run config.',
|
||||||
|
showInDialog: false,
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'object',
|
||||||
|
ref: 'AgentOverride',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
context: {
|
context: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
label: 'Context',
|
label: 'Context',
|
||||||
@@ -2002,6 +2028,36 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
AgentOverride: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Override settings for a specific agent.',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
modelConfig: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
runConfig: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Run configuration for an agent.',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
maxTimeMinutes: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'The maximum execution time for the agent in minutes.',
|
||||||
|
},
|
||||||
|
maxTurns: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'The maximum number of conversational turns.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to disable the agent.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
CustomTheme: {
|
CustomTheme: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -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', () => {
|
describe('getToolDescription', () => {
|
||||||
it('should return default message when no agents are registered', () => {
|
it('should return default message when no agents are registered', () => {
|
||||||
expect(registry.getToolDescription()).toContain(
|
expect(registry.getToolDescription()).toContain(
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { CliHelpAgent } from './cli-help-agent.js';
|
|||||||
import { A2AClientManager } from './a2a-client-manager.js';
|
import { A2AClientManager } from './a2a-client-manager.js';
|
||||||
import { ADCHandler } from './remote-invocation.js';
|
import { ADCHandler } from './remote-invocation.js';
|
||||||
import { type z } from 'zod';
|
import { type z } from 'zod';
|
||||||
import type { GenerateContentConfig } from '@google/genai';
|
|
||||||
import { debugLogger } from '../utils/debugLogger.js';
|
import { debugLogger } from '../utils/debugLogger.js';
|
||||||
import {
|
import {
|
||||||
DEFAULT_GEMINI_MODEL,
|
DEFAULT_GEMINI_MODEL,
|
||||||
@@ -23,6 +22,10 @@ import {
|
|||||||
isPreviewModel,
|
isPreviewModel,
|
||||||
isAutoModel,
|
isAutoModel,
|
||||||
} from '../config/models.js';
|
} from '../config/models.js';
|
||||||
|
import {
|
||||||
|
type ModelConfig,
|
||||||
|
ModelConfigService,
|
||||||
|
} from '../services/modelConfigService.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the model config alias for a given agent definition.
|
* Returns the model config alias for a given agent definition.
|
||||||
@@ -226,49 +229,83 @@ export class AgentRegistry {
|
|||||||
return;
|
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()) {
|
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
|
||||||
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
|
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,
|
// Register model config. We always create a runtime alias. However,
|
||||||
// if the user is using `auto` as a model string then we also create
|
// if the user is using `auto` as a model string then we also create
|
||||||
// runtime overrides to ensure the subagent generation settings are
|
// runtime overrides to ensure the subagent generation settings are
|
||||||
// respected regardless of the final model string from routing.
|
// respected regardless of the final model string from routing.
|
||||||
// TODO(12916): Migrate sub-agents where possible to static configs.
|
// TODO(12916): Migrate sub-agents where possible to static configs.
|
||||||
const modelConfig = definition.modelConfig;
|
const modelConfig = mergedDefinition.modelConfig;
|
||||||
let model = modelConfig.model;
|
let model = modelConfig.model;
|
||||||
if (model === 'inherit') {
|
if (model === 'inherit') {
|
||||||
model = this.config.getModel();
|
model = this.config.getModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateContentConfig: GenerateContentConfig = {
|
let agentModelConfig: ModelConfig = {
|
||||||
temperature: modelConfig.temp,
|
model,
|
||||||
topP: modelConfig.top_p,
|
generateContentConfig: {
|
||||||
thinkingConfig: {
|
temperature: modelConfig.temp,
|
||||||
includeThoughts: true,
|
topP: modelConfig.top_p,
|
||||||
thinkingBudget: modelConfig.thinkingBudget ?? -1,
|
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(
|
this.config.modelConfigService.registerRuntimeModelConfig(
|
||||||
getModelConfigAlias(definition),
|
getModelConfigAlias(mergedDefinition),
|
||||||
{
|
{
|
||||||
modelConfig: {
|
modelConfig: agentModelConfig,
|
||||||
model,
|
|
||||||
generateContentConfig,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isAutoModel(model)) {
|
if (agentModelConfig.model && isAutoModel(agentModelConfig.model)) {
|
||||||
this.config.modelConfigService.registerRuntimeModelOverride({
|
this.config.modelConfigService.registerRuntimeModelOverride({
|
||||||
match: {
|
match: {
|
||||||
overrideScope: definition.name,
|
overrideScope: mergedDefinition.name,
|
||||||
},
|
},
|
||||||
modelConfig: {
|
modelConfig: {
|
||||||
generateContentConfig,
|
generateContentConfig: agentModelConfig.generateContentConfig,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -292,6 +329,17 @@ export class AgentRegistry {
|
|||||||
return;
|
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()) {
|
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
|
||||||
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
|
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ import type { FallbackModelHandler } from '../fallback/types.js';
|
|||||||
import { ModelAvailabilityService } from '../availability/modelAvailabilityService.js';
|
import { ModelAvailabilityService } from '../availability/modelAvailabilityService.js';
|
||||||
import { ModelRouterService } from '../routing/modelRouterService.js';
|
import { ModelRouterService } from '../routing/modelRouterService.js';
|
||||||
import { OutputFormat } from '../output/types.js';
|
import { OutputFormat } from '../output/types.js';
|
||||||
import type { ModelConfigServiceConfig } from '../services/modelConfigService.js';
|
import type {
|
||||||
|
ModelConfig,
|
||||||
|
ModelConfigServiceConfig,
|
||||||
|
} from '../services/modelConfigService.js';
|
||||||
import { ModelConfigService } from '../services/modelConfigService.js';
|
import { ModelConfigService } from '../services/modelConfigService.js';
|
||||||
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
|
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
|
||||||
import { ContextManager } from '../services/contextManager.js';
|
import { ContextManager } from '../services/contextManager.js';
|
||||||
@@ -159,6 +162,21 @@ export interface CliHelpAgentSettings {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AgentRunConfig {
|
||||||
|
maxTimeMinutes?: number;
|
||||||
|
maxTurns?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentOverride {
|
||||||
|
modelConfig?: ModelConfig;
|
||||||
|
runConfig?: AgentRunConfig;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentSettings {
|
||||||
|
overrides?: Record<string, AgentOverride>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All information required in CLI to handle an extension. Defined in Core so
|
* All information required in CLI to handle an extension. Defined in Core so
|
||||||
* that the collection of loaded, active, and inactive extensions can be passed
|
* that the collection of loaded, active, and inactive extensions can be passed
|
||||||
@@ -364,6 +382,7 @@ export interface ConfigParameters {
|
|||||||
onModelChange?: (model: string) => void;
|
onModelChange?: (model: string) => void;
|
||||||
mcpEnabled?: boolean;
|
mcpEnabled?: boolean;
|
||||||
extensionsEnabled?: boolean;
|
extensionsEnabled?: boolean;
|
||||||
|
agents?: AgentSettings;
|
||||||
onReload?: () => Promise<{ disabledSkills?: string[] }>;
|
onReload?: () => Promise<{ disabledSkills?: string[] }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,6 +515,7 @@ export class Config {
|
|||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
private readonly enableAgents: boolean;
|
private readonly enableAgents: boolean;
|
||||||
|
private readonly agents: AgentSettings;
|
||||||
private readonly skillsSupport: boolean;
|
private readonly skillsSupport: boolean;
|
||||||
private disabledSkills: string[];
|
private disabledSkills: string[];
|
||||||
|
|
||||||
@@ -570,6 +590,7 @@ export class Config {
|
|||||||
this.model = params.model;
|
this.model = params.model;
|
||||||
this._activeModel = params.model;
|
this._activeModel = params.model;
|
||||||
this.enableAgents = params.enableAgents ?? false;
|
this.enableAgents = params.enableAgents ?? false;
|
||||||
|
this.agents = params.agents ?? {};
|
||||||
this.disableLLMCorrection = params.disableLLMCorrection ?? false;
|
this.disableLLMCorrection = params.disableLLMCorrection ?? false;
|
||||||
this.skillsSupport = params.skillsSupport ?? false;
|
this.skillsSupport = params.skillsSupport ?? false;
|
||||||
this.disabledSkills = params.disabledSkills ?? [];
|
this.disabledSkills = params.disabledSkills ?? [];
|
||||||
@@ -1443,6 +1464,10 @@ export class Config {
|
|||||||
return this.noBrowser;
|
return this.noBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAgentsSettings(): AgentSettings {
|
||||||
|
return this.agents;
|
||||||
|
}
|
||||||
|
|
||||||
isBrowserLaunchSuppressed(): boolean {
|
isBrowserLaunchSuppressed(): boolean {
|
||||||
return this.getNoBrowser() || !shouldAttemptBrowserLaunch();
|
return this.getNoBrowser() || !shouldAttemptBrowserLaunch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,14 +119,10 @@ export class ModelConfigService {
|
|||||||
...this.runtimeAliases,
|
...this.runtimeAliases,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { aliasChain, baseModel, resolvedConfig } = this.resolveAliasChain(
|
||||||
aliasChain,
|
context.model,
|
||||||
baseModel: initialBaseModel,
|
allAliases,
|
||||||
resolvedConfig: initialResolvedConfig,
|
);
|
||||||
} = this.resolveAliasChain(context.model, allAliases);
|
|
||||||
|
|
||||||
let baseModel = initialBaseModel;
|
|
||||||
let resolvedConfig = initialResolvedConfig;
|
|
||||||
|
|
||||||
const modelToLevel = this.buildModelLevelMap(aliasChain, baseModel);
|
const modelToLevel = this.buildModelLevelMap(aliasChain, baseModel);
|
||||||
const allOverrides = [
|
const allOverrides = [
|
||||||
@@ -142,19 +138,22 @@ export class ModelConfigService {
|
|||||||
|
|
||||||
this.sortOverrides(matches);
|
this.sortOverrides(matches);
|
||||||
|
|
||||||
|
let currentConfig: ModelConfig = {
|
||||||
|
model: baseModel,
|
||||||
|
generateContentConfig: resolvedConfig,
|
||||||
|
};
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
if (match.modelConfig.model) {
|
currentConfig = ModelConfigService.merge(
|
||||||
baseModel = match.modelConfig.model;
|
currentConfig,
|
||||||
}
|
match.modelConfig,
|
||||||
if (match.modelConfig.generateContentConfig) {
|
);
|
||||||
resolvedConfig = this.deepMerge(
|
|
||||||
resolvedConfig,
|
|
||||||
match.modelConfig.generateContentConfig,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { model: baseModel, generateContentConfig: resolvedConfig };
|
return {
|
||||||
|
model: currentConfig.model,
|
||||||
|
generateContentConfig: currentConfig.generateContentConfig ?? {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveAliasChain(
|
private resolveAliasChain(
|
||||||
@@ -165,8 +164,6 @@ export class ModelConfigService {
|
|||||||
baseModel: string | undefined;
|
baseModel: string | undefined;
|
||||||
resolvedConfig: GenerateContentConfig;
|
resolvedConfig: GenerateContentConfig;
|
||||||
} {
|
} {
|
||||||
let baseModel: string | undefined = undefined;
|
|
||||||
let resolvedConfig: GenerateContentConfig = {};
|
|
||||||
const aliasChain: string[] = [];
|
const aliasChain: string[] = [];
|
||||||
|
|
||||||
if (allAliases[requestedModel]) {
|
if (allAliases[requestedModel]) {
|
||||||
@@ -194,17 +191,19 @@ export class ModelConfigService {
|
|||||||
|
|
||||||
// Root-to-Leaf chain for merging and level assignment.
|
// Root-to-Leaf chain for merging and level assignment.
|
||||||
const reversedChain = [...aliasChain].reverse();
|
const reversedChain = [...aliasChain].reverse();
|
||||||
|
let resolvedConfig: ModelConfig = {};
|
||||||
for (const aliasName of reversedChain) {
|
for (const aliasName of reversedChain) {
|
||||||
const alias = allAliases[aliasName];
|
const alias = allAliases[aliasName];
|
||||||
if (alias.modelConfig.model) {
|
resolvedConfig = ModelConfigService.merge(
|
||||||
baseModel = alias.modelConfig.model;
|
|
||||||
}
|
|
||||||
resolvedConfig = this.deepMerge(
|
|
||||||
resolvedConfig,
|
resolvedConfig,
|
||||||
alias.modelConfig.generateContentConfig,
|
alias.modelConfig,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { aliasChain: reversedChain, baseModel, resolvedConfig };
|
return {
|
||||||
|
aliasChain: reversedChain,
|
||||||
|
baseModel: resolvedConfig.model,
|
||||||
|
resolvedConfig: resolvedConfig.generateContentConfig ?? {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -298,21 +297,36 @@ export class ModelConfigService {
|
|||||||
} as ResolvedModelConfig;
|
} as ResolvedModelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isObject(item: unknown): item is Record<string, unknown> {
|
static isObject(item: unknown): item is Record<string, unknown> {
|
||||||
return !!item && typeof item === 'object' && !Array.isArray(item);
|
return !!item && typeof item === 'object' && !Array.isArray(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private deepMerge(
|
/**
|
||||||
config1: GenerateContentConfig | undefined,
|
* Merges an override `ModelConfig` into a base `ModelConfig`.
|
||||||
config2: GenerateContentConfig | undefined,
|
* The override's model name takes precedence if provided.
|
||||||
): Record<string, unknown> {
|
* The `generateContentConfig` properties are deeply merged.
|
||||||
return this.genericDeepMerge(
|
*/
|
||||||
config1 as Record<string, unknown> | undefined,
|
static merge(base: ModelConfig, override: ModelConfig): ModelConfig {
|
||||||
config2 as Record<string, unknown> | undefined,
|
return {
|
||||||
);
|
model: override.model ?? base.model,
|
||||||
|
generateContentConfig: ModelConfigService.deepMerge(
|
||||||
|
base.generateContentConfig,
|
||||||
|
override.generateContentConfig,
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private genericDeepMerge(
|
static deepMerge(
|
||||||
|
config1: GenerateContentConfig | undefined,
|
||||||
|
config2: GenerateContentConfig | undefined,
|
||||||
|
): GenerateContentConfig {
|
||||||
|
return ModelConfigService.genericDeepMerge(
|
||||||
|
config1 as Record<string, unknown> | undefined,
|
||||||
|
config2 as Record<string, unknown> | undefined,
|
||||||
|
) as GenerateContentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static genericDeepMerge(
|
||||||
...objects: Array<Record<string, unknown> | undefined>
|
...objects: Array<Record<string, unknown> | undefined>
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
return objects.reduce((acc: Record<string, unknown>, obj) => {
|
return objects.reduce((acc: Record<string, unknown>, obj) => {
|
||||||
@@ -329,8 +343,11 @@ export class ModelConfigService {
|
|||||||
// override the base array.
|
// override the base array.
|
||||||
// TODO(joshualitt): Consider knobs here, i.e. opt-in to deep merging
|
// TODO(joshualitt): Consider knobs here, i.e. opt-in to deep merging
|
||||||
// arrays on a case-by-case basis.
|
// arrays on a case-by-case basis.
|
||||||
if (this.isObject(accValue) && this.isObject(objValue)) {
|
if (
|
||||||
acc[key] = this.deepMerge(accValue, objValue);
|
ModelConfigService.isObject(accValue) &&
|
||||||
|
ModelConfigService.isObject(objValue)
|
||||||
|
) {
|
||||||
|
acc[key] = ModelConfigService.genericDeepMerge(accValue, objValue);
|
||||||
} else {
|
} else {
|
||||||
acc[key] = objValue;
|
acc[key] = objValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -926,6 +926,26 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"agents": {
|
||||||
|
"title": "Agents",
|
||||||
|
"description": "Settings for subagents.",
|
||||||
|
"markdownDescription": "Settings for subagents.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `{}`",
|
||||||
|
"default": {},
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"overrides": {
|
||||||
|
"title": "Agent Overrides",
|
||||||
|
"description": "Override settings for specific agents, e.g. to disable the agent, set a custom model config, or run config.",
|
||||||
|
"markdownDescription": "Override settings for specific agents, e.g. to disable the agent, set a custom model config, or run config.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `{}`",
|
||||||
|
"default": {},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/$defs/AgentOverride"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"context": {
|
"context": {
|
||||||
"title": "Context",
|
"title": "Context",
|
||||||
"description": "Settings for managing context provided to the model.",
|
"description": "Settings for managing context provided to the model.",
|
||||||
@@ -1854,6 +1874,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AgentOverride": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Override settings for a specific agent.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"modelConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"runConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Run configuration for an agent.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"maxTimeMinutes": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The maximum execution time for the agent in minutes."
|
||||||
|
},
|
||||||
|
"maxTurns": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The maximum number of conversational turns."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to disable the agent."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"CustomTheme": {
|
"CustomTheme": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Custom theme definition used for styling Gemini CLI output. Colors are provided as hex strings or named ANSI colors.",
|
"description": "Custom theme definition used for styling Gemini CLI output. Colors are provided as hex strings or named ANSI colors.",
|
||||||
|
|||||||
Reference in New Issue
Block a user