feat(agent-runner): enforce 4.x host-owned execution

This commit is contained in:
huanghuoguoguo
2026-07-12 20:36:32 +08:00
parent 9aa71d54e3
commit d88d05e27c
170 changed files with 6952 additions and 5381 deletions
@@ -16,7 +16,10 @@ class AgentsRouterGroup(group.RouterGroup):
return self.success(data={'agents': await self.ap.agent_service.get_agents(sort_by, sort_order)})
json_data = await quart.request.json
created = await self.ap.agent_service.create_agent(json_data)
try:
created = await self.ap.agent_service.create_agent(json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data=created)
@self.route('/_/metadata', methods=['GET'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
@@ -33,7 +36,10 @@ class AgentsRouterGroup(group.RouterGroup):
if quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.agent_service.update_agent(agent_uuid, json_data)
try:
await self.ap.agent_service.update_agent(agent_uuid, json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
await self.ap.agent_service.delete_agent(agent_uuid)
@@ -3,6 +3,7 @@ from __future__ import annotations
import quart
from ... import group
from ......pipeline.extension_preferences import normalize_extension_preferences
@group.group_class('pipelines', '/api/v1/pipelines')
@@ -19,7 +20,10 @@ class PipelinesRouterGroup(group.RouterGroup):
elif quart.request.method == 'POST':
json_data = await quart.request.json
pipeline_uuid = await self.ap.pipeline_service.create_pipeline(json_data)
try:
pipeline_uuid = await self.ap.pipeline_service.create_pipeline(json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data={'uuid': pipeline_uuid})
@@ -41,7 +45,10 @@ class PipelinesRouterGroup(group.RouterGroup):
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.pipeline_service.update_pipeline(pipeline_uuid, json_data)
try:
await self.ap.pipeline_service.update_pipeline(pipeline_uuid, json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
elif quart.request.method == 'DELETE':
@@ -81,21 +88,19 @@ class PipelinesRouterGroup(group.RouterGroup):
self.ap.logger.warning('Unable to list skills for pipeline extensions: %s', exc)
available_skills = []
extensions_prefs = pipeline.get('extensions_preferences', {})
extensions_prefs = normalize_extension_preferences(pipeline.get('extensions_preferences'))
return self.success(
data={
'enable_all_plugins': extensions_prefs.get('enable_all_plugins', True),
'enable_all_mcp_servers': extensions_prefs.get('enable_all_mcp_servers', True),
'enable_all_skills': extensions_prefs.get('enable_all_skills', True),
'bound_plugins': extensions_prefs.get('plugins', []),
'enable_all_plugins': extensions_prefs['enable_all_plugins'],
'enable_all_mcp_servers': extensions_prefs['enable_all_mcp_servers'],
'enable_all_skills': extensions_prefs['enable_all_skills'],
'bound_plugins': extensions_prefs['plugins'],
'available_plugins': plugins,
'bound_mcp_servers': extensions_prefs.get('mcp_servers', []),
'bound_mcp_servers': extensions_prefs['mcp_servers'],
'available_mcp_servers': mcp_servers,
'bound_mcp_resources': extensions_prefs.get('mcp_resources', []),
'mcp_resource_agent_read_enabled': extensions_prefs.get(
'mcp_resource_agent_read_enabled', True
),
'bound_skills': extensions_prefs.get('skills', []),
'bound_mcp_resources': extensions_prefs['mcp_resources'],
'mcp_resource_agent_read_enabled': extensions_prefs['mcp_resource_agent_read_enabled'],
'bound_skills': extensions_prefs['skills'],
'available_skills': available_skills,
}
)
@@ -111,16 +116,41 @@ class PipelinesRouterGroup(group.RouterGroup):
bound_mcp_resources = json_data.get('bound_mcp_resources')
mcp_resource_agent_read_enabled = json_data.get('mcp_resource_agent_read_enabled')
await self.ap.pipeline_service.update_pipeline_extensions(
pipeline_uuid,
bound_plugins,
bound_mcp_servers,
enable_all_plugins,
enable_all_mcp_servers,
bound_skills=bound_skills,
enable_all_skills=enable_all_skills,
bound_mcp_resources=bound_mcp_resources,
mcp_resource_agent_read_enabled=mcp_resource_agent_read_enabled,
)
extension_flags = {
'enable_all_plugins': enable_all_plugins,
'enable_all_mcp_servers': enable_all_mcp_servers,
'enable_all_skills': enable_all_skills,
}
for field, value in extension_flags.items():
if field in json_data and not isinstance(value, bool):
return self.http_status(
400,
-1,
f"Pipeline extension field '{field}' must be a boolean",
)
if 'mcp_resource_agent_read_enabled' in json_data and not isinstance(
mcp_resource_agent_read_enabled, bool
):
return self.http_status(
400,
-1,
"Pipeline extension field 'mcp_resource_agent_read_enabled' must be a boolean",
)
try:
await self.ap.pipeline_service.update_pipeline_extensions(
pipeline_uuid,
bound_plugins,
bound_mcp_servers,
enable_all_plugins,
enable_all_mcp_servers,
bound_skills=bound_skills,
enable_all_skills=enable_all_skills,
bound_mcp_resources=bound_mcp_resources,
mcp_resource_agent_read_enabled=mcp_resource_agent_read_enabled,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
@@ -3,59 +3,61 @@ from __future__ import annotations
import quart
from ... import group
from ......pipeline.extension_preferences import normalize_extension_preferences
@group.group_class('tools', '/api/v1/tools')
class ToolsRouterGroup(group.RouterGroup):
async def _get_scoped_tool_catalog(self) -> list[dict] | None:
pipeline_uuid = quart.request.args.get('pipeline_uuid') or quart.request.args.get('pipeline_id')
bound_plugins: list[str] | None = None
bound_mcp_servers: list[str] | None = None
if pipeline_uuid:
pipeline = await self.ap.pipeline_service.get_pipeline(pipeline_uuid)
if pipeline is None:
return None
extensions_prefs = normalize_extension_preferences(pipeline.get('extensions_preferences'))
if not extensions_prefs['enable_all_plugins']:
bound_plugins = [f'{plugin["author"]}/{plugin["name"]}' for plugin in extensions_prefs['plugins']]
if not extensions_prefs['enable_all_mcp_servers']:
bound_mcp_servers = extensions_prefs['mcp_servers']
return await self.ap.tool_mgr.get_resolved_tool_catalog(
bound_plugins,
bound_mcp_servers,
include_skill_authoring=True,
)
async def initialize(self) -> None:
@self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
"""获取所有可用工具列表"""
pipeline_uuid = quart.request.args.get('pipeline_uuid') or quart.request.args.get('pipeline_id')
bound_plugins: list[str] | None = None
bound_mcp_servers: list[str] | None = None
catalog = await self._get_scoped_tool_catalog()
if catalog is None:
return self.http_status(404, -1, 'pipeline not found')
return self.success(data={'tools': catalog})
if pipeline_uuid:
pipeline = await self.ap.pipeline_service.get_pipeline(pipeline_uuid)
if pipeline is None:
return self.http_status(404, -1, 'pipeline not found')
extensions_prefs = pipeline.get('extensions_preferences', {}) or {}
if not extensions_prefs.get('enable_all_plugins', True):
bound_plugins = [
f'{plugin.get("author", "")}/{plugin.get("name", "")}'
for plugin in extensions_prefs.get('plugins', [])
if isinstance(plugin, dict) and plugin.get('name')
]
if not extensions_prefs.get('enable_all_mcp_servers', True):
bound_mcp_servers = [
server for server in (extensions_prefs.get('mcp_servers', []) or []) if isinstance(server, str)
]
return self.success(
data={
'tools': await self.ap.tool_mgr.get_tool_catalog(
bound_plugins,
bound_mcp_servers,
include_skill_authoring=True,
)
}
)
@self.route('/<tool_name>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
@self.route('/<path:tool_name>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _(tool_name: str) -> str:
"""获取特定工具详情"""
tools = await self.ap.tool_mgr.get_all_tools()
catalog = await self._get_scoped_tool_catalog()
if catalog is None:
return self.http_status(404, -1, 'pipeline not found')
for tool in tools:
if tool.name == tool_name:
for tool in catalog:
if tool.get('name') == tool_name:
return self.success(
data={
'tool': {
'name': tool.name,
'description': tool.description,
'human_desc': tool.human_desc,
'parameters': tool.parameters,
'name': tool['name'],
'description': tool.get('description') or '',
'human_desc': tool.get('human_desc') or '',
'parameters': tool.get('parameters') or {},
'source': tool.get('source'),
'source_name': tool.get('source_name'),
'source_id': tool.get('source_id'),
}
}
)