mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-08 14:56:03 +00:00
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>
92 lines
2.6 KiB
Python
92 lines
2.6 KiB
Python
"""Agent runner ID parsing and formatting."""
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class RunnerIdParts:
|
|
"""Parsed runner ID components."""
|
|
source: str # 'plugin' (future: 'builtin')
|
|
plugin_author: str
|
|
plugin_name: str
|
|
runner_name: str
|
|
|
|
def to_plugin_id(self) -> str:
|
|
"""Return plugin identifier as author/name."""
|
|
return f'{self.plugin_author}/{self.plugin_name}'
|
|
|
|
|
|
def parse_runner_id(runner_id: str) -> RunnerIdParts:
|
|
"""Parse runner ID string into components.
|
|
|
|
Args:
|
|
runner_id: Runner ID in format 'plugin:author/plugin_name/runner_name'
|
|
|
|
Returns:
|
|
RunnerIdParts with parsed components
|
|
|
|
Raises:
|
|
ValueError: If runner_id format is invalid
|
|
"""
|
|
if runner_id.startswith('plugin:'):
|
|
parts = runner_id[7:].split('/')
|
|
if len(parts) != 3:
|
|
raise ValueError(
|
|
f'Invalid plugin runner ID format: {runner_id}. '
|
|
f'Expected: plugin:author/plugin_name/runner_name'
|
|
)
|
|
plugin_author, plugin_name, runner_name = parts
|
|
if not plugin_author or not plugin_name or not runner_name:
|
|
raise ValueError(
|
|
f'Invalid plugin runner ID: {runner_id}. '
|
|
f'author, plugin_name, and runner_name must be non-empty'
|
|
)
|
|
return RunnerIdParts(
|
|
source='plugin',
|
|
plugin_author=plugin_author,
|
|
plugin_name=plugin_name,
|
|
runner_name=runner_name,
|
|
)
|
|
else:
|
|
# For backward compatibility with old built-in runner names
|
|
# This should eventually be removed after migration
|
|
raise ValueError(
|
|
f'Invalid runner ID format: {runner_id}. '
|
|
f'Expected: plugin:author/plugin_name/runner_name'
|
|
)
|
|
|
|
|
|
def format_runner_id(
|
|
source: str,
|
|
plugin_author: str,
|
|
plugin_name: str,
|
|
runner_name: str,
|
|
) -> str:
|
|
"""Format runner ID from components.
|
|
|
|
Args:
|
|
source: Runner source ('plugin')
|
|
plugin_author: Plugin author
|
|
plugin_name: Plugin name
|
|
runner_name: Runner component name
|
|
|
|
Returns:
|
|
Runner ID string
|
|
"""
|
|
if source == 'plugin':
|
|
return f'plugin:{plugin_author}/{plugin_name}/{runner_name}'
|
|
else:
|
|
raise ValueError(f'Invalid runner source: {source}')
|
|
|
|
|
|
def is_plugin_runner_id(runner_id: str) -> bool:
|
|
"""Check if runner ID is a plugin runner.
|
|
|
|
Args:
|
|
runner_id: Runner ID string
|
|
|
|
Returns:
|
|
True if runner ID starts with 'plugin:'
|
|
"""
|
|
return runner_id.startswith('plugin:') |