| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- from __future__ import annotations
- from pathlib import Path
- import pytest
- from agents.models.openai_responses import OpenAIResponsesModel
- from agency_swarm import Agency, Agent, OpenClawAgent
- from agency_swarm.integrations.openclaw_model import (
- build_openclaw_responses_model,
- register_current_app_openclaw_defaults,
- )
- from agency_swarm.tools.send_message import Handoff
- from agency_swarm.utils.model_utils import (
- get_default_settings_model_name,
- get_model_name,
- get_usage_tracking_model_name,
- )
- from tests.integration.fastapi._openclaw_test_support import reset_openclaw_current_app_defaults
- def test_openclaw_agent_auto_builds_responses_model(monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.delenv("OPENCLAW_PROXY_BASE_URL", raising=False)
- monkeypatch.delenv("OPENCLAW_PROXY_PORT", raising=False)
- monkeypatch.delenv("PORT", raising=False)
- monkeypatch.setenv("OPENCLAW_PROVIDER_MODEL", "openai/gpt-5.4-mini")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- assert isinstance(agent.model, OpenAIResponsesModel)
- assert agent.model.model == "openclaw:main"
- assert get_model_name(agent.model) == "openclaw:main"
- assert get_usage_tracking_model_name(agent.model) == "openai/gpt-5.4-mini"
- assert str(agent.model._client.base_url) == "http://127.0.0.1:8000/openclaw/v1/"
- assert agent.supports_outbound_communication is False
- assert agent.model_settings.reasoning is not None
- assert agent.model_settings.reasoning.effort == "none"
- assert agent.model_settings.verbosity == "low"
- def test_openclaw_agent_supports_custom_host_port_and_path() -> None:
- with pytest.MonkeyPatch.context() as monkeypatch:
- monkeypatch.setenv("OPENCLAW_PROXY_BASE_URL", "http://env.example/openclaw/v1")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- host="127.0.0.1",
- port=18080,
- api_path="/worker/v1",
- )
- assert str(agent.model._client.base_url) == "http://127.0.0.1:18080/worker/v1/"
- assert agent.model.model == "openclaw:main"
- @pytest.mark.parametrize(
- ("kwargs", "expected_url"),
- [
- ({"api_path": "/worker/v1"}, "https://proxy.example/worker/v1/"),
- ({"host": "worker.example"}, "https://worker.example/openclaw/v1/"),
- ({"port": 9443}, "https://proxy.example:9443/openclaw/v1/"),
- ],
- )
- def test_openclaw_agent_preserves_env_proxy_base_url_when_partially_overridden(
- monkeypatch: pytest.MonkeyPatch,
- kwargs: dict[str, str | int],
- expected_url: str,
- ) -> None:
- monkeypatch.setenv("OPENCLAW_PROXY_BASE_URL", "https://proxy.example/openclaw/v1")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- api_key="external-token",
- **kwargs,
- )
- assert str(agent.model._client.base_url) == expected_url
- def test_openclaw_agent_defaults_external_v1_urls_to_provider_model(
- monkeypatch: pytest.MonkeyPatch,
- ) -> None:
- monkeypatch.setenv("OPENCLAW_PROVIDER_MODEL", "anthropic/claude-sonnet-4-5")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- base_url="http://127.0.0.1:18789/v1",
- api_key="external-token",
- )
- assert str(agent.model._client.base_url) == "http://127.0.0.1:18789/v1/"
- assert agent.model.model == "anthropic/claude-sonnet-4-5"
- def test_build_openclaw_responses_model_defaults_raw_gateway_to_local_runtime_token(
- monkeypatch: pytest.MonkeyPatch,
- ) -> None:
- monkeypatch.delenv("OPENCLAW_GATEWAY_TOKEN", raising=False)
- monkeypatch.delenv("OPENCLAW_PROXY_API_KEY", raising=False)
- monkeypatch.delenv("APP_TOKEN", raising=False)
- model = build_openclaw_responses_model(base_url="http://127.0.0.1:18789/v1")
- assert model._client.api_key == "openclaw-local-token"
- @pytest.mark.parametrize(
- "base_url",
- [
- "https://example.com/openclaw/v1",
- "http://127.0.0.1:18789/v1",
- ],
- )
- def test_openclaw_agent_preserves_model_alias_override_for_external_servers(base_url: str) -> None:
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- base_url=base_url,
- api_key="external-token",
- model="openclaw:custom",
- )
- assert str(agent.model._client.base_url) == f"{base_url}/"
- assert agent.model.model == "openclaw:custom"
- def test_build_openclaw_responses_model_preserves_explicit_alias_for_direct_gateway_urls() -> None:
- model = build_openclaw_responses_model(
- model="openclaw:custom",
- base_url="http://127.0.0.1:18789/v1",
- api_key="external-token",
- )
- assert model.model == "openclaw:custom"
- assert get_usage_tracking_model_name(model) == "openclaw:custom"
- assert get_default_settings_model_name(model) == "openclaw:custom"
- def test_openclaw_agent_uses_gateway_token_before_app_token_for_direct_gateway_urls(
- monkeypatch: pytest.MonkeyPatch,
- ) -> None:
- monkeypatch.setenv("OPENCLAW_PROXY_API_KEY", "proxy-token")
- monkeypatch.setenv("APP_TOKEN", "app-token")
- monkeypatch.setenv("OPENCLAW_GATEWAY_TOKEN", "gateway-token")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- base_url="http://127.0.0.1:18789/v1",
- )
- assert agent.model._client.api_key == "gateway-token"
- def test_openclaw_agent_keeps_app_token_first_for_local_proxy_urls(monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.delenv("OPENCLAW_PROXY_API_KEY", raising=False)
- monkeypatch.setenv("APP_TOKEN", "app-token")
- monkeypatch.setenv("OPENCLAW_GATEWAY_TOKEN", "gateway-token")
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- assert agent.model._client.api_key == "app-token"
- def test_openclaw_agent_treats_localhost_proxy_aliases_as_current_app_proxy(
- monkeypatch: pytest.MonkeyPatch,
- ) -> None:
- monkeypatch.delenv("OPENCLAW_PROXY_API_KEY", raising=False)
- monkeypatch.delenv("OPENCLAW_PROVIDER_MODEL", raising=False)
- monkeypatch.setenv("APP_TOKEN", "app-token")
- monkeypatch.delenv("OPENCLAW_GATEWAY_TOKEN", raising=False)
- monkeypatch.setenv("OPENCLAW_PROXY_BASE_URL", "http://127.0.0.1:8000/openclaw/v1")
- reset_openclaw_current_app_defaults(monkeypatch)
- model = build_openclaw_responses_model(base_url="http://localhost:8000/openclaw/v1")
- assert model._client.api_key == "app-token"
- assert get_usage_tracking_model_name(model) == "openai/gpt-5.4"
- def test_openclaw_agent_uses_app_token_for_explicit_same_app_proxy_url_when_one_current_app_proxy_is_registered(
- monkeypatch: pytest.MonkeyPatch,
- ) -> None:
- monkeypatch.setenv("OPENCLAW_PROXY_API_KEY", "proxy-token")
- monkeypatch.setenv("OPENCLAW_PROXY_BASE_URL", "http://127.0.0.1:9000/openclaw/v1")
- monkeypatch.setenv("APP_TOKEN", "app-token")
- monkeypatch.setenv("OPENCLAW_GATEWAY_TOKEN", "gateway-token")
- reset_openclaw_current_app_defaults(monkeypatch)
- register_current_app_openclaw_defaults(
- default_model="openclaw:custom",
- provider_model="openai/gpt-5.4-mini",
- base_url="http://127.0.0.1:9000/openclaw/v1",
- )
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- host="127.0.0.1",
- port=9000,
- )
- assert agent.model.model == "openclaw:custom"
- assert agent.model._client.api_key == "app-token"
- def test_openclaw_agent_rejects_manual_handoffs() -> None:
- recipient = Agent(
- name="Recipient",
- description="Recipient",
- instructions="Return the result.",
- model="gpt-5.4-mini",
- )
- with pytest.raises(TypeError, match="does not accept manual handoffs"):
- OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- handoffs=[Handoff().create_handoff(recipient)],
- )
- def test_openclaw_agent_rejects_framework_tool_wiring() -> None:
- with pytest.raises(TypeError, match="does not accept Agency Swarm tool wiring"):
- OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- tools=[object()],
- )
- def test_openclaw_agent_rejects_files_folder_tool_wiring() -> None:
- with pytest.raises(TypeError, match="does not accept Agency Swarm tool wiring"):
- OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- files_folder="./files",
- )
- def test_openclaw_agent_rejects_manual_communication_capability_overrides() -> None:
- with pytest.raises(TypeError, match="always receive-only"):
- OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- supports_outbound_communication=True,
- )
- def test_openclaw_agent_skips_shared_tool_wiring() -> None:
- openclaw_worker = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- coordinator = Agent(
- name="Coordinator",
- description="Coordinator",
- instructions="Coordinate the work.",
- model="gpt-5.4-mini",
- )
- agency = Agency(
- coordinator,
- communication_flows=[(coordinator, openclaw_worker)],
- shared_tools=[object()],
- )
- assert agency.agents["OpenClawWorker"].supports_framework_tool_wiring is False
- def test_openclaw_agent_skips_shared_file_preprocessing_when_no_agent_supports_it(
- monkeypatch: pytest.MonkeyPatch,
- tmp_path: Path,
- ) -> None:
- monkeypatch.delenv("OPENAI_API_KEY", raising=False)
- shared_files = tmp_path / "shared-files"
- shared_files.mkdir()
- (shared_files / "notes.txt").write_text("hello", encoding="utf-8")
- openclaw_worker = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- agency = Agency(openclaw_worker, shared_files_folder=str(shared_files))
- assert agency.agents["OpenClawWorker"].supports_framework_tool_wiring is False
- def test_openclaw_agent_cannot_register_subagent() -> None:
- agent = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- recipient = Agent(
- name="Recipient",
- description="Recipient",
- instructions="Return the result.",
- model="gpt-5.4-mini",
- )
- with pytest.raises(ValueError, match="cannot register subagents because it is configured as receive-only"):
- agent.register_subagent(recipient)
- def test_openclaw_agent_cannot_be_sender_in_communication_flows() -> None:
- openclaw_worker = OpenClawAgent(
- name="OpenClawWorker",
- description="Worker",
- instructions="Handle OpenClaw work.",
- )
- specialist = Agent(
- name="Specialist",
- description="Specialist",
- instructions="Return the result.",
- model="gpt-5.4-mini",
- )
- with pytest.raises(ValueError, match="cannot be the sender in communication_flows"):
- Agency(openclaw_worker, communication_flows=[(openclaw_worker, specialist)])
|