mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 09:01:17 -07:00
@@ -417,7 +417,7 @@ export function markdownToAgentDefinition(
|
||||
return {
|
||||
kind: 'remote',
|
||||
name: markdown.name,
|
||||
description: markdown.description || '(Loading description...)',
|
||||
description: markdown.description || '',
|
||||
displayName: markdown.display_name,
|
||||
agentCardUrl: markdown.agent_card_url,
|
||||
auth: markdown.auth
|
||||
|
||||
@@ -572,6 +572,181 @@ describe('AgentRegistry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge user and agent description and skills when registering a remote agent', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'RemoteAgentWithDescription',
|
||||
description: 'User-provided description',
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
const mockAgentCard = {
|
||||
name: 'RemoteAgentWithDescription',
|
||||
description: 'Card-provided description',
|
||||
skills: [
|
||||
{ name: 'Skill1', description: 'Desc1' },
|
||||
{ name: 'Skill2', description: 'Desc2' },
|
||||
],
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue(mockAgentCard),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
const registered = registry.getDefinition('RemoteAgentWithDescription');
|
||||
expect(registered?.description).toBe(
|
||||
'User Description: User-provided description\nAgent Description: Card-provided description\nSkills:\nSkill1: Desc1\nSkill2: Desc2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should include skills when agent description is empty', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'RemoteAgentWithSkillsOnly',
|
||||
description: 'User-provided description',
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
const mockAgentCard = {
|
||||
name: 'RemoteAgentWithSkillsOnly',
|
||||
description: '',
|
||||
skills: [{ name: 'Skill1', description: 'Desc1' }],
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue(mockAgentCard),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
const registered = registry.getDefinition('RemoteAgentWithSkillsOnly');
|
||||
expect(registered?.description).toBe(
|
||||
'User Description: User-provided description\nSkills:\nSkill1: Desc1',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty user or agent descriptions and no skills during merging', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'RemoteAgentWithEmptyAgentDescription',
|
||||
description: 'User-provided description',
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
const mockAgentCard = {
|
||||
name: 'RemoteAgentWithEmptyAgentDescription',
|
||||
description: '', // Empty agent description
|
||||
skills: [],
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue(mockAgentCard),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
const registered = registry.getDefinition(
|
||||
'RemoteAgentWithEmptyAgentDescription',
|
||||
);
|
||||
// Should only contain user description
|
||||
expect(registered?.description).toBe(
|
||||
'User Description: User-provided description',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not accumulate descriptions on repeated registration', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'RemoteAgentAccumulationTest',
|
||||
description: 'User-provided description',
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
const mockAgentCard = {
|
||||
name: 'RemoteAgentAccumulationTest',
|
||||
description: 'Card-provided description',
|
||||
skills: [{ name: 'Skill1', description: 'Desc1' }],
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue(mockAgentCard),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
// Register first time
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
let registered = registry.getDefinition('RemoteAgentAccumulationTest');
|
||||
const firstDescription = registered?.description;
|
||||
expect(firstDescription).toBe(
|
||||
'User Description: User-provided description\nAgent Description: Card-provided description\nSkills:\nSkill1: Desc1',
|
||||
);
|
||||
|
||||
// Register second time with the SAME object
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
registered = registry.getDefinition('RemoteAgentAccumulationTest');
|
||||
expect(registered?.description).toBe(firstDescription);
|
||||
});
|
||||
|
||||
it('should allow registering a remote agent with an empty initial description', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'EmptyDescAgent',
|
||||
description: '', // Empty initial description
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue({
|
||||
name: 'EmptyDescAgent',
|
||||
description: 'Loaded from card',
|
||||
}),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
const registered = registry.getDefinition('EmptyDescAgent');
|
||||
expect(registered?.description).toBe(
|
||||
'Agent Description: Loaded from card',
|
||||
);
|
||||
});
|
||||
|
||||
it('should provide fallback for skill descriptions if missing in the card', async () => {
|
||||
const remoteAgent: AgentDefinition = {
|
||||
kind: 'remote',
|
||||
name: 'SkillFallbackAgent',
|
||||
description: 'User description',
|
||||
agentCardUrl: 'https://example.com/card',
|
||||
inputConfig: { inputSchema: { type: 'object' } },
|
||||
};
|
||||
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
loadAgent: vi.fn().mockResolvedValue({
|
||||
name: 'SkillFallbackAgent',
|
||||
description: 'Card description',
|
||||
skills: [{ name: 'SkillNoDesc' }], // Missing description
|
||||
}),
|
||||
clearCache: vi.fn(),
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
await registry.testRegisterAgent(remoteAgent);
|
||||
|
||||
const registered = registry.getDefinition('SkillFallbackAgent');
|
||||
expect(registered?.description).toContain(
|
||||
'SkillNoDesc: No description provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle special characters in agent names', async () => {
|
||||
const specialAgent = {
|
||||
...MOCK_AGENT_V1,
|
||||
|
||||
@@ -335,9 +335,10 @@ export class AgentRegistry {
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if (!definition.name || !definition.description) {
|
||||
// Remote agents can have an empty description initially as it will be populated from the AgentCard
|
||||
if (!definition.name) {
|
||||
debugLogger.warn(
|
||||
`[AgentRegistry] Skipping invalid agent definition. Missing name or description.`,
|
||||
`[AgentRegistry] Skipping invalid agent definition. Missing name.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -360,24 +361,48 @@ export class AgentRegistry {
|
||||
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
|
||||
}
|
||||
|
||||
const remoteDef = definition;
|
||||
|
||||
// Capture the original description from the first registration
|
||||
if (remoteDef.originalDescription === undefined) {
|
||||
remoteDef.originalDescription = remoteDef.description;
|
||||
}
|
||||
|
||||
// Log remote A2A agent registration for visibility.
|
||||
try {
|
||||
const clientManager = A2AClientManager.getInstance();
|
||||
// Use ADCHandler to ensure we can load agents hosted on secure platforms (e.g. Vertex AI)
|
||||
const authHandler = new ADCHandler();
|
||||
const agentCard = await clientManager.loadAgent(
|
||||
definition.name,
|
||||
definition.agentCardUrl,
|
||||
remoteDef.name,
|
||||
remoteDef.agentCardUrl,
|
||||
authHandler,
|
||||
);
|
||||
|
||||
const userDescription = remoteDef.originalDescription;
|
||||
const agentDescription = agentCard.description;
|
||||
const descriptions: string[] = [];
|
||||
|
||||
if (userDescription?.trim()) {
|
||||
descriptions.push(`User Description: ${userDescription.trim()}`);
|
||||
}
|
||||
if (agentDescription?.trim()) {
|
||||
descriptions.push(`Agent Description: ${agentDescription.trim()}`);
|
||||
}
|
||||
if (agentCard.skills && agentCard.skills.length > 0) {
|
||||
definition.description = agentCard.skills
|
||||
const skillsList = agentCard.skills
|
||||
.map(
|
||||
(skill: { name: string; description: string }) =>
|
||||
`${skill.name}: ${skill.description}`,
|
||||
`${skill.name}: ${skill.description || 'No description provided'}`,
|
||||
)
|
||||
.join('\n');
|
||||
descriptions.push(`Skills:\n${skillsList}`);
|
||||
}
|
||||
|
||||
if (descriptions.length > 0) {
|
||||
definition.description = descriptions.join('\n');
|
||||
}
|
||||
|
||||
if (this.config.getDebugMode()) {
|
||||
debugLogger.log(
|
||||
`[AgentRegistry] Registered remote agent '${definition.name}' with card: ${definition.agentCardUrl}`,
|
||||
|
||||
@@ -119,6 +119,8 @@ export interface RemoteAgentDefinition<
|
||||
> extends BaseAgentDefinition<TOutput> {
|
||||
kind: 'remote';
|
||||
agentCardUrl: string;
|
||||
/** The user-provided description, before any remote card merging. */
|
||||
originalDescription?: string;
|
||||
/**
|
||||
* Optional authentication configuration for the remote agent.
|
||||
* If not specified, the agent will try to use defaults based on the AgentCard's
|
||||
|
||||
Reference in New Issue
Block a user