mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 17:05:28 +08:00
feat: delegate sandbox policy to runners and simplify pipeline migration
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""Authenticated plugin Box resources and invocation file operations."""
|
||||
|
||||
from langbot_plugin.entities.io.actions.enums import PluginToRuntimeAction as Action
|
||||
from langbot_plugin.runtime.io.handler import ActionResponse
|
||||
from ..box.runner import RunnerBoxService
|
||||
from .agent_run_support import _validate_agent_run_session
|
||||
|
||||
|
||||
def register(h):
|
||||
async def dispatch(action, data):
|
||||
from .handler import _resolve_action_query
|
||||
|
||||
action_context, _ = await h._require_plugin_action_context()
|
||||
context = h._execution_context(action_context)
|
||||
query = None
|
||||
run_id = data.get('run_id')
|
||||
context_actions = {Action.BIND_BOX, Action.IMPORT_BOX_ATTACHMENTS, Action.EXPORT_BOX_FILES}
|
||||
if action in context_actions and not run_id:
|
||||
return ActionResponse.error('run_id is required for Box context operations')
|
||||
if run_id:
|
||||
session, error = await _validate_agent_run_session(
|
||||
run_id,
|
||||
data.get('caller_plugin_identity'),
|
||||
h.ap,
|
||||
'Box API',
|
||||
api_capability='box',
|
||||
)
|
||||
if error:
|
||||
return error
|
||||
query = _resolve_action_query(data, session, h.ap, action_context)
|
||||
if query is None:
|
||||
return ActionResponse.error('The current execution context has expired')
|
||||
service = RunnerBoxService(h.ap.box_service)
|
||||
if action == Action.GET_BOX_STATUS:
|
||||
return ActionResponse.success(await service.status(context))
|
||||
if action == Action.LIST_BOXES:
|
||||
return ActionResponse.success(
|
||||
{'items': [service.public_session(s) for s in await service.sessions(context)]}
|
||||
)
|
||||
if action == Action.ACQUIRE_BOX:
|
||||
request = {k: v for k, v in data.items() if k not in {'run_id', 'caller_plugin_identity'}}
|
||||
return ActionResponse.success(await service.acquire(context, request, query))
|
||||
if action == Action.BIND_BOX:
|
||||
return ActionResponse.success(await service.bind(context, query, run_id, data['box_id']))
|
||||
if action == Action.IMPORT_BOX_ATTACHMENTS:
|
||||
return ActionResponse.success(await service.import_attachments(query, data.get('attachment_ids')))
|
||||
if action == Action.EXPORT_BOX_FILES:
|
||||
return ActionResponse.success(await service.export_files(query))
|
||||
raise ValueError('Unsupported Box action')
|
||||
|
||||
def register_action(action):
|
||||
@h.action(action)
|
||||
async def invoke(data):
|
||||
try:
|
||||
return await dispatch(action, data)
|
||||
except Exception as exc:
|
||||
return ActionResponse.error(f'{type(exc).__name__}: {exc}')
|
||||
|
||||
for action in (
|
||||
Action.GET_BOX_STATUS,
|
||||
Action.LIST_BOXES,
|
||||
Action.ACQUIRE_BOX,
|
||||
Action.BIND_BOX,
|
||||
Action.IMPORT_BOX_ATTACHMENTS,
|
||||
Action.EXPORT_BOX_FILES,
|
||||
):
|
||||
register_action(action)
|
||||
@@ -1631,6 +1631,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
plugin_author: str,
|
||||
plugin_name: str,
|
||||
task_context: taskmgr.TaskContext | None,
|
||||
version: str | None = None,
|
||||
) -> tuple[bytes | None, str | None]:
|
||||
"""Return a plugin package, or install an MCP/skill and return none."""
|
||||
|
||||
@@ -1640,6 +1641,22 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
timeout=15,
|
||||
event_hooks=httpclient.httpx_response_limit_hooks(_MARKETPLACE_PLUGIN_DOWNLOAD_MAX_BYTES),
|
||||
) as client:
|
||||
if version is not None:
|
||||
if (
|
||||
not isinstance(version, str)
|
||||
or not version
|
||||
or any(
|
||||
c not in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._+-' for c in version
|
||||
)
|
||||
):
|
||||
raise ValueError('Invalid plugin version')
|
||||
_status, package = await _marketplace_get(
|
||||
client,
|
||||
f'{space_url}/api/v1/marketplace/plugins/download/{plugin_author}/{plugin_name}/{version}',
|
||||
max_bytes=_MARKETPLACE_PLUGIN_DOWNLOAD_MAX_BYTES,
|
||||
)
|
||||
return package, version
|
||||
|
||||
mcp_status, mcp_body = await _marketplace_get(
|
||||
client,
|
||||
f'{space_url}/api/v1/marketplace/mcps/{plugin_author}/{plugin_name}',
|
||||
@@ -1726,11 +1743,13 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
if task_context is not None:
|
||||
task_context.set_current_action('downloading plugin package')
|
||||
task_context.metadata['progress_percent'] = 15
|
||||
version_options = {'version': install_info['plugin_version']} if install_info.get('plugin_version') else {}
|
||||
file_bytes, version = await self._download_marketplace_package(
|
||||
execution_context,
|
||||
plugin_author,
|
||||
plugin_name,
|
||||
task_context,
|
||||
**version_options,
|
||||
)
|
||||
if file_bytes is None:
|
||||
return
|
||||
|
||||
@@ -1188,7 +1188,18 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
)
|
||||
if error:
|
||||
return error
|
||||
output_lock = None
|
||||
output_lock_acquired = False
|
||||
try:
|
||||
if data.get('file_ids') is not None:
|
||||
from ..box.runner import exported_message, binding_for
|
||||
|
||||
query = _resolve_action_query(data, session, self.ap, action_context)
|
||||
output_lock = binding_for(query).lock
|
||||
await output_lock.acquire()
|
||||
output_lock_acquired = True
|
||||
files = exported_message(query, data['file_ids'])
|
||||
data = {**data, 'params': {**(data.get('params') or {}), 'message': files.model_dump(mode='json')}}
|
||||
tool_name, parameters, message = resolve_platform_api_call(
|
||||
session, data.get('bot_uuid'), data['action'], data.get('params') or {}, data.get('context_tool')
|
||||
)
|
||||
@@ -1208,9 +1219,14 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
parameters,
|
||||
message_chain=message,
|
||||
)
|
||||
if data.get('file_ids') is not None:
|
||||
exported_message(query, data['file_ids'], consume=True)
|
||||
return handler.ActionResponse.success(data={'result': _serialize_plugin_api_result(result)})
|
||||
except (ValueError, KeyError, TypeError) as exc:
|
||||
return handler.ActionResponse.error(message=str(exc))
|
||||
finally:
|
||||
if output_lock_acquired:
|
||||
output_lock.release()
|
||||
|
||||
@self.action(PluginToRuntimeAction.SEND_MESSAGE)
|
||||
async def send_message(data: dict[str, Any]) -> handler.ActionResponse:
|
||||
@@ -2634,6 +2650,9 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
|
||||
agent_pull_actions.register(self)
|
||||
runner_actions.register(self)
|
||||
from . import box_actions
|
||||
|
||||
box_actions.register(self)
|
||||
agent_state_actions.register(self)
|
||||
|
||||
@self.action(CommonAction.PING)
|
||||
|
||||
Reference in New Issue
Block a user