mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-24 09:25:21 +08:00
feat(runner): unify plugin execution across agents and event processors
This commit is contained in:
@@ -26,7 +26,7 @@ class _RuntimeActionName:
|
||||
|
||||
AGENT_RUN_ADMIN_PERMISSION = 'agent_run:admin'
|
||||
RUNTIME_ADMIN_PERMISSION = 'runtime:admin'
|
||||
AGENT_RUNNER_ADMIN_PERMISSION = 'agent_runner:admin'
|
||||
RUNNER_ADMIN_PERMISSION = 'runner:admin'
|
||||
LEDGER_ONLY_SIDE_EFFECTING_RESULT_TYPES = {
|
||||
'message.delta',
|
||||
'message.completed',
|
||||
@@ -50,15 +50,15 @@ def _normalize_permission_set(value: Any) -> set[str]:
|
||||
return set()
|
||||
|
||||
|
||||
def _iter_agent_runner_admin_plugin_configs(ap: app.Application) -> list[dict[str, Any]]:
|
||||
def _iter_runner_admin_plugin_configs(ap: app.Application) -> list[dict[str, Any]]:
|
||||
instance_config = getattr(ap, 'instance_config', None)
|
||||
config_data = getattr(instance_config, 'data', {}) if instance_config is not None else {}
|
||||
if not isinstance(config_data, dict):
|
||||
return []
|
||||
agent_runner_config = config_data.get('agent_runner', {})
|
||||
if not isinstance(agent_runner_config, dict):
|
||||
runner_config = config_data.get('runner', {})
|
||||
if not isinstance(runner_config, dict):
|
||||
return []
|
||||
raw_admin_plugins = agent_runner_config.get('admin_plugins', [])
|
||||
raw_admin_plugins = runner_config.get('admin_plugins', [])
|
||||
if isinstance(raw_admin_plugins, dict):
|
||||
items: list[dict[str, Any]] = []
|
||||
for identity, entry in raw_admin_plugins.items():
|
||||
@@ -74,12 +74,12 @@ def _iter_agent_runner_admin_plugin_configs(ap: app.Application) -> list[dict[st
|
||||
return []
|
||||
|
||||
|
||||
def _agent_runner_admin_permissions(ap: app.Application, plugin_identity: str | None) -> set[str]:
|
||||
def _runner_admin_permissions(ap: app.Application, plugin_identity: str | None) -> set[str]:
|
||||
if not isinstance(plugin_identity, str) or not plugin_identity.strip():
|
||||
return set()
|
||||
normalized_identity = plugin_identity.strip()
|
||||
permissions: set[str] = set()
|
||||
for entry in _iter_agent_runner_admin_plugin_configs(ap):
|
||||
for entry in _iter_runner_admin_plugin_configs(ap):
|
||||
if entry.get('enabled', True) is False:
|
||||
continue
|
||||
identity = entry.get('identity') or entry.get('plugin_identity') or entry.get('plugin') or entry.get('id')
|
||||
@@ -90,19 +90,19 @@ def _agent_runner_admin_permissions(ap: app.Application, plugin_identity: str |
|
||||
return permissions
|
||||
|
||||
|
||||
def _has_agent_runner_admin_permission(
|
||||
def _has_runner_admin_permission(
|
||||
ap: app.Application,
|
||||
plugin_identity: str | None,
|
||||
permission: str,
|
||||
) -> bool:
|
||||
permissions = _agent_runner_admin_permissions(ap, plugin_identity)
|
||||
permissions = _runner_admin_permissions(ap, plugin_identity)
|
||||
if not permissions:
|
||||
return False
|
||||
domain = permission.split(':', 1)[0]
|
||||
return bool(
|
||||
permission in permissions
|
||||
or f'{domain}:*' in permissions
|
||||
or AGENT_RUNNER_ADMIN_PERMISSION in permissions
|
||||
or RUNNER_ADMIN_PERMISSION in permissions
|
||||
or '*' in permissions
|
||||
)
|
||||
|
||||
@@ -251,11 +251,11 @@ async def _validate_agent_run_session(
|
||||
allow_persistent_authorization: bool = False,
|
||||
admin_permission: str | None = None,
|
||||
) -> Union[tuple[None, handler.ActionResponse], tuple[Any, None]]:
|
||||
"""Validate an AgentRunner pull API run session and run-scoped API access."""
|
||||
"""Validate an Runner pull API run session and run-scoped API access."""
|
||||
if (
|
||||
not run_id
|
||||
and admin_permission
|
||||
and _has_agent_runner_admin_permission(
|
||||
and _has_runner_admin_permission(
|
||||
ap,
|
||||
caller_plugin_identity,
|
||||
admin_permission,
|
||||
@@ -294,7 +294,7 @@ async def _validate_agent_run_session(
|
||||
|
||||
if api_capability:
|
||||
available_apis = _get_run_authorization(session).get('available_apis', {})
|
||||
has_admin_permission = bool(admin_permission) and _has_agent_runner_admin_permission(
|
||||
has_admin_permission = bool(admin_permission) and _has_runner_admin_permission(
|
||||
ap,
|
||||
caller_plugin_identity,
|
||||
admin_permission,
|
||||
@@ -428,7 +428,7 @@ def _project_event_record_for_api(event: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _project_runner_descriptor_for_api(descriptor: Any) -> dict[str, Any]:
|
||||
"""Project an AgentRunnerDescriptor-like object onto a JSON dict."""
|
||||
"""Project an RunnerDescriptor-like object onto a JSON dict."""
|
||||
if isinstance(descriptor, dict):
|
||||
return dict(descriptor)
|
||||
if hasattr(descriptor, 'model_dump'):
|
||||
@@ -449,7 +449,7 @@ def _project_runner_descriptor_for_api(descriptor: Any) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
async def _record_agent_runner_admin_action(
|
||||
async def _record_runner_admin_action(
|
||||
ap: app.Application,
|
||||
store: Any,
|
||||
*,
|
||||
@@ -460,7 +460,7 @@ async def _record_agent_runner_admin_action(
|
||||
target_runtime_id: str | None = None,
|
||||
detail: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Record a small audit trail for privileged AgentRunner operations."""
|
||||
"""Record a small audit trail for privileged Runner operations."""
|
||||
audit_data: dict[str, Any] = {
|
||||
'action': action,
|
||||
'caller_plugin_identity': caller_plugin_identity,
|
||||
@@ -485,4 +485,4 @@ async def _record_agent_runner_admin_action(
|
||||
metadata={'permission': permission},
|
||||
)
|
||||
except Exception as exc:
|
||||
ap.logger.warning(f'Failed to record AgentRunner admin audit event: {exc}', exc_info=True)
|
||||
ap.logger.warning(f'Failed to record Runner admin audit event: {exc}', exc_info=True)
|
||||
|
||||
@@ -71,7 +71,7 @@ def register(h):
|
||||
await store.append_event(
|
||||
event_id=None,
|
||||
event_type='steering.injected',
|
||||
source='agent_runner',
|
||||
source='runner',
|
||||
bot_id=conversation.get('bot_id') if isinstance(conversation, dict) else None,
|
||||
workspace_id=conversation.get('workspace_id') if isinstance(conversation, dict) else None,
|
||||
conversation_id=conversation.get('conversation_id') if isinstance(conversation, dict) else None,
|
||||
|
||||
@@ -1094,8 +1094,8 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
async def initialize_plugins(self):
|
||||
pass
|
||||
|
||||
async def _refresh_agent_runner_registry(self) -> None:
|
||||
registry = getattr(self.ap, 'agent_runner_registry', None)
|
||||
async def _refresh_runner_registry(self) -> None:
|
||||
registry = getattr(self.ap, 'runner_registry', None)
|
||||
if registry is None:
|
||||
return
|
||||
try:
|
||||
@@ -1814,7 +1814,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
if task_context is not None:
|
||||
task_context.set_current_action('refreshing plugin components')
|
||||
task_context.metadata['progress_percent'] = 95
|
||||
await self._refresh_agent_runner_registry()
|
||||
await self._refresh_runner_registry()
|
||||
if task_context is not None:
|
||||
operation = task_context.metadata.get('operation')
|
||||
task_context.set_current_action('plugin updated' if operation == 'upgrade' else 'plugin installed')
|
||||
@@ -1903,7 +1903,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
self._workspace_installations.pop(binding.workspace_uuid, None)
|
||||
if task_context is not None:
|
||||
task_context.set_current_action('plugin removed')
|
||||
await self._refresh_agent_runner_registry()
|
||||
await self._refresh_runner_registry()
|
||||
return {}
|
||||
|
||||
async def list_plugins(self, component_kinds: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
@@ -2245,9 +2245,9 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
async for ret in gen:
|
||||
yield command_context.CommandReturn.model_validate(ret)
|
||||
|
||||
# AgentRunner methods
|
||||
async def list_agent_runners(self, bound_plugins: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
"""List all available AgentRunner components.
|
||||
# Runner methods
|
||||
async def list_runners(self, bound_plugins: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
"""List all available Runner components.
|
||||
|
||||
Returns list of dicts with plugin_author, plugin_name, runner_name, manifest, etc.
|
||||
"""
|
||||
@@ -2260,26 +2260,26 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
runners: list[dict[str, Any]] = []
|
||||
for binding in await self._operation_bindings(include_plugins=bound_plugins):
|
||||
with runtime_handler.installation_scope(binding):
|
||||
runners.extend(await runtime_handler.list_agent_runners(include_plugins=bound_plugins))
|
||||
runners.extend(await runtime_handler.list_runners(include_plugins=bound_plugins))
|
||||
return runners
|
||||
|
||||
async def run_agent(
|
||||
async def run_runner(
|
||||
self,
|
||||
plugin_author: str,
|
||||
plugin_name: str,
|
||||
runner_name: str,
|
||||
context: dict[str, Any],
|
||||
) -> typing.AsyncGenerator[dict[str, Any], None]:
|
||||
"""Run an AgentRunner from a plugin.
|
||||
"""Run an Runner from a plugin.
|
||||
|
||||
Args:
|
||||
plugin_author: Plugin author
|
||||
plugin_name: Plugin name
|
||||
runner_name: AgentRunner component name
|
||||
context: AgentRunContext as dict
|
||||
runner_name: Runner component name
|
||||
context: RunnerContext as dict
|
||||
|
||||
Yields:
|
||||
AgentRunResult dicts
|
||||
RunnerResult dicts
|
||||
"""
|
||||
if not self.is_enable_plugin:
|
||||
# Return a protocol-level failure result.
|
||||
@@ -2297,7 +2297,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
'metadata', {}
|
||||
).get('workspace_id')
|
||||
if not isinstance(workspace_id, str) or not workspace_id.strip():
|
||||
raise ValueError('AgentRunner execution requires a Workspace')
|
||||
raise ValueError('Runner execution requires a Workspace')
|
||||
execution_context = await self._current_execution_context()
|
||||
if workspace_id.strip() != execution_context.workspace_uuid:
|
||||
raise WorkspaceNotFoundError('Plugin resource not found')
|
||||
@@ -2309,7 +2309,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
)
|
||||
runtime_handler = self._runtime_handler()
|
||||
with runtime_handler.installation_scope(binding):
|
||||
async for ret in runtime_handler.run_agent(
|
||||
async for ret in runtime_handler.run_runner(
|
||||
plugin_author,
|
||||
plugin_name,
|
||||
runner_name,
|
||||
|
||||
@@ -56,7 +56,7 @@ from ..agent.runner.platform_tools import execute_platform_tool, get_platform_to
|
||||
from ..pipeline.pool import get_query_execution_context
|
||||
|
||||
|
||||
from . import agent_pull_actions, agent_runner_actions, agent_state_actions
|
||||
from . import agent_pull_actions, runner_actions, agent_state_actions
|
||||
from .agent_run_support import (
|
||||
_validate_agent_run_session,
|
||||
)
|
||||
@@ -177,7 +177,7 @@ async def _get_pipeline_knowledge_base_uuids(ap: app.Application, query: Any) ->
|
||||
return []
|
||||
|
||||
runner_config = RunnerConfigResolver.resolve_runner_config(pipeline_config, runner_id)
|
||||
registry = getattr(ap, 'agent_runner_registry', None)
|
||||
registry = getattr(ap, 'runner_registry', None)
|
||||
if registry is None:
|
||||
return []
|
||||
|
||||
@@ -189,7 +189,7 @@ async def _get_pipeline_knowledge_base_uuids(ap: app.Application, query: Any) ->
|
||||
bound_plugins,
|
||||
)
|
||||
except Exception as e:
|
||||
ap.logger.warning(f'Failed to load AgentRunner descriptor for knowledge-base scope: {e}')
|
||||
ap.logger.warning(f'Failed to load Runner descriptor for knowledge-base scope: {e}')
|
||||
return []
|
||||
|
||||
return config_schema.extract_knowledge_base_uuids(descriptor, runner_config)
|
||||
@@ -1275,7 +1275,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def count_tokens(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Count model input tokens.
|
||||
|
||||
For AgentRunner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For Runner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
"""
|
||||
action_context, _ = await self._require_plugin_action_context()
|
||||
@@ -1338,7 +1338,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def invoke_llm(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Invoke llm
|
||||
|
||||
For AgentRunner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For Runner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
"""
|
||||
action_context, _ = await self._require_plugin_action_context()
|
||||
@@ -1346,11 +1346,11 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
messages = data['messages']
|
||||
funcs = data.get('funcs', [])
|
||||
extra_args = data.get('extra_args', {})
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
session = None
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'model', llm_model_uuid, self.ap, caller_plugin_identity, operation='invoke'
|
||||
@@ -1431,7 +1431,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def invoke_llm_stream(data: dict[str, Any]):
|
||||
"""Invoke llm with streaming response
|
||||
|
||||
For AgentRunner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For Runner calls: requires run_id and validates model_uuid against session.resources.models.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
"""
|
||||
action_context, _ = await self._require_plugin_action_context()
|
||||
@@ -1439,11 +1439,11 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
messages = data['messages']
|
||||
funcs = data.get('funcs', [])
|
||||
extra_args = data.get('extra_args', {})
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
session = None
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'model', llm_model_uuid, self.ap, caller_plugin_identity, operation='stream'
|
||||
@@ -1565,27 +1565,27 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def call_tool(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Call a tool
|
||||
|
||||
For AgentRunner calls: requires run_id and validates tool_name against session.resources.tools.
|
||||
For Runner calls: requires run_id and validates tool_name against session.resources.tools.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
"""
|
||||
tool_name = data['tool_name']
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
session = None
|
||||
source_ref = None
|
||||
is_agent_runner_call = bool(run_id)
|
||||
is_runner_call = bool(run_id)
|
||||
action_context = self._require_runtime_action_context()
|
||||
|
||||
if is_agent_runner_call:
|
||||
if is_runner_call:
|
||||
if 'parameters' not in data:
|
||||
return handler.ActionResponse.error(
|
||||
message='parameters is required for AgentRunner tool calls',
|
||||
message='parameters is required for Runner tool calls',
|
||||
)
|
||||
parameters = data.get('parameters') or {}
|
||||
else:
|
||||
parameters = data.get('tool_parameters') or {}
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'tool', tool_name, self.ap, caller_plugin_identity, operation='call'
|
||||
@@ -1627,7 +1627,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
result = await self.ap.tool_mgr.execute_func_call(
|
||||
**execute_kwargs,
|
||||
)
|
||||
if is_agent_runner_call:
|
||||
if is_runner_call:
|
||||
return handler.ActionResponse.success(data={'result': result})
|
||||
return handler.ActionResponse.success(data={'tool_response': result})
|
||||
except Exception as e:
|
||||
@@ -1640,19 +1640,19 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def get_tool_detail(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Get tool detail for LLM function calling.
|
||||
|
||||
For AgentRunner calls: requires run_id and validates tool_name against session.resources.tools.
|
||||
For Runner calls: requires run_id and validates tool_name against session.resources.tools.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
|
||||
Returns tool manifest including name, description, and parameters schema.
|
||||
"""
|
||||
tool_name = data['tool_name']
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
session = None
|
||||
source_ref = None
|
||||
action_context = self._require_runtime_action_context()
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'tool', tool_name, self.ap, caller_plugin_identity, operation='detail'
|
||||
@@ -1693,10 +1693,10 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
|
||||
# ================= Binary Storage Handlers =================
|
||||
# Permission validation:
|
||||
# - For AgentRunner calls (with run_id): validates storage permission via session_registry
|
||||
# - For Runner calls (with run_id): validates storage permission via session_registry
|
||||
# - For regular plugin calls (no run_id): unrestricted access (backward compatibility)
|
||||
# - Plugin storage: inherent isolation via owner = plugin identity (set by SDK runtime)
|
||||
# - Workspace storage: requires ctx.resources.storage.workspace_storage for AgentRunner
|
||||
# - Workspace storage: requires ctx.resources.storage.workspace_storage for Runner
|
||||
|
||||
@self.action(RuntimeToLangBotAction.SET_BINARY_STORAGE)
|
||||
async def set_binary_storage(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
@@ -2329,10 +2329,10 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def retrieve_knowledge(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Retrieve documents from any knowledge base.
|
||||
|
||||
For AgentRunner calls: requires run_id and validates kb_id against session.resources.knowledge_bases.
|
||||
For Runner calls: requires run_id and validates kb_id against session.resources.knowledge_bases.
|
||||
For regular plugin calls: no run_id, unrestricted access (backward compatibility).
|
||||
|
||||
Note: SDK AgentRunAPIProxy.retrieve_knowledge calls this action with run_id.
|
||||
Note: SDK RunnerAPIProxy.retrieve_knowledge calls this action with run_id.
|
||||
"""
|
||||
action_context, _ = await self._require_plugin_action_context()
|
||||
execution_context = self._execution_context(action_context)
|
||||
@@ -2340,10 +2340,10 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
query_text = data['query_text']
|
||||
top_k = data.get('top_k', 5)
|
||||
filters = data.get('filters') or {}
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'knowledge_base', kb_id, self.ap, caller_plugin_identity, operation='retrieve'
|
||||
@@ -2410,11 +2410,11 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
async def retrieve_knowledge_base(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""Retrieve documents from a knowledge base within the current run or query scope.
|
||||
|
||||
For AgentRunner calls: requires run_id and validates kb_id against session.resources.knowledge_bases.
|
||||
For Runner calls: requires run_id and validates kb_id against session.resources.knowledge_bases.
|
||||
For regular plugin calls: no run_id, validates against pipeline's configured knowledge bases.
|
||||
|
||||
Note: This action has dual validation paths:
|
||||
- AgentRunner: uses session_registry for permission check
|
||||
- Runner: uses session_registry for permission check
|
||||
- Regular plugin: uses RunnerConfigResolver.resolve_runner_config for pipeline-level check
|
||||
"""
|
||||
action_context, _ = await self._require_plugin_action_context()
|
||||
@@ -2424,12 +2424,12 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
query_text = data['query_text']
|
||||
top_k = data.get('top_k', 5)
|
||||
filters = data.get('filters') or {}
|
||||
run_id = data.get('run_id') # Optional: present for AgentRunner calls
|
||||
run_id = data.get('run_id') # Optional: present for Runner calls
|
||||
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
|
||||
session = None
|
||||
query = None
|
||||
|
||||
# Permission validation for AgentRunner calls
|
||||
# Permission validation for Runner calls
|
||||
if run_id:
|
||||
session, error = await _validate_run_authorization(
|
||||
run_id, 'knowledge_base', kb_id, self.ap, caller_plugin_identity, operation='retrieve'
|
||||
@@ -2535,7 +2535,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
)
|
||||
|
||||
agent_pull_actions.register(self)
|
||||
agent_runner_actions.register(self)
|
||||
runner_actions.register(self)
|
||||
agent_state_actions.register(self)
|
||||
|
||||
@self.action(CommonAction.PING)
|
||||
@@ -2834,7 +2834,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
|
||||
return result['tools']
|
||||
|
||||
async def list_agent_runners(self, include_plugins: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
async def list_runners(self, include_plugins: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
"""List agent runners from plugin runtime.
|
||||
|
||||
Returns list of dicts with:
|
||||
@@ -2844,7 +2844,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
- manifest
|
||||
"""
|
||||
result = await self.call_action(
|
||||
LangBotToRuntimeAction.LIST_AGENT_RUNNERS,
|
||||
LangBotToRuntimeAction.LIST_RUNNERS,
|
||||
{
|
||||
'include_plugins': include_plugins,
|
||||
},
|
||||
@@ -2853,20 +2853,20 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
|
||||
return result['runners']
|
||||
|
||||
async def run_agent(
|
||||
async def run_runner(
|
||||
self,
|
||||
plugin_author: str,
|
||||
plugin_name: str,
|
||||
runner_name: str,
|
||||
context: dict[str, Any],
|
||||
) -> typing.AsyncGenerator[dict[str, Any], None]:
|
||||
"""Run an AgentRunner component.
|
||||
"""Run an Runner component.
|
||||
|
||||
Yields AgentRunResult dicts.
|
||||
Yields RunnerResult dicts.
|
||||
"""
|
||||
timeout = self._get_runner_action_timeout(context)
|
||||
gen = self.call_action_generator(
|
||||
LangBotToRuntimeAction.RUN_AGENT,
|
||||
LangBotToRuntimeAction.RUN_RUNNER,
|
||||
{
|
||||
'plugin_author': plugin_author,
|
||||
'plugin_name': plugin_name,
|
||||
|
||||
+38
-38
@@ -16,7 +16,7 @@ from .agent_run_support import (
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
_plugin_runtime_action,
|
||||
_has_agent_runner_admin_permission,
|
||||
_has_runner_admin_permission,
|
||||
_deadline_seconds_from_payload,
|
||||
_get_run_authorization,
|
||||
_authorize_target_run,
|
||||
@@ -27,7 +27,7 @@ from .agent_run_support import (
|
||||
_run_scope_filters,
|
||||
_run_ledger_scope_filters,
|
||||
_project_runner_descriptor_for_api,
|
||||
_record_agent_runner_admin_action,
|
||||
_record_runner_admin_action,
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ def register(h):
|
||||
run_id = data.get('run_id')
|
||||
target_run_id = data.get('target_run_id') or run_id
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -74,7 +74,7 @@ def register(h):
|
||||
if auth_error:
|
||||
return auth_error
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_get',
|
||||
@@ -96,7 +96,7 @@ def register(h):
|
||||
before_cursor = data.get('before_cursor')
|
||||
limit = data.get('limit', 50)
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -159,7 +159,7 @@ def register(h):
|
||||
**scope_filters,
|
||||
)
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_list',
|
||||
@@ -185,10 +185,10 @@ def register(h):
|
||||
|
||||
@h.action(_plugin_runtime_action('RUNNER_LIST', 'runner_list'))
|
||||
async def runner_list(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
"""List Host-discovered AgentRunner descriptors."""
|
||||
"""List Host-discovered Runner descriptors."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -213,7 +213,7 @@ def register(h):
|
||||
if include_plugins is not None and not isinstance(include_plugins, list):
|
||||
return handler.ActionResponse.error(message='include_plugins must be a list')
|
||||
|
||||
registry = getattr(h.ap, 'agent_runner_registry', None)
|
||||
registry = getattr(h.ap, 'runner_registry', None)
|
||||
if registry is None:
|
||||
return handler.ActionResponse.success(data={'items': []})
|
||||
|
||||
@@ -231,7 +231,7 @@ def register(h):
|
||||
)
|
||||
items = [_project_runner_descriptor_for_api(item) for item in runners]
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
None,
|
||||
action='runner_list',
|
||||
@@ -257,7 +257,7 @@ def register(h):
|
||||
limit = data.get('limit', 50)
|
||||
direction = data.get('direction', 'forward')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -307,7 +307,7 @@ def register(h):
|
||||
direction=str(direction or 'forward'),
|
||||
)
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_events_page',
|
||||
@@ -333,7 +333,7 @@ def register(h):
|
||||
run_id = data.get('run_id')
|
||||
target_run_id = data.get('target_run_id') or run_id
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -376,7 +376,7 @@ def register(h):
|
||||
if not updated:
|
||||
return handler.ActionResponse.error(message=f'Run {target_run_id} not found')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_cancel',
|
||||
@@ -397,7 +397,7 @@ def register(h):
|
||||
target_run_id = data.get('target_run_id') or run_id
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
result = data.get('result') if isinstance(data.get('result'), dict) else {}
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -479,7 +479,7 @@ def register(h):
|
||||
metadata=metadata,
|
||||
)
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_append_result',
|
||||
@@ -500,7 +500,7 @@ def register(h):
|
||||
target_run_id = data.get('target_run_id') or run_id
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
status = data.get('status')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -558,7 +558,7 @@ def register(h):
|
||||
if not updated:
|
||||
return handler.ActionResponse.error(message=f'Run {target_run_id} not found')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_finalize',
|
||||
@@ -578,7 +578,7 @@ def register(h):
|
||||
run_id = data.get('run_id')
|
||||
runtime_id = data.get('runtime_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -617,7 +617,7 @@ def register(h):
|
||||
heartbeat_deadline_seconds=_deadline_seconds_from_payload(data),
|
||||
)
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runtime_register',
|
||||
@@ -637,7 +637,7 @@ def register(h):
|
||||
run_id = data.get('run_id')
|
||||
runtime_id = data.get('runtime_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -675,7 +675,7 @@ def register(h):
|
||||
if runtime is None:
|
||||
return handler.ActionResponse.error(message=f'Runtime {runtime_id} not found')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runtime_heartbeat',
|
||||
@@ -694,7 +694,7 @@ def register(h):
|
||||
"""List Host-owned runtime registry records."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -730,7 +730,7 @@ def register(h):
|
||||
limit=data.get('limit', 50),
|
||||
)
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runtime_list',
|
||||
@@ -759,7 +759,7 @@ def register(h):
|
||||
"""Reconcile stale runtime heartbeats and expired claim leases."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -796,7 +796,7 @@ def register(h):
|
||||
)
|
||||
released_claims = await store.release_expired_claims()
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runtime_reconcile',
|
||||
@@ -824,7 +824,7 @@ def register(h):
|
||||
"""Get run statistics within a time window (admin-only)."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -858,7 +858,7 @@ def register(h):
|
||||
end_time=end_time,
|
||||
runner_id=runner_id,
|
||||
)
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_stats',
|
||||
@@ -880,7 +880,7 @@ def register(h):
|
||||
"""Get runtime registry statistics (admin-only)."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -906,7 +906,7 @@ def register(h):
|
||||
|
||||
try:
|
||||
stats = await store.get_runtime_stats()
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runtime_stats',
|
||||
@@ -924,7 +924,7 @@ def register(h):
|
||||
"""Get runner-aggregated statistics (admin-only)."""
|
||||
run_id = data.get('run_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
AGENT_RUN_ADMIN_PERMISSION,
|
||||
@@ -958,7 +958,7 @@ def register(h):
|
||||
end_time=end_time,
|
||||
limit=limit,
|
||||
)
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='runner_stats',
|
||||
@@ -981,7 +981,7 @@ def register(h):
|
||||
run_id = data.get('run_id')
|
||||
runtime_id = data.get('runtime_id')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -1035,7 +1035,7 @@ def register(h):
|
||||
if run is None:
|
||||
return handler.ActionResponse.error(message='No queued run available')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_claim',
|
||||
@@ -1061,7 +1061,7 @@ def register(h):
|
||||
runtime_id = data.get('runtime_id')
|
||||
claim_token = data.get('claim_token')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -1108,7 +1108,7 @@ def register(h):
|
||||
if run is None:
|
||||
return handler.ActionResponse.error(message=f'Run claim {target_run_id} not found')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_renew_claim',
|
||||
@@ -1131,7 +1131,7 @@ def register(h):
|
||||
runtime_id = data.get('runtime_id')
|
||||
claim_token = data.get('claim_token')
|
||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
||||
is_admin = _has_agent_runner_admin_permission(
|
||||
is_admin = _has_runner_admin_permission(
|
||||
h.ap,
|
||||
caller_plugin_identity,
|
||||
RUNTIME_ADMIN_PERMISSION,
|
||||
@@ -1184,7 +1184,7 @@ def register(h):
|
||||
if run is None:
|
||||
return handler.ActionResponse.error(message=f'Run claim {target_run_id} not found')
|
||||
if is_admin:
|
||||
await _record_agent_runner_admin_action(
|
||||
await _record_runner_admin_action(
|
||||
h.ap,
|
||||
store,
|
||||
action='run_release_claim',
|
||||
Reference in New Issue
Block a user