Ctrl K
ComunidadGuías…

test/acp/test_config.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from ag2.acp import ACPConfig, ClaudeCodeConfig, CodexConfig, KiloCodeConfig, OpenCodeConfig
from ag2.config.client import LLMClient


class TestClaudeCodeConfig:
    def test_defaults(self) -> None:
        cfg = ClaudeCodeConfig()
        assert cfg.command  # non-empty launch command
        assert cfg.permission_policy == "ask"
        assert cfg.cwd == "."
        assert cfg.allow_terminal is True

    def test_copy_preserves_subclass(self) -> None:
        cfg = ClaudeCodeConfig(cwd="/a")
        cfg2 = cfg.copy(cwd="/b")
        assert cfg.cwd == "/a"
        assert cfg2.cwd == "/b"
        assert isinstance(cfg2, ClaudeCodeConfig)

    def test_create_returns_llmclient(self) -> None:
        client = ClaudeCodeConfig().create()
        assert isinstance(client, LLMClient)


class TestCodexConfig:
    def test_defaults(self) -> None:
        cfg = CodexConfig()
        assert cfg.command == ["codex-acp"]
        assert cfg.permission_policy == "ask"
        assert cfg.cwd == "."
        assert cfg.allow_terminal is True

    def test_copy_preserves_subclass(self) -> None:
        cfg = CodexConfig(cwd="/a")
        cfg2 = cfg.copy(cwd="/b")
        assert cfg.cwd == "/a"
        assert cfg2.cwd == "/b"
        assert isinstance(cfg2, CodexConfig)

    def test_create_returns_llmclient(self) -> None:
        client = CodexConfig().create()
        assert isinstance(client, LLMClient)


class TestOpenCodeConfig:
    def test_defaults(self) -> None:
        cfg = OpenCodeConfig()
        assert cfg.command == ["opencode", "acp"]
        assert cfg.permission_policy == "ask"
        assert cfg.cwd == "."
        assert cfg.allow_terminal is True

    def test_copy_preserves_subclass(self) -> None:
        cfg = OpenCodeConfig(cwd="/a")
        cfg2 = cfg.copy(cwd="/b")
        assert cfg.cwd == "/a"
        assert cfg2.cwd == "/b"
        assert isinstance(cfg2, OpenCodeConfig)

    def test_create_returns_llmclient(self) -> None:
        client = OpenCodeConfig().create()
        assert isinstance(client, LLMClient)

    def test_model_does_not_alter_command(self) -> None:
        # `model` is applied via ACP session/set_config_option after the handshake,
        # never via CLI flags — the launch command must stay untouched.
        cfg = OpenCodeConfig(model="anthropic/claude-sonnet-5")
        assert cfg.command == ["opencode", "acp"]


class TestKiloCodeConfig:
    def test_defaults(self) -> None:
        cfg = KiloCodeConfig()
        assert cfg.command == ["kilo", "acp"]
        assert cfg.permission_policy == "ask"
        assert cfg.cwd == "."
        assert cfg.allow_terminal is True

    def test_copy_preserves_subclass(self) -> None:
        cfg = KiloCodeConfig(cwd="/a")
        cfg2 = cfg.copy(cwd="/b")
        assert cfg.cwd == "/a"
        assert cfg2.cwd == "/b"
        assert isinstance(cfg2, KiloCodeConfig)

    def test_create_returns_llmclient(self) -> None:
        client = KiloCodeConfig().create()
        assert isinstance(client, LLMClient)

    def test_model_does_not_alter_command(self) -> None:
        # `model` is applied via ACP session/set_config_option after the handshake,
        # never via CLI flags — the launch command must stay untouched.
        cfg = KiloCodeConfig(model="kilo/anthropic/claude-haiku-4.5")
        assert cfg.command == ["kilo", "acp"]


def test_acp_config_is_usable_directly() -> None:
    cfg = ACPConfig(command=["my-agent", "--acp"], permission_policy="auto")
    assert cfg.command == ["my-agent", "--acp"]
    assert cfg.permission_policy == "auto"


def test_the_published_parameter_order_still_holds() -> None:
    # The parameter order is API: a caller who passed these positionally wrote
    # against it, so growing the config — a shared base for the remote one, a new
    # policy — must leave every existing position meaning what it meant. A new
    # field therefore goes after ``expose_tools``, never between.
    cfg = ACPConfig(["my-agent", "--acp"], "/work", {"KEY": "v"}, "sonnet", "auto", "/root", False, ["/extra"])

    assert cfg == ACPConfig(
        command=["my-agent", "--acp"],
        cwd="/work",
        env={"KEY": "v"},
        model="sonnet",
        permission_policy="auto",
        fs_root="/root",
        allow_terminal=False,
        additional_directories=["/extra"],
    )


def test_presets_take_a_command_positionally_too() -> None:
    # A preset is an ACPConfig whose command has a default, and overriding that
    # default positionally is how the docs' `npx -y ...` variants are written.
    assert ClaudeCodeConfig(["npx", "-y", "@agentclientprotocol/claude-agent-acp"]).command[0] == "npx"
    assert CodexConfig(["npx", "-y", "@agentclientprotocol/codex-acp"], "/work").cwd == "/work"
    assert OpenCodeConfig(["opencode", "acp"]).cwd == "."
    assert KiloCodeConfig(["npx", "-y", "@kilocode/cli", "acp"]).command[-1] == "acp"


def test_elicitation_policy_defaults_to_ask_and_survives_copy() -> None:
    # Same default as the permission policy: asking is what a caller gets for free.
    assert ACPConfig().elicitation_policy == "ask"
    assert ClaudeCodeConfig().elicitation_policy == "ask"

    unattended = ACPConfig().copy(elicitation_policy="decline")
    assert unattended.elicitation_policy == "decline"
    assert ACPConfig().elicitation_policy == "ask"  # original untouched


def test_expose_tools_defaults_on_and_survives_copy() -> None:
    cfg = ACPConfig()
    assert cfg.expose_tools is True

    off = cfg.copy(expose_tools=False)
    assert off.expose_tools is False
    assert cfg.expose_tools is True  # original untouched