Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions packages/core/src/agents/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,40 @@ describe('AgentRegistry', () => {
});
});

it('should register a remote agent definition', () => {
const remoteAgent: AgentDefinition = {
kind: 'remote',
name: 'RemoteAgent',
description: 'A remote agent',
agentCardUrl: 'https://example.com/card',
inputConfig: { inputs: {} },
};
registry.testRegisterAgent(remoteAgent);
expect(registry.getDefinition('RemoteAgent')).toEqual(remoteAgent);
});

it('should log remote agent registration in debug mode', () => {
const debugConfig = makeFakeConfig({ debugMode: true });
const debugRegistry = new TestableAgentRegistry(debugConfig);
const debugLogSpy = vi
.spyOn(debugLogger, 'log')
.mockImplementation(() => {});

const remoteAgent: AgentDefinition = {
kind: 'remote',
name: 'RemoteAgent',
description: 'A remote agent',
agentCardUrl: 'https://example.com/card',
inputConfig: { inputs: {} },
};

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', () => {
const specialAgent = {
...MOCK_AGENT_V1,
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/agents/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,12 @@ export class AgentRegistry {
);
}

// Register configured remote A2A agents.
// TODO: Implement remote agent registration.
// 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}`,
);
}
}

/**
Expand Down
141 changes: 139 additions & 2 deletions packages/core/src/agents/toml-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ describe('toml-loader', () => {
`);

const result = await parseAgentToml(filePath);
expect(result).toEqual({
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
name: 'test-agent',
description: 'A test agent',
prompts: {
Expand All @@ -55,6 +56,110 @@ describe('toml-loader', () => {
});
});

it('should parse a valid remote agent TOML file', async () => {
const filePath = await writeAgentToml(`
kind = "remote"
name = "remote-agent"
description = "A remote agent"
agent_card_url = "https://example.com/card"
`);

const result = await parseAgentToml(filePath);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
kind: 'remote',
name: 'remote-agent',
description: 'A remote agent',
agent_card_url: 'https://example.com/card',
});
});

it('should infer remote agent kind from agent_card_url', async () => {
const filePath = await writeAgentToml(`
name = "inferred-remote"
description = "Inferred"
agent_card_url = "https://example.com/inferred"
`);

const result = await parseAgentToml(filePath);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
kind: 'remote',
name: 'inferred-remote',
description: 'Inferred',
agent_card_url: 'https://example.com/inferred',
});
});

it('should parse a remote agent without description', async () => {
const filePath = await writeAgentToml(`
kind = "remote"
name = "no-description-remote"
agent_card_url = "https://example.com/card"
`);

const result = await parseAgentToml(filePath);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
kind: 'remote',
name: 'no-description-remote',
agent_card_url: 'https://example.com/card',
});
expect(result[0].description).toBeUndefined();

// defined after conversion to AgentDefinition
const agentDef = tomlToAgentDefinition(result[0]);
expect(agentDef.description).toBe('(Loading description...)');
});

it('should parse multiple agents in one file', async () => {
const filePath = await writeAgentToml(`
[[remote_agents]]
kind = "remote"
name = "agent-1"
description = "Remote 1"
agent_card_url = "https://example.com/1"

[[remote_agents]]
kind = "remote"
name = "agent-2"
description = "Remote 2"
agent_card_url = "https://example.com/2"
`);

const result = await parseAgentToml(filePath);
expect(result).toHaveLength(2);
expect(result[0].name).toBe('agent-1');
expect(result[0].kind).toBe('remote');
expect(result[1].name).toBe('agent-2');
expect(result[1].kind).toBe('remote');
});

it('should allow omitting kind in remote_agents block', async () => {
const filePath = await writeAgentToml(`
[[remote_agents]]
name = "implicit-remote-1"
agent_card_url = "https://example.com/1"

[[remote_agents]]
name = "implicit-remote-2"
agent_card_url = "https://example.com/2"
`);

const result = await parseAgentToml(filePath);
expect(result).toHaveLength(2);
expect(result[0]).toMatchObject({
kind: 'remote',
name: 'implicit-remote-1',
agent_card_url: 'https://example.com/1',
});
expect(result[1]).toMatchObject({
kind: 'remote',
name: 'implicit-remote-2',
agent_card_url: 'https://example.com/2',
});
});

it('should throw AgentLoadError if file reading fails', async () => {
const filePath = path.join(tempDir, 'non-existent.toml');
await expect(parseAgentToml(filePath)).rejects.toThrow(AgentLoadError);
Expand Down Expand Up @@ -112,14 +217,44 @@ describe('toml-loader', () => {
system_prompt = "You are a test agent."
`);
await expect(parseAgentToml(filePath)).rejects.toThrow(
/Validation failed: tools.0: Invalid tool name/,
/Validation failed:[\s\S]*tools.0: Invalid tool name/,
);
});

it('should throw AgentLoadError if file contains both single and multiple agents', async () => {
const filePath = await writeAgentToml(`
name = "top-level-agent"
description = "I should not be here"
[prompts]
system_prompt = "..."

[[remote_agents]]
kind = "remote"
name = "array-agent"
description = "I am in an array"
agent_card_url = "https://example.com/card"
`);

await expect(parseAgentToml(filePath)).rejects.toThrow(
/Validation failed/,
);
});

it('should show both options in error message when validation fails ambiguously', async () => {
const filePath = await writeAgentToml(`
name = "ambiguous-agent"
description = "I have neither prompts nor card"
`);
await expect(parseAgentToml(filePath)).rejects.toThrow(
/Validation failed: Agent Definition:\n\(Local Agent\) prompts: Required\n\(Remote Agent\) agent_card_url: Required/,
);
});
});

describe('tomlToAgentDefinition', () => {
it('should convert valid TOML to AgentDefinition with defaults', () => {
const toml = {
kind: 'local' as const,
name: 'test-agent',
description: 'A test agent',
prompts: {
Expand Down Expand Up @@ -154,6 +289,7 @@ describe('toml-loader', () => {

it('should pass through model aliases', () => {
const toml = {
kind: 'local' as const,
name: 'test-agent',
description: 'A test agent',
model: {
Expand All @@ -170,6 +306,7 @@ describe('toml-loader', () => {

it('should pass through unknown model names (e.g. auto)', () => {
const toml = {
kind: 'local' as const,
name: 'test-agent',
description: 'A test agent',
model: {
Expand Down
Loading