a2a-pack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- a2a_pack/__init__.py +144 -0
- a2a_pack/a2a_client.py +170 -0
- a2a_pack/agent.py +443 -0
- a2a_pack/auth.py +35 -0
- a2a_pack/card.py +88 -0
- a2a_pack/cli/__init__.py +1 -0
- a2a_pack/cli/api_client.py +129 -0
- a2a_pack/cli/credentials.py +68 -0
- a2a_pack/cli/loader.py +34 -0
- a2a_pack/cli/main.py +478 -0
- a2a_pack/cli/manifests.py +133 -0
- a2a_pack/cli/templates/Dockerfile.tmpl +14 -0
- a2a_pack/cli/templates/a2a.yaml.tmpl +8 -0
- a2a_pack/cli/templates/agent.py.tmpl +24 -0
- a2a_pack/cli/templates/deployment.yaml.tmpl +77 -0
- a2a_pack/cli/templates/dockerignore.tmpl +7 -0
- a2a_pack/cli/templates/requirements.txt.tmpl +1 -0
- a2a_pack/cli/templates/workflow.yml.tmpl +35 -0
- a2a_pack/context.py +521 -0
- a2a_pack/discovery.py +176 -0
- a2a_pack/grants.py +148 -0
- a2a_pack/mcp/__init__.py +28 -0
- a2a_pack/mcp/http.py +69 -0
- a2a_pack/mcp/server.py +241 -0
- a2a_pack/runtime.py +134 -0
- a2a_pack/sandbox.py +174 -0
- a2a_pack/serve/__init__.py +3 -0
- a2a_pack/serve/asgi.py +312 -0
- a2a_pack/workspace.py +528 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/Dockerfile.tmpl +14 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/a2a.yaml.tmpl +8 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/agent.py.tmpl +24 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/deployment.yaml.tmpl +77 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/dockerignore.tmpl +7 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/requirements.txt.tmpl +1 -0
- a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/workflow.yml.tmpl +35 -0
- a2a_pack-0.1.0.dist-info/METADATA +143 -0
- a2a_pack-0.1.0.dist-info/RECORD +41 -0
- a2a_pack-0.1.0.dist-info/WHEEL +4 -0
- a2a_pack-0.1.0.dist-info/entry_points.txt +2 -0
- a2a_pack-0.1.0.dist-info/licenses/LICENSE +21 -0
a2a_pack/__init__.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""a2a-pack — developer SDK + CLI for the a2a cloud platform.
|
|
2
|
+
|
|
3
|
+
See https://docs.a2acloud.io for the full reference.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Single source of truth — pyproject.toml reads this via hatch.version.
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .a2a_client import (
|
|
10
|
+
A2AClient,
|
|
11
|
+
CallResult,
|
|
12
|
+
HttpA2AClient,
|
|
13
|
+
InMemoryA2AClient,
|
|
14
|
+
)
|
|
15
|
+
from .agent import (
|
|
16
|
+
A2AAgent,
|
|
17
|
+
ParamSpec,
|
|
18
|
+
SkillInputError,
|
|
19
|
+
SkillInvocationError,
|
|
20
|
+
SkillNotFound,
|
|
21
|
+
SkillSpec,
|
|
22
|
+
skill,
|
|
23
|
+
)
|
|
24
|
+
from .discovery import (
|
|
25
|
+
ControlPlaneDiscovery,
|
|
26
|
+
DiscoveredAgent,
|
|
27
|
+
DiscoveryClient,
|
|
28
|
+
InMemoryDiscovery,
|
|
29
|
+
)
|
|
30
|
+
from .grants import Grant, GrantInvalid, mint_grant, sign_grant, verify_grant
|
|
31
|
+
from .mcp import (
|
|
32
|
+
MCP_PROTOCOL_VERSION,
|
|
33
|
+
MCPServer,
|
|
34
|
+
build_http_app as build_mcp_http_app,
|
|
35
|
+
mount_http as mount_mcp_http,
|
|
36
|
+
skills_to_tools,
|
|
37
|
+
)
|
|
38
|
+
from .auth import APIKeyAuth, JWTAuth, NoAuth
|
|
39
|
+
from .card import AgentCard, SkillCard
|
|
40
|
+
from .context import (
|
|
41
|
+
AgentEvent,
|
|
42
|
+
ArtifactRef,
|
|
43
|
+
CancelledByCaller,
|
|
44
|
+
LLMCreds,
|
|
45
|
+
LocalRunContext,
|
|
46
|
+
MissingScopes,
|
|
47
|
+
RunContext,
|
|
48
|
+
)
|
|
49
|
+
from .runtime import (
|
|
50
|
+
AgentRuntime,
|
|
51
|
+
EgressPolicy,
|
|
52
|
+
Lifecycle,
|
|
53
|
+
LLMProvisioning,
|
|
54
|
+
Pricing,
|
|
55
|
+
Resources,
|
|
56
|
+
Sandbox,
|
|
57
|
+
SkillPolicy,
|
|
58
|
+
State,
|
|
59
|
+
)
|
|
60
|
+
from .sandbox import (
|
|
61
|
+
ExecResult,
|
|
62
|
+
SandboxClient,
|
|
63
|
+
SandboxHandle,
|
|
64
|
+
SandboxSpec,
|
|
65
|
+
SandboxUnavailable,
|
|
66
|
+
)
|
|
67
|
+
from .workspace import (
|
|
68
|
+
FileMatch,
|
|
69
|
+
FileType,
|
|
70
|
+
LocalWorkspaceClient,
|
|
71
|
+
LocalWorkspaceView,
|
|
72
|
+
WorkspaceAccess,
|
|
73
|
+
WorkspaceClient,
|
|
74
|
+
WorkspaceDenied,
|
|
75
|
+
WorkspaceGrant,
|
|
76
|
+
WorkspaceMode,
|
|
77
|
+
WorkspacePatch,
|
|
78
|
+
WorkspaceView,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
"A2AAgent",
|
|
83
|
+
"A2AClient",
|
|
84
|
+
"APIKeyAuth",
|
|
85
|
+
"AgentCard",
|
|
86
|
+
"AgentEvent",
|
|
87
|
+
"AgentRuntime",
|
|
88
|
+
"ArtifactRef",
|
|
89
|
+
"CallResult",
|
|
90
|
+
"CancelledByCaller",
|
|
91
|
+
"ControlPlaneDiscovery",
|
|
92
|
+
"DiscoveredAgent",
|
|
93
|
+
"DiscoveryClient",
|
|
94
|
+
"EgressPolicy",
|
|
95
|
+
"ExecResult",
|
|
96
|
+
"FileMatch",
|
|
97
|
+
"FileType",
|
|
98
|
+
"Grant",
|
|
99
|
+
"GrantInvalid",
|
|
100
|
+
"HttpA2AClient",
|
|
101
|
+
"InMemoryA2AClient",
|
|
102
|
+
"InMemoryDiscovery",
|
|
103
|
+
"JWTAuth",
|
|
104
|
+
"LLMCreds",
|
|
105
|
+
"LLMProvisioning",
|
|
106
|
+
"Lifecycle",
|
|
107
|
+
"LocalRunContext",
|
|
108
|
+
"MCP_PROTOCOL_VERSION",
|
|
109
|
+
"MCPServer",
|
|
110
|
+
"build_mcp_http_app",
|
|
111
|
+
"mount_mcp_http",
|
|
112
|
+
"skills_to_tools",
|
|
113
|
+
"LocalWorkspaceClient",
|
|
114
|
+
"LocalWorkspaceView",
|
|
115
|
+
"MissingScopes",
|
|
116
|
+
"NoAuth",
|
|
117
|
+
"ParamSpec",
|
|
118
|
+
"Pricing",
|
|
119
|
+
"Resources",
|
|
120
|
+
"RunContext",
|
|
121
|
+
"Sandbox",
|
|
122
|
+
"mint_grant",
|
|
123
|
+
"sign_grant",
|
|
124
|
+
"verify_grant",
|
|
125
|
+
"SandboxClient",
|
|
126
|
+
"SandboxHandle",
|
|
127
|
+
"SandboxSpec",
|
|
128
|
+
"SandboxUnavailable",
|
|
129
|
+
"SkillCard",
|
|
130
|
+
"SkillInputError",
|
|
131
|
+
"SkillInvocationError",
|
|
132
|
+
"SkillNotFound",
|
|
133
|
+
"SkillPolicy",
|
|
134
|
+
"SkillSpec",
|
|
135
|
+
"State",
|
|
136
|
+
"WorkspaceAccess",
|
|
137
|
+
"WorkspaceClient",
|
|
138
|
+
"WorkspaceDenied",
|
|
139
|
+
"WorkspaceGrant",
|
|
140
|
+
"WorkspaceMode",
|
|
141
|
+
"WorkspacePatch",
|
|
142
|
+
"WorkspaceView",
|
|
143
|
+
"skill",
|
|
144
|
+
]
|
a2a_pack/a2a_client.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Agent-to-agent invocation surface available via ``ctx.call(...)``.
|
|
2
|
+
|
|
3
|
+
An agent never speaks raw HTTP to another agent. It calls
|
|
4
|
+
``ctx.call(target, skill, args, grant=...)`` and the runtime-attached
|
|
5
|
+
:class:`A2AClient` handles transport: HTTP for cross-pod, in-memory for
|
|
6
|
+
local tests, anything else (gRPC, message bus) for future runtimes.
|
|
7
|
+
|
|
8
|
+
The grant token (see :mod:`a2a_pack.grants`) is the *only* way to hand
|
|
9
|
+
workspace access across agents. Callee-side runtime validates it before
|
|
10
|
+
materializing a :class:`WorkspaceClient`.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .agent import A2AAgent
|
|
20
|
+
from .context import RunContext
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class CallResult:
|
|
25
|
+
"""What an A2A invocation returns to the calling skill."""
|
|
26
|
+
|
|
27
|
+
result: Any
|
|
28
|
+
events: tuple[dict[str, Any], ...] = ()
|
|
29
|
+
artifacts: tuple[dict[str, Any], ...] = ()
|
|
30
|
+
grant_id: str | None = None # echoed for audit
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class A2AClient(ABC):
|
|
34
|
+
"""Transport-shaped agent-to-agent client."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def call(
|
|
38
|
+
self,
|
|
39
|
+
target: str,
|
|
40
|
+
skill: str,
|
|
41
|
+
*,
|
|
42
|
+
args: dict[str, Any] | None = None,
|
|
43
|
+
grant: str | None = None,
|
|
44
|
+
timeout: float | None = None,
|
|
45
|
+
) -> CallResult:
|
|
46
|
+
"""Invoke ``skill`` on ``target`` and return its :class:`CallResult`.
|
|
47
|
+
|
|
48
|
+
``target`` is opaque to this layer — for the HTTP impl it's an agent
|
|
49
|
+
URL; for the in-memory impl it's an agent name.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# In-memory: routes calls to A2AAgent instances in the same process. Useful
|
|
55
|
+
# for the demo + tests.
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class InMemoryA2AClient(A2AClient):
|
|
61
|
+
"""Routes calls to agent instances registered by name.
|
|
62
|
+
|
|
63
|
+
The receiving agent gets a *new* :class:`RunContext` built by the
|
|
64
|
+
``ctx_factory`` callable, so caller and callee don't share state.
|
|
65
|
+
Pass ``ctx_factory=lambda agent, grant: ...`` to control how scoped
|
|
66
|
+
workspaces / sandboxes are wired in.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
agents: dict[str, "A2AAgent"]
|
|
70
|
+
ctx_factory: Any = None # Callable[[A2AAgent, str | None], RunContext]
|
|
71
|
+
|
|
72
|
+
async def call(
|
|
73
|
+
self,
|
|
74
|
+
target: str,
|
|
75
|
+
skill: str,
|
|
76
|
+
*,
|
|
77
|
+
args: dict[str, Any] | None = None,
|
|
78
|
+
grant: str | None = None,
|
|
79
|
+
timeout: float | None = None,
|
|
80
|
+
) -> CallResult:
|
|
81
|
+
if target not in self.agents:
|
|
82
|
+
raise KeyError(f"no agent registered: {target!r}")
|
|
83
|
+
agent = self.agents[target]
|
|
84
|
+
ctx = self.ctx_factory(agent, grant) if self.ctx_factory else None
|
|
85
|
+
if ctx is None:
|
|
86
|
+
from .context import LocalRunContext
|
|
87
|
+
from .auth import NoAuth
|
|
88
|
+
|
|
89
|
+
ctx = LocalRunContext(auth=NoAuth(), task_id=f"a2a-{target}")
|
|
90
|
+
result = await agent.invoke_json(skill, ctx, args or {})
|
|
91
|
+
events = tuple(
|
|
92
|
+
{"kind": e.kind, "payload": e.payload}
|
|
93
|
+
for e in getattr(ctx, "events", ())
|
|
94
|
+
)
|
|
95
|
+
# surface artifacts captured by LocalRunContext, if present
|
|
96
|
+
artifacts: tuple[dict[str, Any], ...] = ()
|
|
97
|
+
local_arts = getattr(ctx, "artifacts", None)
|
|
98
|
+
if isinstance(local_arts, dict):
|
|
99
|
+
artifacts = tuple(
|
|
100
|
+
{"name": name, "size_bytes": len(data)}
|
|
101
|
+
for name, data in local_arts.items()
|
|
102
|
+
)
|
|
103
|
+
return CallResult(
|
|
104
|
+
result=result,
|
|
105
|
+
events=events,
|
|
106
|
+
artifacts=artifacts,
|
|
107
|
+
grant_id=_grant_id_or_none(grant),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# HTTP: posts to <target>/invoke/<skill> with {arguments, grant} body.
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class HttpA2AClient(A2AClient):
|
|
118
|
+
"""A2A client that POSTs to the standard /invoke/{skill} endpoint."""
|
|
119
|
+
|
|
120
|
+
default_timeout: float = 60.0
|
|
121
|
+
|
|
122
|
+
async def call(
|
|
123
|
+
self,
|
|
124
|
+
target: str,
|
|
125
|
+
skill: str,
|
|
126
|
+
*,
|
|
127
|
+
args: dict[str, Any] | None = None,
|
|
128
|
+
grant: str | None = None,
|
|
129
|
+
timeout: float | None = None,
|
|
130
|
+
) -> CallResult:
|
|
131
|
+
import httpx # late import: server-side needs no client
|
|
132
|
+
|
|
133
|
+
body: dict[str, Any] = {"arguments": args or {}}
|
|
134
|
+
if grant is not None:
|
|
135
|
+
body["grant"] = grant
|
|
136
|
+
url = f"{target.rstrip('/')}/invoke/{skill}"
|
|
137
|
+
async with httpx.AsyncClient(timeout=timeout or self.default_timeout) as c:
|
|
138
|
+
resp = await c.post(url, json=body)
|
|
139
|
+
if resp.status_code >= 400:
|
|
140
|
+
raise RuntimeError(f"a2a {url} -> {resp.status_code}: {resp.text}")
|
|
141
|
+
data = resp.json()
|
|
142
|
+
return CallResult(
|
|
143
|
+
result=data.get("result"),
|
|
144
|
+
events=tuple(data.get("events") or ()),
|
|
145
|
+
artifacts=tuple(data.get("artifacts") or ()),
|
|
146
|
+
grant_id=_grant_id_or_none(grant),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _grant_id_or_none(grant: str | None) -> str | None:
|
|
151
|
+
"""Extract grant_id without re-validating the signature (audit only)."""
|
|
152
|
+
if not grant or "." not in grant:
|
|
153
|
+
return None
|
|
154
|
+
try:
|
|
155
|
+
from .grants import _b64decode
|
|
156
|
+
|
|
157
|
+
payload = _b64decode(grant.rsplit(".", 1)[0])
|
|
158
|
+
import json
|
|
159
|
+
|
|
160
|
+
return json.loads(payload).get("grant_id")
|
|
161
|
+
except Exception: # noqa: BLE001
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = [
|
|
166
|
+
"A2AClient",
|
|
167
|
+
"CallResult",
|
|
168
|
+
"HttpA2AClient",
|
|
169
|
+
"InMemoryA2AClient",
|
|
170
|
+
]
|