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.
Files changed (41) hide show
  1. a2a_pack/__init__.py +144 -0
  2. a2a_pack/a2a_client.py +170 -0
  3. a2a_pack/agent.py +443 -0
  4. a2a_pack/auth.py +35 -0
  5. a2a_pack/card.py +88 -0
  6. a2a_pack/cli/__init__.py +1 -0
  7. a2a_pack/cli/api_client.py +129 -0
  8. a2a_pack/cli/credentials.py +68 -0
  9. a2a_pack/cli/loader.py +34 -0
  10. a2a_pack/cli/main.py +478 -0
  11. a2a_pack/cli/manifests.py +133 -0
  12. a2a_pack/cli/templates/Dockerfile.tmpl +14 -0
  13. a2a_pack/cli/templates/a2a.yaml.tmpl +8 -0
  14. a2a_pack/cli/templates/agent.py.tmpl +24 -0
  15. a2a_pack/cli/templates/deployment.yaml.tmpl +77 -0
  16. a2a_pack/cli/templates/dockerignore.tmpl +7 -0
  17. a2a_pack/cli/templates/requirements.txt.tmpl +1 -0
  18. a2a_pack/cli/templates/workflow.yml.tmpl +35 -0
  19. a2a_pack/context.py +521 -0
  20. a2a_pack/discovery.py +176 -0
  21. a2a_pack/grants.py +148 -0
  22. a2a_pack/mcp/__init__.py +28 -0
  23. a2a_pack/mcp/http.py +69 -0
  24. a2a_pack/mcp/server.py +241 -0
  25. a2a_pack/runtime.py +134 -0
  26. a2a_pack/sandbox.py +174 -0
  27. a2a_pack/serve/__init__.py +3 -0
  28. a2a_pack/serve/asgi.py +312 -0
  29. a2a_pack/workspace.py +528 -0
  30. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/Dockerfile.tmpl +14 -0
  31. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/a2a.yaml.tmpl +8 -0
  32. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/agent.py.tmpl +24 -0
  33. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/deployment.yaml.tmpl +77 -0
  34. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/dockerignore.tmpl +7 -0
  35. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/requirements.txt.tmpl +1 -0
  36. a2a_pack-0.1.0.data/data/a2a_pack/cli/templates/workflow.yml.tmpl +35 -0
  37. a2a_pack-0.1.0.dist-info/METADATA +143 -0
  38. a2a_pack-0.1.0.dist-info/RECORD +41 -0
  39. a2a_pack-0.1.0.dist-info/WHEEL +4 -0
  40. a2a_pack-0.1.0.dist-info/entry_points.txt +2 -0
  41. 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
+ ]