/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
import { parseAgentMarkdown, markdownToAgentDefinition, loadAgentsFromDirectory, AgentLoadError, } from './agentLoader.js';
import { GEMINI_MODEL_ALIAS_PRO } from '../config/models.js';
import { DEFAULT_MAX_TIME_MINUTES, DEFAULT_MAX_TURNS, } from './types.js';
describe('loader', () => {
    let tempDir;
    beforeEach(async () => {
        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agent-test-'));
    });
    afterEach(async () => {
        if (tempDir) {
            await fs.rm(tempDir, { recursive: true, force: true });
        }
    });
    async function writeAgentMarkdown(content, fileName = 'test.md') {
        const filePath = path.join(tempDir, fileName);
        await fs.writeFile(filePath, content);
        return filePath;
    }
    describe('parseAgentMarkdown', () => {
        it('should parse a valid markdown agent file', async () => {
            const filePath = await writeAgentMarkdown(`---
name: test-agent-md
description: A markdown agent
---
You are a markdown agent.`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                name: 'test-agent-md',
                description: 'A markdown agent',
                kind: 'local',
                system_prompt: 'You are a markdown agent.',
            });
        });
        it('should parse frontmatter with tools and model config', async () => {
            const filePath = await writeAgentMarkdown(`---
name: complex-agent
description: A complex markdown agent
tools:
  - run_shell_command
model: gemini-pro
temperature: 0.7
---
System prompt content.`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                name: 'complex-agent',
                description: 'A complex markdown agent',
                tools: ['run_shell_command'],
                model: 'gemini-pro',
                temperature: 0.7,
                system_prompt: 'System prompt content.',
            });
        });
        it('should parse frontmatter with mcp_servers', async () => {
            const filePath = await writeAgentMarkdown(`---
name: mcp-agent
description: An agent with MCP servers
mcp_servers:
  test-server:
    command: node
    args: [server.js]
    include_tools: [tool1, tool2]
---
System prompt content.`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                name: 'mcp-agent',
                description: 'An agent with MCP servers',
                mcp_servers: {
                    'test-server': {
                        command: 'node',
                        args: ['server.js'],
                        include_tools: ['tool1', 'tool2'],
                    },
                },
            });
        });
        it('should throw AgentLoadError if frontmatter is missing', async () => {
            const filePath = await writeAgentMarkdown(`Just some markdown content.`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(AgentLoadError);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow('Missing mandatory YAML frontmatter');
        });
        it('should throw AgentLoadError if frontmatter is invalid YAML', async () => {
            const filePath = await writeAgentMarkdown(`---
name: [invalid yaml
---
Body`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(AgentLoadError);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow('YAML frontmatter parsing failed');
        });
        it('should throw AgentLoadError if validation fails (missing required field)', async () => {
            const filePath = await writeAgentMarkdown(`---
name: test-agent
# missing description
---
Body`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Validation failed/);
        });
        it('should parse a valid remote agent markdown file', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: remote-agent
description: A remote agent
agent_card_url: https://example.com/card
---
`);
            const result = await parseAgentMarkdown(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 writeAgentMarkdown(`---
name: inferred-remote
description: Inferred
agent_card_url: https://example.com/inferred
---
`);
            const result = await parseAgentMarkdown(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 with no body', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: no-body-remote
agent_card_url: https://example.com/card
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toEqual({
                kind: 'remote',
                name: 'no-body-remote',
                agent_card_url: 'https://example.com/card',
            });
        });
        it('should parse multiple remote agents in a list', async () => {
            const filePath = await writeAgentMarkdown(`---
- kind: remote
  name: remote-1
  agent_card_url: https://example.com/1
- kind: remote
  name: remote-2
  agent_card_url: https://example.com/2
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(2);
            expect(result[0]).toEqual({
                kind: 'remote',
                name: 'remote-1',
                agent_card_url: 'https://example.com/1',
            });
            expect(result[1]).toEqual({
                kind: 'remote',
                name: 'remote-2',
                agent_card_url: 'https://example.com/2',
            });
        });
        it('should parse frontmatter without a trailing newline', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: no-trailing-newline
agent_card_url: https://example.com/card
---`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toEqual({
                kind: 'remote',
                name: 'no-trailing-newline',
                agent_card_url: 'https://example.com/card',
            });
        });
        it('should throw AgentLoadError if agent name is not a valid slug', async () => {
            const filePath = await writeAgentMarkdown(`---
name: Invalid Name With Spaces
description: Test
---
Body`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Name must be a valid slug/);
        });
    });
    describe('markdownToAgentDefinition', () => {
        it('should convert valid Markdown DTO to AgentDefinition with defaults', () => {
            const markdown = {
                kind: 'local',
                name: 'test-agent',
                description: 'A test agent',
                system_prompt: 'You are a test agent.',
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result).toMatchObject({
                name: 'test-agent',
                description: 'A test agent',
                promptConfig: {
                    systemPrompt: 'You are a test agent.',
                    query: '${query}',
                },
                modelConfig: {
                    model: 'inherit',
                    generateContentConfig: {
                        topP: 0.95,
                    },
                },
                runConfig: {
                    maxTimeMinutes: DEFAULT_MAX_TIME_MINUTES,
                    maxTurns: DEFAULT_MAX_TURNS,
                },
                inputConfig: {
                    inputSchema: {
                        type: 'object',
                        properties: {
                            query: {
                                type: 'string',
                                description: 'The task for the agent.',
                            },
                        },
                        required: [],
                    },
                },
            });
        });
        it('should pass through model aliases', () => {
            const markdown = {
                kind: 'local',
                name: 'test-agent',
                description: 'A test agent',
                model: GEMINI_MODEL_ALIAS_PRO,
                system_prompt: 'You are a test agent.',
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result.modelConfig.model).toBe(GEMINI_MODEL_ALIAS_PRO);
        });
        it('should convert mcp_servers in local agent', () => {
            const markdown = {
                kind: 'local',
                name: 'mcp-agent',
                description: 'An agent with MCP servers',
                mcp_servers: {
                    'test-server': {
                        command: 'node',
                        args: ['server.js'],
                        include_tools: ['tool1'],
                    },
                },
                system_prompt: 'prompt',
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result.kind).toBe('local');
            expect(result.mcpServers).toBeDefined();
            expect(result.mcpServers['test-server']).toMatchObject({
                command: 'node',
                args: ['server.js'],
                includeTools: ['tool1'],
            });
        });
        it('should pass through unknown model names (e.g. auto)', () => {
            const markdown = {
                kind: 'local',
                name: 'test-agent',
                description: 'A test agent',
                model: 'auto',
                system_prompt: 'You are a test agent.',
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result.modelConfig.model).toBe('auto');
        });
        it('should convert remote agent definition', () => {
            const markdown = {
                kind: 'remote',
                name: 'remote-agent',
                description: 'A remote agent',
                agent_card_url: 'https://example.com/card',
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result).toEqual({
                kind: 'remote',
                name: 'remote-agent',
                description: 'A remote agent',
                displayName: undefined,
                agentCardUrl: 'https://example.com/card',
                inputConfig: {
                    inputSchema: {
                        type: 'object',
                        properties: {
                            query: {
                                type: 'string',
                                description: 'The task for the agent.',
                            },
                        },
                        required: [],
                    },
                },
            });
        });
    });
    describe('loadAgentsFromDirectory', () => {
        it('should load definitions from a directory (Markdown only)', async () => {
            await writeAgentMarkdown(`---
name: agent-1
description: Agent 1
---
Prompt 1`, 'valid.md');
            // Create a non-supported file
            await fs.writeFile(path.join(tempDir, 'other.txt'), 'content');
            // Create a hidden file
            await writeAgentMarkdown(`---
name: hidden
description: Hidden
---
Hidden`, '_hidden.md');
            const result = await loadAgentsFromDirectory(tempDir);
            expect(result.agents).toHaveLength(1);
            expect(result.agents[0].name).toBe('agent-1');
            expect(result.errors).toHaveLength(0);
        });
        it('should return empty result if directory does not exist', async () => {
            const nonExistentDir = path.join(tempDir, 'does-not-exist');
            const result = await loadAgentsFromDirectory(nonExistentDir);
            expect(result.agents).toHaveLength(0);
            expect(result.errors).toHaveLength(0);
        });
        it('should capture errors for malformed individual files', async () => {
            // Create a malformed Markdown file
            await writeAgentMarkdown('invalid markdown', 'malformed.md');
            const result = await loadAgentsFromDirectory(tempDir);
            expect(result.agents).toHaveLength(0);
            expect(result.errors).toHaveLength(1);
        });
    });
    describe('remote agent auth configuration', () => {
        it('should parse remote agent with apiKey auth', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: api-key-agent
agent_card_url: https://example.com/card
auth:
  type: apiKey
  key: $MY_API_KEY
  name: X-Custom-Key
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'api-key-agent',
                auth: {
                    type: 'apiKey',
                    key: '$MY_API_KEY',
                    name: 'X-Custom-Key',
                },
            });
        });
        it('should parse remote agent with http Bearer auth', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: bearer-agent
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: Bearer
  token: $BEARER_TOKEN
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'bearer-agent',
                auth: {
                    type: 'http',
                    scheme: 'Bearer',
                    token: '$BEARER_TOKEN',
                },
            });
        });
        it('should parse remote agent with http Basic auth', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: basic-agent
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: Basic
  username: $AUTH_USER
  password: $AUTH_PASS
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'basic-agent',
                auth: {
                    type: 'http',
                    scheme: 'Basic',
                    username: '$AUTH_USER',
                    password: '$AUTH_PASS',
                },
            });
        });
        it('should parse remote agent with Digest via raw value', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: digest-agent
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: Digest
  value: username="admin", response="abc123"
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'digest-agent',
                auth: {
                    type: 'http',
                    scheme: 'Digest',
                    value: 'username="admin", response="abc123"',
                },
            });
        });
        it('should parse remote agent with generic raw auth value', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: raw-agent
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: CustomScheme
  value: raw-token-value
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'raw-agent',
                auth: {
                    type: 'http',
                    scheme: 'CustomScheme',
                    value: 'raw-token-value',
                },
            });
        });
        it('should throw error for Bearer auth without token', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: invalid-bearer
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: Bearer
---
`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Bearer scheme requires "token"/);
        });
        it('should throw error for Basic auth without credentials', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: invalid-basic
agent_card_url: https://example.com/card
auth:
  type: http
  scheme: Basic
  username: user
---
`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Basic authentication requires "password"/);
        });
        it('should throw error for apiKey auth without key', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: invalid-apikey
agent_card_url: https://example.com/card
auth:
  type: apiKey
---
`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/auth\.key.*Required/);
        });
        it('should convert auth config in markdownToAgentDefinition', () => {
            const markdown = {
                kind: 'remote',
                name: 'auth-agent',
                agent_card_url: 'https://example.com/card',
                auth: {
                    type: 'apiKey',
                    key: '$API_KEY',
                },
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result).toMatchObject({
                kind: 'remote',
                name: 'auth-agent',
                auth: {
                    type: 'apiKey',
                    key: '$API_KEY',
                },
            });
        });
        it('should parse remote agent with oauth2 auth', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: oauth2-agent
agent_card_url: https://example.com/card
auth:
  type: oauth2
  client_id: $MY_OAUTH_CLIENT_ID
  scopes:
    - read
    - write
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'oauth2-agent',
                auth: {
                    type: 'oauth2',
                    client_id: '$MY_OAUTH_CLIENT_ID',
                    scopes: ['read', 'write'],
                },
            });
        });
        it('should parse remote agent with oauth2 auth including all fields', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: oauth2-full-agent
agent_card_url: https://example.com/card
auth:
  type: oauth2
  client_id: my-client-id
  client_secret: my-client-secret
  scopes:
    - openid
    - profile
  authorization_url: https://auth.example.com/authorize
  token_url: https://auth.example.com/token
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'oauth2-full-agent',
                auth: {
                    type: 'oauth2',
                    client_id: 'my-client-id',
                    client_secret: 'my-client-secret',
                    scopes: ['openid', 'profile'],
                    authorization_url: 'https://auth.example.com/authorize',
                    token_url: 'https://auth.example.com/token',
                },
            });
        });
        it('should parse remote agent with minimal oauth2 config (type only)', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: oauth2-minimal-agent
agent_card_url: https://example.com/card
auth:
  type: oauth2
---
`);
            const result = await parseAgentMarkdown(filePath);
            expect(result).toHaveLength(1);
            expect(result[0]).toMatchObject({
                kind: 'remote',
                name: 'oauth2-minimal-agent',
                auth: {
                    type: 'oauth2',
                },
            });
        });
        it('should reject oauth2 auth with invalid authorization_url', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: invalid-oauth2-agent
agent_card_url: https://example.com/card
auth:
  type: oauth2
  client_id: my-client
  authorization_url: not-a-valid-url
---
`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Invalid url/);
        });
        it('should reject oauth2 auth with invalid token_url', async () => {
            const filePath = await writeAgentMarkdown(`---
kind: remote
name: invalid-oauth2-agent
agent_card_url: https://example.com/card
auth:
  type: oauth2
  client_id: my-client
  token_url: not-a-valid-url
---
`);
            await expect(parseAgentMarkdown(filePath)).rejects.toThrow(/Invalid url/);
        });
        it('should convert oauth2 auth config in markdownToAgentDefinition', () => {
            const markdown = {
                kind: 'remote',
                name: 'oauth2-convert-agent',
                agent_card_url: 'https://example.com/card',
                auth: {
                    type: 'oauth2',
                    client_id: '$MY_CLIENT_ID',
                    scopes: ['read'],
                    authorization_url: 'https://auth.example.com/authorize',
                    token_url: 'https://auth.example.com/token',
                },
            };
            const result = markdownToAgentDefinition(markdown);
            expect(result).toMatchObject({
                kind: 'remote',
                name: 'oauth2-convert-agent',
                auth: {
                    type: 'oauth2',
                    client_id: '$MY_CLIENT_ID',
                    scopes: ['read'],
                    authorization_url: 'https://auth.example.com/authorize',
                    token_url: 'https://auth.example.com/token',
                },
            });
        });
    });
});
//# sourceMappingURL=agentLoader.test.js.map