feat: support dynamic agent runner defaults

This commit is contained in:
huanghuoguoguo
2026-05-16 09:35:40 +08:00
parent b1cde952e4
commit c2037f9833
16 changed files with 981 additions and 117 deletions

View File

@@ -187,6 +187,15 @@ 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)
if registry is None:
return
try:
await registry.refresh()
except Exception as e:
self.ap.logger.warning(f'Failed to refresh agent runner registry: {e}')
async def ping_plugin_runtime(self):
if not hasattr(self, 'handler'):
raise PluginRuntimeNotConnectedError('Plugin runtime is not connected')
@@ -546,6 +555,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
task_context.metadata.update(metadata)
await self._wait_for_installed_plugin_ready(plugin_author, plugin_name, task_context)
await self._refresh_agent_runner_registry()
async def upgrade_plugin(
self,
@@ -564,6 +574,8 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
if task_context is not None:
task_context.trace(trace)
await self._refresh_agent_runner_registry()
async def delete_plugin(
self,
plugin_author: str,
@@ -588,6 +600,8 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
task_context.trace('Cleaning up plugin configuration and storage...')
await self.handler.cleanup_plugin_data(plugin_author, plugin_name)
await self._refresh_agent_runner_registry()
async def list_plugins(self, component_kinds: list[str] | None = None) -> list[dict[str, Any]]:
"""List plugins, optionally filtered by component kinds.

View File

@@ -41,6 +41,63 @@ def _make_rag_error_response(error: Exception, error_type: str, **extra_context)
return handler.ActionResponse.error(message=message)
def _i18n_to_dict(value: Any) -> dict[str, Any]:
"""Convert SDK i18n values to plain dictionaries."""
if value is None:
return {}
if isinstance(value, dict):
return value
if hasattr(value, 'to_dict'):
return value.to_dict()
if hasattr(value, 'model_dump'):
return value.model_dump()
return {'en_US': str(value)}
def _i18n_to_text(value: Any) -> str:
"""Return a stable human-readable text from SDK i18n values."""
data = _i18n_to_dict(value)
for key in ('en_US', 'zh_Hans', 'zh_Hant'):
text = data.get(key)
if text:
return str(text)
for text in data.values():
if text:
return str(text)
return ''
def _build_tool_detail(tool: Any, requested_tool_name: str | None = None) -> dict[str, Any]:
"""Normalize LLMTool and plugin ComponentManifest objects for tool detail APIs."""
if hasattr(tool, 'metadata') and hasattr(tool, 'spec'):
metadata = tool.metadata
spec = tool.spec or {}
description = spec.get('llm_prompt') or _i18n_to_text(getattr(metadata, 'description', None))
parameters = spec.get('parameters') or {}
return {
'name': requested_tool_name or getattr(metadata, 'name', ''),
'label': _i18n_to_dict(getattr(metadata, 'label', None)),
'description': description,
'human_desc': description,
'parameters': parameters,
'spec': spec,
}
name = getattr(tool, 'name', requested_tool_name or '')
description = getattr(tool, 'description', None) or getattr(tool, 'human_desc', '') or ''
parameters = getattr(tool, 'parameters', None) or {}
return {
'name': name,
'label': {},
'description': description,
'human_desc': getattr(tool, 'human_desc', description) or description,
'parameters': parameters,
'spec': {'parameters': parameters},
}
async def _validate_run_authorization(
run_id: str,
resource_type: str,
@@ -462,7 +519,13 @@ class RuntimeConnectionHandler(handler.Handler):
return
messages_obj = [provider_message.Message.model_validate(message) for message in messages]
funcs_obj = [resource_tool.LLMTool.model_validate(func) for func in funcs]
# The func field is excluded during model_dump() in plugin side
# but required by LLMTool validation on Host.
async def _placeholder_func(**kwargs):
pass
funcs_obj = [resource_tool.LLMTool.model_validate({**func, 'func': _placeholder_func}) for func in funcs]
async for chunk in llm_model.provider.invoke_llm_stream(
query=None,
@@ -538,30 +601,26 @@ class RuntimeConnectionHandler(handler.Handler):
"""
tool_name = data['tool_name']
run_id = data.get('run_id') # Optional: present for AgentRunner calls
caller_plugin_identity = data.get('caller_plugin_identity') # Optional: for cross-plugin validation
# Permission validation for AgentRunner calls
if run_id:
session, error = await _validate_run_authorization(
run_id, 'tool', tool_name, self.ap
run_id, 'tool', tool_name, self.ap, caller_plugin_identity
)
if error:
return error
try:
tool = self.ap.tool_mgr.get_tool_by_name(tool_name)
tool = await self.ap.tool_mgr.get_tool_by_name(tool_name)
if tool is None:
return handler.ActionResponse.error(
message=f'Tool {tool_name} not found',
)
# Build tool detail for LLM function calling
tool_detail = {
'name': tool.name,
'description': tool.description or '',
'parameters': tool.parameters or {},
}
tool_detail = _build_tool_detail(tool, requested_tool_name=tool_name)
return handler.ActionResponse.success(data=tool_detail)
return handler.ActionResponse.success(data={'tool': tool_detail})
except Exception as e:
traceback.print_exc()
return handler.ActionResponse.error(