mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 22:48:03 +00:00
feat(agents): Add remote agents to agent registry (#15711)
This commit is contained in:
42
package-lock.json
generated
42
package-lock.json
generated
@@ -2500,6 +2500,7 @@
|
||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^6.0.0",
|
||||
"@octokit/graphql": "^9.0.2",
|
||||
@@ -2680,6 +2681,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -2713,6 +2715,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
|
||||
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
@@ -3081,6 +3084,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
|
||||
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.0.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -3114,6 +3118,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
|
||||
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.0.1",
|
||||
"@opentelemetry/resources": "2.0.1"
|
||||
@@ -3166,6 +3171,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
|
||||
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.0.1",
|
||||
"@opentelemetry/resources": "2.0.1",
|
||||
@@ -4403,6 +4409,7 @@
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4680,6 +4687,7 @@
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
@@ -5691,6 +5699,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -6135,8 +6144,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
@@ -7426,7 +7434,6 @@
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
@@ -8750,6 +8757,7 @@
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -9352,7 +9360,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -9362,7 +9369,6 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -9372,7 +9378,6 @@
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
@@ -9626,7 +9631,6 @@
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
@@ -9645,7 +9649,6 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -9654,15 +9657,13 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
@@ -10948,6 +10949,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.6.tgz",
|
||||
"integrity": "sha512-QHl6l1cl3zPCaRMzt9TUbTX6Q5SzvkGEZDDad0DmSf5SPmT1/90k6pGPejEvDCJprkitwObXpPaTWGHItqsy4g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
@@ -14141,8 +14143,7 @@
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "3.0.0",
|
||||
@@ -14723,6 +14724,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -14733,6 +14735,7 @@
|
||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
@@ -16992,6 +16995,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -17218,7 +17222,8 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.3",
|
||||
@@ -17226,6 +17231,7 @@
|
||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -17410,6 +17416,7 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -17572,7 +17579,6 @@
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
@@ -17628,6 +17634,7 @@
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -17744,6 +17751,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -17757,6 +17765,7 @@
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@@ -18463,6 +18472,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -18894,6 +18904,7 @@
|
||||
"version": "0.24.0-nightly.20251227.37be16243",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@google-cloud/logging": "^11.2.1",
|
||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
@@ -19025,6 +19036,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@google-cloud/logging": "^11.2.1",
|
||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { AgentDefinition, LocalAgentDefinition } from './types.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { coreEvents, CoreEvent } from '../utils/events.js';
|
||||
import { A2AClientManager } from './a2a-client-manager.js';
|
||||
import {
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
GEMINI_MODEL_ALIAS_AUTO,
|
||||
@@ -26,10 +27,16 @@ vi.mock('./toml-loader.js', () => ({
|
||||
.mockResolvedValue({ agents: [], errors: [] }),
|
||||
}));
|
||||
|
||||
vi.mock('./a2a-client-manager.js', () => ({
|
||||
A2AClientManager: {
|
||||
getInstance: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// A test-only subclass to expose the protected `registerAgent` method.
|
||||
class TestableAgentRegistry extends AgentRegistry {
|
||||
testRegisterAgent(definition: AgentDefinition): void {
|
||||
this.registerAgent(definition);
|
||||
async testRegisterAgent(definition: AgentDefinition): Promise<void> {
|
||||
await this.registerAgent(definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +244,8 @@ describe('AgentRegistry', () => {
|
||||
});
|
||||
|
||||
describe('registration logic', () => {
|
||||
it('should register a valid agent definition', () => {
|
||||
registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
it('should register a valid agent definition', async () => {
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
expect(registry.getDefinition('MockAgent')).toEqual(MOCK_AGENT_V1);
|
||||
expect(
|
||||
mockConfig.modelConfigService.getResolvedConfig({
|
||||
@@ -257,7 +264,7 @@ describe('AgentRegistry', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should register a remote agent definition', () => {
|
||||
it('should register a remote agent definition', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'RemoteAgent',
|
||||
@@ -265,11 +272,16 @@ describe('AgentRegistry', () => {
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputs: {} },
|
||||
};
|
||||
registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue({ name: 'RemoteAgent' }),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
expect(registry.getDefinition('RemoteAgent')).toEqual(remoteAgent);
|
||||
});
|
||||
|
||||
it('should log remote agent registration in debug mode', () => {
|
||||
it('should log remote agent registration in debug mode', async () => {
|
||||
const debugConfig = makeFakeConfig({ debugMode: true });
|
||||
const debugRegistry = new TestableAgentRegistry(debugConfig);
|
||||
const debugLogSpy = vi
|
||||
@@ -284,31 +296,35 @@ describe('AgentRegistry', () => {
|
||||
inputConfig: { inputs: {} },
|
||||
};
|
||||
|
||||
debugRegistry.testRegisterAgent(remoteAgent);
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue({ name: 'RemoteAgent' }),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await debugRegistry.testRegisterAgent(remoteAgent);
|
||||
|
||||
expect(debugLogSpy).toHaveBeenCalledWith(
|
||||
`[AgentRegistry] Registered remote agent 'RemoteAgent' with card: https://example.com/card`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle special characters in agent names', () => {
|
||||
it('should handle special characters in agent names', async () => {
|
||||
const specialAgent = {
|
||||
...MOCK_AGENT_V1,
|
||||
name: 'Agent-123_$pecial.v2',
|
||||
};
|
||||
registry.testRegisterAgent(specialAgent);
|
||||
await registry.testRegisterAgent(specialAgent);
|
||||
expect(registry.getDefinition('Agent-123_$pecial.v2')).toEqual(
|
||||
specialAgent,
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject an agent definition missing a name', () => {
|
||||
it('should reject an agent definition missing a name', async () => {
|
||||
const invalidAgent = { ...MOCK_AGENT_V1, name: '' };
|
||||
const debugWarnSpy = vi
|
||||
.spyOn(debugLogger, 'warn')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
registry.testRegisterAgent(invalidAgent);
|
||||
await registry.testRegisterAgent(invalidAgent);
|
||||
|
||||
expect(registry.getDefinition('MockAgent')).toBeUndefined();
|
||||
expect(debugWarnSpy).toHaveBeenCalledWith(
|
||||
@@ -316,13 +332,13 @@ describe('AgentRegistry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject an agent definition missing a description', () => {
|
||||
it('should reject an agent definition missing a description', async () => {
|
||||
const invalidAgent = { ...MOCK_AGENT_V1, description: '' };
|
||||
const debugWarnSpy = vi
|
||||
.spyOn(debugLogger, 'warn')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
registry.testRegisterAgent(invalidAgent as AgentDefinition);
|
||||
await registry.testRegisterAgent(invalidAgent as AgentDefinition);
|
||||
|
||||
expect(registry.getDefinition('MockAgent')).toBeUndefined();
|
||||
expect(debugWarnSpy).toHaveBeenCalledWith(
|
||||
@@ -330,41 +346,41 @@ describe('AgentRegistry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should overwrite an existing agent definition', () => {
|
||||
registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
it('should overwrite an existing agent definition', async () => {
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
expect(registry.getDefinition('MockAgent')?.description).toBe(
|
||||
'Mock Description V1',
|
||||
);
|
||||
|
||||
registry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
expect(registry.getDefinition('MockAgent')?.description).toBe(
|
||||
'Mock Description V2 (Updated)',
|
||||
);
|
||||
expect(registry.getAllDefinitions()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should log overwrites when in debug mode', () => {
|
||||
it('should log overwrites when in debug mode', async () => {
|
||||
const debugConfig = makeFakeConfig({ debugMode: true });
|
||||
const debugRegistry = new TestableAgentRegistry(debugConfig);
|
||||
const debugLogSpy = vi
|
||||
.spyOn(debugLogger, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
debugRegistry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
debugRegistry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
await debugRegistry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
await debugRegistry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
|
||||
expect(debugLogSpy).toHaveBeenCalledWith(
|
||||
`[AgentRegistry] Overriding agent 'MockAgent'`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not log overwrites when not in debug mode', () => {
|
||||
it('should not log overwrites when not in debug mode', async () => {
|
||||
const debugLogSpy = vi
|
||||
.spyOn(debugLogger, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
registry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V2);
|
||||
|
||||
expect(debugLogSpy).not.toHaveBeenCalledWith(
|
||||
`[AgentRegistry] Overriding agent 'MockAgent'`,
|
||||
@@ -373,12 +389,10 @@ describe('AgentRegistry', () => {
|
||||
|
||||
it('should handle bulk registrations correctly', async () => {
|
||||
const promises = Array.from({ length: 100 }, (_, i) =>
|
||||
Promise.resolve(
|
||||
registry.testRegisterAgent({
|
||||
...MOCK_AGENT_V1,
|
||||
name: `Agent${i}`,
|
||||
}),
|
||||
),
|
||||
registry.testRegisterAgent({
|
||||
...MOCK_AGENT_V1,
|
||||
name: `Agent${i}`,
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
@@ -387,7 +401,7 @@ describe('AgentRegistry', () => {
|
||||
});
|
||||
|
||||
describe('inheritance and refresh', () => {
|
||||
it('should resolve "inherit" to the current model from configuration', () => {
|
||||
it('should resolve "inherit" to the current model from configuration', async () => {
|
||||
const config = makeFakeConfig({ model: 'current-model' });
|
||||
const registry = new TestableAgentRegistry(config);
|
||||
|
||||
@@ -396,7 +410,7 @@ describe('AgentRegistry', () => {
|
||||
modelConfig: { ...MOCK_AGENT_V1.modelConfig, model: 'inherit' },
|
||||
};
|
||||
|
||||
registry.testRegisterAgent(agent);
|
||||
await registry.testRegisterAgent(agent);
|
||||
|
||||
const resolved = config.modelConfigService.getResolvedConfig({
|
||||
model: getModelConfigAlias(agent),
|
||||
@@ -415,7 +429,7 @@ describe('AgentRegistry', () => {
|
||||
modelConfig: { ...MOCK_AGENT_V1.modelConfig, model: 'inherit' },
|
||||
};
|
||||
|
||||
registry.testRegisterAgent(agent);
|
||||
await registry.testRegisterAgent(agent);
|
||||
|
||||
// Verify initial state
|
||||
let resolved = config.modelConfigService.getResolvedConfig({
|
||||
@@ -429,6 +443,17 @@ describe('AgentRegistry', () => {
|
||||
model: 'new-model',
|
||||
});
|
||||
|
||||
// Since the listener is async but not awaited by emit, we should manually
|
||||
// trigger refresh or wait.
|
||||
await vi.waitFor(() => {
|
||||
const resolved = config.modelConfigService.getResolvedConfig({
|
||||
model: getModelConfigAlias(agent),
|
||||
});
|
||||
if (resolved.model !== 'new-model') {
|
||||
throw new Error('Model not updated yet');
|
||||
}
|
||||
});
|
||||
|
||||
// Verify refreshed state
|
||||
resolved = config.modelConfigService.getResolvedConfig({
|
||||
model: getModelConfigAlias(agent),
|
||||
@@ -443,9 +468,9 @@ describe('AgentRegistry', () => {
|
||||
name: 'AnotherAgent',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
registry.testRegisterAgent(ANOTHER_AGENT);
|
||||
beforeEach(async () => {
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
await registry.testRegisterAgent(ANOTHER_AGENT);
|
||||
});
|
||||
|
||||
it('getDefinition should return the correct definition', () => {
|
||||
@@ -472,9 +497,9 @@ describe('AgentRegistry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return formatted list of agents when agents are available', () => {
|
||||
registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
registry.testRegisterAgent({
|
||||
it('should return formatted list of agents when agents are available', async () => {
|
||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||
await registry.testRegisterAgent({
|
||||
...MOCK_AGENT_V2,
|
||||
name: 'AnotherAgent',
|
||||
description: 'Another agent description',
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { AgentDefinition } from './types.js';
|
||||
import { loadAgentsFromDirectory } from './toml-loader.js';
|
||||
import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
|
||||
import { IntrospectionAgent } from './introspection-agent.js';
|
||||
import { A2AClientManager } from './a2a-client-manager.js';
|
||||
import { type z } from 'zod';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import {
|
||||
@@ -47,7 +48,12 @@ export class AgentRegistry {
|
||||
this.loadBuiltInAgents();
|
||||
|
||||
coreEvents.on(CoreEvent.ModelChanged, () => {
|
||||
this.refreshAgents();
|
||||
this.refreshAgents().catch((e) => {
|
||||
debugLogger.error(
|
||||
'[AgentRegistry] Failed to refresh agents on model change:',
|
||||
e,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (!this.config.isAgentsEnabled()) {
|
||||
@@ -63,9 +69,9 @@ export class AgentRegistry {
|
||||
);
|
||||
coreEvents.emitFeedback('error', `Agent loading error: ${error.message}`);
|
||||
}
|
||||
for (const agent of userAgents.agents) {
|
||||
this.registerAgent(agent);
|
||||
}
|
||||
await Promise.allSettled(
|
||||
userAgents.agents.map((agent) => this.registerAgent(agent)),
|
||||
);
|
||||
|
||||
// Load project-level agents: .gemini/agents/ (relative to Project Root)
|
||||
const folderTrustEnabled = this.config.getFolderTrust();
|
||||
@@ -80,9 +86,9 @@ export class AgentRegistry {
|
||||
`Agent loading error: ${error.message}`,
|
||||
);
|
||||
}
|
||||
for (const agent of projectAgents.agents) {
|
||||
this.registerAgent(agent);
|
||||
}
|
||||
await Promise.allSettled(
|
||||
projectAgents.agents.map((agent) => this.registerAgent(agent)),
|
||||
);
|
||||
} else {
|
||||
coreEvents.emitFeedback(
|
||||
'info',
|
||||
@@ -135,20 +141,22 @@ export class AgentRegistry {
|
||||
CodebaseInvestigatorAgent.runConfig.max_turns,
|
||||
},
|
||||
};
|
||||
this.registerAgent(agentDef);
|
||||
this.registerLocalAgent(agentDef);
|
||||
}
|
||||
|
||||
// Register the introspection agent if it's explicitly enabled.
|
||||
if (introspectionSettings.enabled) {
|
||||
this.registerAgent(IntrospectionAgent);
|
||||
this.registerLocalAgent(IntrospectionAgent);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshAgents(): void {
|
||||
private async refreshAgents(): Promise<void> {
|
||||
this.loadBuiltInAgents();
|
||||
for (const agent of this.agents.values()) {
|
||||
this.registerAgent(agent);
|
||||
}
|
||||
await Promise.allSettled(
|
||||
Array.from(this.agents.values()).map((agent) =>
|
||||
this.registerAgent(agent),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,9 +164,26 @@ export class AgentRegistry {
|
||||
* it will be overwritten, respecting the precedence established by the
|
||||
* initialization order.
|
||||
*/
|
||||
protected registerAgent<TOutput extends z.ZodTypeAny>(
|
||||
protected async registerAgent<TOutput extends z.ZodTypeAny>(
|
||||
definition: AgentDefinition<TOutput>,
|
||||
): Promise<void> {
|
||||
if (definition.kind === 'local') {
|
||||
this.registerLocalAgent(definition);
|
||||
} else if (definition.kind === 'remote') {
|
||||
await this.registerRemoteAgent(definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a local agent definition synchronously.
|
||||
*/
|
||||
protected registerLocalAgent<TOutput extends z.ZodTypeAny>(
|
||||
definition: AgentDefinition<TOutput>,
|
||||
): void {
|
||||
if (definition.kind !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if (!definition.name || !definition.description) {
|
||||
debugLogger.warn(
|
||||
@@ -175,37 +200,79 @@ export class AgentRegistry {
|
||||
|
||||
// Register model config.
|
||||
// TODO(12916): Migrate sub-agents where possible to static configs.
|
||||
if (definition.kind === 'local') {
|
||||
const modelConfig = definition.modelConfig;
|
||||
let model = modelConfig.model;
|
||||
if (model === 'inherit') {
|
||||
model = this.config.getModel();
|
||||
}
|
||||
const modelConfig = definition.modelConfig;
|
||||
let model = modelConfig.model;
|
||||
if (model === 'inherit') {
|
||||
model = this.config.getModel();
|
||||
}
|
||||
|
||||
const runtimeAlias: ModelConfigAlias = {
|
||||
modelConfig: {
|
||||
model,
|
||||
generateContentConfig: {
|
||||
temperature: modelConfig.temp,
|
||||
topP: modelConfig.top_p,
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
thinkingBudget: modelConfig.thinkingBudget ?? -1,
|
||||
},
|
||||
const runtimeAlias: ModelConfigAlias = {
|
||||
modelConfig: {
|
||||
model,
|
||||
generateContentConfig: {
|
||||
temperature: modelConfig.temp,
|
||||
topP: modelConfig.top_p,
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
thinkingBudget: modelConfig.thinkingBudget ?? -1,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
this.config.modelConfigService.registerRuntimeModelConfig(
|
||||
getModelConfigAlias(definition),
|
||||
runtimeAlias,
|
||||
this.config.modelConfigService.registerRuntimeModelConfig(
|
||||
getModelConfigAlias(definition),
|
||||
runtimeAlias,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a remote agent definition asynchronously.
|
||||
*/
|
||||
protected async registerRemoteAgent<TOutput extends z.ZodTypeAny>(
|
||||
definition: AgentDefinition<TOutput>,
|
||||
): Promise<void> {
|
||||
if (definition.kind !== 'remote') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if (!definition.name || !definition.description) {
|
||||
debugLogger.warn(
|
||||
`[AgentRegistry] Skipping invalid agent definition. Missing name or description.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
|
||||
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
|
||||
}
|
||||
|
||||
// Log remote A2A agent registration for visibility.
|
||||
if (definition.kind === 'remote' && this.config.getDebugMode()) {
|
||||
debugLogger.log(
|
||||
`[AgentRegistry] Registered remote agent '${definition.name}' with card: ${definition.agentCardUrl}`,
|
||||
try {
|
||||
const clientManager = A2AClientManager.getInstance();
|
||||
const agentCard = await clientManager.loadAgent(
|
||||
definition.name,
|
||||
definition.agentCardUrl,
|
||||
);
|
||||
if (agentCard.skills && agentCard.skills.length > 0) {
|
||||
definition.description = agentCard.skills
|
||||
.map(
|
||||
(skill: { name: string; description: string }) =>
|
||||
`${skill.name}: ${skill.description}`,
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
if (this.config.getDebugMode()) {
|
||||
debugLogger.log(
|
||||
`[AgentRegistry] Registered remote agent '${definition.name}' with card: ${definition.agentCardUrl}`,
|
||||
);
|
||||
}
|
||||
this.agents.set(definition.name, definition);
|
||||
} catch (e) {
|
||||
debugLogger.warn(
|
||||
`[AgentRegistry] Error loading A2A agent "${definition.name}":`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user