ship grants, a2a_client, discovery, sandbox SDK + tests
This commit is contained in:
@@ -13,6 +13,9 @@ from typing import Any, Generic, Sequence, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .a2a_client import A2AClient, CallResult
|
||||
from .discovery import DiscoveryClient
|
||||
from .sandbox import SandboxClient, SandboxUnavailable
|
||||
from .workspace import WorkspaceClient
|
||||
|
||||
AuthT = TypeVar("AuthT", bound=BaseModel)
|
||||
@@ -88,6 +91,43 @@ class RunContext(ABC, Generic[AuthT]):
|
||||
Raises if the agent's :attr:`A2AAgent.workspace_access` is disabled.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def sandbox(self) -> SandboxClient:
|
||||
"""Code-execution surface (microsandbox-backed by default).
|
||||
|
||||
Raises :class:`SandboxUnavailable` if the runtime did not attach a
|
||||
sandbox client to this context (e.g. local dev with no host daemon).
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def discover(self) -> DiscoveryClient:
|
||||
"""Registry-backed discovery: find other agents by tag/capability/skill."""
|
||||
|
||||
async def call(
|
||||
self,
|
||||
target: str,
|
||||
skill: str,
|
||||
*,
|
||||
args: dict[str, Any] | None = None,
|
||||
grant: str | None = None,
|
||||
timeout: float | None = None,
|
||||
) -> CallResult:
|
||||
"""Invoke another agent's skill via the runtime's :class:`A2AClient`.
|
||||
|
||||
``target`` is whatever the underlying client expects — an HTTP URL
|
||||
for :class:`HttpA2AClient`, an agent name for in-process routing.
|
||||
Pair with :meth:`WorkspaceClient.delegate` to hand a scoped
|
||||
workspace grant to the callee.
|
||||
"""
|
||||
client = self._a2a_client()
|
||||
return await client.call(target, skill, args=args, grant=grant, timeout=timeout)
|
||||
|
||||
@abstractmethod
|
||||
def _a2a_client(self) -> A2AClient:
|
||||
"""Return the runtime's outbound A2A client (or raise if absent)."""
|
||||
|
||||
# --- concrete helpers built on emit_event ---
|
||||
|
||||
async def emit_progress(self, message: str) -> None:
|
||||
@@ -146,11 +186,17 @@ class LocalRunContext(RunContext[AuthT]):
|
||||
task_id: str = "local-task",
|
||||
secrets: dict[str, str] | None = None,
|
||||
workspace: WorkspaceClient | None = None,
|
||||
sandbox: SandboxClient | None = None,
|
||||
a2a: A2AClient | None = None,
|
||||
discover: DiscoveryClient | None = None,
|
||||
) -> None:
|
||||
self.task_id = task_id
|
||||
self.auth = auth
|
||||
self._secrets: dict[str, str] = dict(secrets or {})
|
||||
self._workspace = workspace
|
||||
self._sandbox = sandbox
|
||||
self._a2a = a2a
|
||||
self._discover = discover
|
||||
self._cancel = asyncio.Event()
|
||||
self.events: list[AgentEvent] = []
|
||||
self.artifacts: dict[str, bytes] = {}
|
||||
@@ -164,6 +210,31 @@ class LocalRunContext(RunContext[AuthT]):
|
||||
)
|
||||
return self._workspace
|
||||
|
||||
@property
|
||||
def sandbox(self) -> SandboxClient:
|
||||
if self._sandbox is None:
|
||||
raise SandboxUnavailable(
|
||||
"no sandbox client attached to this context; "
|
||||
"the runtime layer must provision one"
|
||||
)
|
||||
return self._sandbox
|
||||
|
||||
@property
|
||||
def discover(self) -> DiscoveryClient:
|
||||
if self._discover is None:
|
||||
raise PermissionError(
|
||||
"no discovery client attached; runtime must provision one"
|
||||
)
|
||||
return self._discover
|
||||
|
||||
def _a2a_client(self) -> A2AClient:
|
||||
if self._a2a is None:
|
||||
raise PermissionError(
|
||||
"no A2A client attached; runtime must provision one before "
|
||||
"ctx.call(...) can be used"
|
||||
)
|
||||
return self._a2a
|
||||
|
||||
async def emit_event(self, event: AgentEvent) -> None:
|
||||
self.events.append(event)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user