Files
LangBot/tests/unit_tests/agent/test_id.py
huanghuoguoguo 5aaa422250 feat(agent-runner): integrate AgentRunner Protocol v1 with plugin system
Phase 0 integration complete - verified minimal loop with local-agent stub runner.

Changes:
- Add AgentRunOrchestrator for plugin-based agent execution
- Add AgentResultNormalizer for Protocol v1 result conversion
- Add AgentRunnerDescriptor for runner ID parsing (plugin:author/name/runner)
- Update chat handler to use new orchestrator instead of direct runner lookup
- Add plugin handler methods for list_agent_runners and run_agent
- Add connector methods for AgentRunner protocol forwarding
- Update pipeline API to include runner options in metadata
- Add integration docs and implementation plan

Integration verified:
- Runner: plugin:langbot/local-agent/default
- Input: "你好"
- Output: [stub] Echo: 你好
- Date: 2026-05-10 10:09

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 11:05:27 +08:00

137 lines
4.3 KiB
Python

"""Tests for agent runner ID parsing and formatting."""
from __future__ import annotations
import pytest
from langbot.pkg.agent.runner.id import (
parse_runner_id,
format_runner_id,
RunnerIdParts,
is_plugin_runner_id,
)
class TestRunnerIdParsing:
"""Tests for parse_runner_id."""
def test_parse_plugin_runner_id(self):
"""Parse valid plugin runner ID."""
runner_id = 'plugin:langbot/local-agent/default'
parts = parse_runner_id(runner_id)
assert parts.source == 'plugin'
assert parts.plugin_author == 'langbot'
assert parts.plugin_name == 'local-agent'
assert parts.runner_name == 'default'
def test_parse_plugin_runner_id_complex_names(self):
"""Parse plugin runner ID with complex names."""
runner_id = 'plugin:alice/helpdesk-agent/ticket-handler'
parts = parse_runner_id(runner_id)
assert parts.source == 'plugin'
assert parts.plugin_author == 'alice'
assert parts.plugin_name == 'helpdesk-agent'
assert parts.runner_name == 'ticket-handler'
def test_parse_invalid_plugin_runner_id_missing_parts(self):
"""Parse invalid plugin runner ID with missing parts."""
runner_id = 'plugin:langbot/local-agent'
with pytest.raises(ValueError) as exc_info:
parse_runner_id(runner_id)
assert 'Invalid plugin runner ID format' in str(exc_info.value)
def test_parse_invalid_plugin_runner_id_empty_parts(self):
"""Parse invalid plugin runner ID with empty parts."""
runner_id = 'plugin://default'
with pytest.raises(ValueError) as exc_info:
parse_runner_id(runner_id)
assert 'non-empty' in str(exc_info.value)
def test_parse_invalid_runner_id_not_plugin(self):
"""Parse invalid runner ID without plugin prefix."""
runner_id = 'local-agent'
with pytest.raises(ValueError) as exc_info:
parse_runner_id(runner_id)
assert 'Invalid runner ID format' in str(exc_info.value)
def test_parse_invalid_runner_id_empty_string(self):
"""Parse empty runner ID."""
runner_id = ''
with pytest.raises(ValueError):
parse_runner_id(runner_id)
class TestRunnerIdFormatting:
"""Tests for format_runner_id."""
def test_format_plugin_runner_id(self):
"""Format plugin runner ID."""
runner_id = format_runner_id(
source='plugin',
plugin_author='langbot',
plugin_name='local-agent',
runner_name='default',
)
assert runner_id == 'plugin:langbot/local-agent/default'
def test_format_invalid_source(self):
"""Format runner ID with invalid source."""
with pytest.raises(ValueError) as exc_info:
format_runner_id(
source='builtin',
plugin_author='langbot',
plugin_name='local-agent',
runner_name='default',
)
assert 'Invalid runner source' in str(exc_info.value)
class TestRunnerIdParts:
"""Tests for RunnerIdParts dataclass."""
def test_get_plugin_id(self):
"""Get plugin ID from parts."""
parts = RunnerIdParts(
source='plugin',
plugin_author='langbot',
plugin_name='local-agent',
runner_name='default',
)
assert parts.to_plugin_id() == 'langbot/local-agent'
def test_frozen_dataclass(self):
"""RunnerIdParts should be immutable."""
parts = RunnerIdParts(
source='plugin',
plugin_author='langbot',
plugin_name='local-agent',
runner_name='default',
)
with pytest.raises(Exception): # FrozenInstanceError
parts.plugin_author = 'other'
class TestIsPluginRunnerId:
"""Tests for is_plugin_runner_id."""
def test_is_plugin_runner_id_true(self):
"""Check plugin runner ID returns True."""
assert is_plugin_runner_id('plugin:langbot/local-agent/default') is True
def test_is_plugin_runner_id_false(self):
"""Check non-plugin runner ID returns False."""
assert is_plugin_runner_id('local-agent') is False
assert is_plugin_runner_id('builtin:local-agent') is False
assert is_plugin_runner_id('') is False