feat: command execution via plugin

This commit is contained in:
Junyan Qin
2025-07-13 10:26:48 +08:00
parent 10a44c70b6
commit 5922be7e15
4 changed files with 72 additions and 27 deletions

View File

@@ -56,3 +56,24 @@ class SystemRouterGroup(group.RouterGroup):
return self.success(
data=await self.ap.tool_mgr.execute_func_call(data['tool_name'], data['tool_parameters'])
)
@self.route('/debug/plugin/action', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
if not constants.debug_mode:
return self.http_status(403, 403, 'Forbidden')
data = await quart.request.json
class AnoymousAction:
value = 'anonymous_action'
def __init__(self, value: str):
self.value = value
resp = await self.ap.plugin_connector.handler.call_action(
AnoymousAction(data['action']),
data['data'],
timeout=data.get('timeout', 10),
)
return self.success(data=resp)

View File

@@ -16,13 +16,11 @@ importutil.import_modules_in_pkg(operators)
class CommandManager:
"""命令管理器"""
ap: app.Application
cmd_list: list[operator.CommandOperator]
"""
运行时命令列表,扁平存储,各个对象包含对应的子节点引用
Runtime command list, flat storage, each object contains a reference to the corresponding child node
"""
def __init__(self, ap: app.Application):
@@ -64,30 +62,15 @@ class CommandManager:
) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
"""执行命令"""
found = False
if len(context.crt_params) > 0: # 查找下一个参数是否对应此节点的某个子节点名
for oper in operator_list:
if (context.crt_params[0] == oper.name or context.crt_params[0] in oper.alias) and (
oper.parent_class is None or oper.parent_class == operator.__class__
):
found = True
command_list = await self.ap.plugin_connector.list_commands()
context.crt_command = context.crt_params[0]
context.crt_params = context.crt_params[1:]
async for ret in self._execute(context, oper.children, oper):
yield ret
break
if not found: # 如果下一个参数未在此节点的子节点中找到,则执行此节点或者报错
if operator is None:
yield command_context.CommandReturn(error=command_errors.CommandNotFoundError(context.crt_params[0]))
else:
if operator.lowest_privilege > context.privilege:
yield command_context.CommandReturn(error=command_errors.CommandPrivilegeError(operator.name))
else:
async for ret in operator.execute(context):
yield ret
for command in command_list:
if command.metadata.name == context.command:
async for ret in self.ap.plugin_connector.execute_command(context):
yield ret
break
else:
yield command_context.CommandReturn(error=command_errors.CommandNotFoundError(context.command))
async def execute(
self,
@@ -103,7 +86,6 @@ class CommandManager:
privilege = 2
ctx = command_context.ExecuteContext(
query=query,
session=session,
command_text=command_text,
command='',
@@ -113,5 +95,9 @@ class CommandManager:
privilege=privilege,
)
ctx.command = ctx.params[0]
ctx.shift()
async for ret in self._execute(ctx, self.cmd_list):
yield ret

View File

@@ -16,6 +16,7 @@ from langbot_plugin.api.entities import events
from langbot_plugin.api.entities import context
import langbot_plugin.runtime.io.connection as base_connection
from langbot_plugin.api.definition.components.manifest import ComponentManifest
from langbot_plugin.api.entities.builtin.command import context as command_context
class PluginRuntimeConnector:
@@ -118,3 +119,18 @@ class PluginRuntimeConnector:
async def call_tool(self, tool_name: str, parameters: dict[str, Any]) -> dict[str, Any]:
return await self.handler.call_tool(tool_name, parameters)
async def list_commands(self) -> list[ComponentManifest]:
list_commands_data = await self.handler.list_commands()
return [ComponentManifest.model_validate(command) for command in list_commands_data]
async def execute_command(
self, command_ctx: command_context.ExecuteContext
) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
gen = self.handler.execute_command(command_ctx.model_dump(serialize_as_any=True))
async for ret in gen:
cmd_ret = command_context.CommandReturn.model_validate(ret)
yield cmd_ret

View File

@@ -117,3 +117,25 @@ class RuntimeConnectionHandler(handler.Handler):
)
return result['tool_response']
async def list_commands(self) -> list[dict[str, Any]]:
"""List commands"""
result = await self.call_action(
LangBotToRuntimeAction.LIST_COMMANDS,
{},
timeout=10,
)
return result['commands']
async def execute_command(self, command_context: dict[str, Any]) -> typing.AsyncGenerator[dict[str, Any], None]:
"""Execute command"""
gen = self.call_action_generator(
LangBotToRuntimeAction.EXECUTE_COMMAND,
{
'command_context': command_context,
},
timeout=10,
)
async for ret in gen:
yield ret