a2al 0.1.0__tar.gz
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.
- a2al-0.1.0/PKG-INFO +64 -0
- a2al-0.1.0/README.md +41 -0
- a2al-0.1.0/pyproject.toml +36 -0
- a2al-0.1.0/setup.cfg +4 -0
- a2al-0.1.0/src/a2al/__init__.py +5 -0
- a2al-0.1.0/src/a2al/_sidecar.py +187 -0
- a2al-0.1.0/src/a2al.egg-info/PKG-INFO +64 -0
- a2al-0.1.0/src/a2al.egg-info/SOURCES.txt +8 -0
- a2al-0.1.0/src/a2al.egg-info/dependency_links.txt +1 -0
- a2al-0.1.0/src/a2al.egg-info/top_level.txt +1 -0
a2al-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a2al
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for a2al: spawn a2ald and call its REST API
|
|
5
|
+
Author: A2AL Authors
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/a2al/a2al
|
|
8
|
+
Project-URL: Repository, https://github.com/a2al/a2al
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/a2al/a2al/issues
|
|
10
|
+
Keywords: a2al,a2a,agent,ai,sidecar
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# a2al (Python)
|
|
25
|
+
|
|
26
|
+
Python client for [a2al](https://github.com/a2al/a2al): spawns a local `a2ald` daemon and exposes a typed REST client for its API.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install a2al
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pre-built `a2ald` binaries are bundled inside platform wheels for:
|
|
35
|
+
|
|
36
|
+
| Platform | Architecture |
|
|
37
|
+
|----------|-------------|
|
|
38
|
+
| Linux | x86_64, arm64 |
|
|
39
|
+
| macOS | x86_64, arm64 |
|
|
40
|
+
| Windows | x86_64 |
|
|
41
|
+
|
|
42
|
+
On unsupported platforms, install `a2ald` manually and ensure it is on `PATH`, or set `A2ALD_PATH` to the executable path.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from a2al import Daemon, Client
|
|
48
|
+
|
|
49
|
+
with Daemon() as d:
|
|
50
|
+
c = Client(d.api_base, token=d.api_token)
|
|
51
|
+
print(c.health())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Environment Variables
|
|
55
|
+
|
|
56
|
+
| Variable | Description |
|
|
57
|
+
|----------|-------------|
|
|
58
|
+
| `A2ALD_PATH` | Override path to the `a2ald` executable |
|
|
59
|
+
| `A2AL_API_TOKEN` | Bearer token when the daemon enforces auth |
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
- Python 3.10+
|
|
64
|
+
- No third-party dependencies (standard library only)
|
a2al-0.1.0/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# a2al (Python)
|
|
2
|
+
|
|
3
|
+
Python client for [a2al](https://github.com/a2al/a2al): spawns a local `a2ald` daemon and exposes a typed REST client for its API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install a2al
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Pre-built `a2ald` binaries are bundled inside platform wheels for:
|
|
12
|
+
|
|
13
|
+
| Platform | Architecture |
|
|
14
|
+
|----------|-------------|
|
|
15
|
+
| Linux | x86_64, arm64 |
|
|
16
|
+
| macOS | x86_64, arm64 |
|
|
17
|
+
| Windows | x86_64 |
|
|
18
|
+
|
|
19
|
+
On unsupported platforms, install `a2ald` manually and ensure it is on `PATH`, or set `A2ALD_PATH` to the executable path.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from a2al import Daemon, Client
|
|
25
|
+
|
|
26
|
+
with Daemon() as d:
|
|
27
|
+
c = Client(d.api_base, token=d.api_token)
|
|
28
|
+
print(c.health())
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Environment Variables
|
|
32
|
+
|
|
33
|
+
| Variable | Description |
|
|
34
|
+
|----------|-------------|
|
|
35
|
+
| `A2ALD_PATH` | Override path to the `a2ald` executable |
|
|
36
|
+
| `A2AL_API_TOKEN` | Bearer token when the daemon enforces auth |
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Python 3.10+
|
|
41
|
+
- No third-party dependencies (standard library only)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "a2al"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for a2al: spawn a2ald and call its REST API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MPL-2.0" }
|
|
12
|
+
authors = [{ name = "A2AL Authors" }]
|
|
13
|
+
keywords = ["a2al", "a2a", "agent", "ai", "sidecar"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/a2al/a2al"
|
|
29
|
+
Repository = "https://github.com/a2al/a2al"
|
|
30
|
+
"Bug Tracker" = "https://github.com/a2al/a2al/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.package-data]
|
|
36
|
+
a2al = ["bin/*"]
|
a2al-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import socket
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import time
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Mapping, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _find_exe() -> str:
|
|
19
|
+
exe = "a2ald.exe" if sys.platform == "win32" else "a2ald"
|
|
20
|
+
embedded = Path(__file__).parent / "bin" / exe
|
|
21
|
+
if embedded.is_file() and os.access(str(embedded), os.X_OK):
|
|
22
|
+
return str(embedded)
|
|
23
|
+
return exe
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _free_port() -> int:
|
|
27
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
28
|
+
s.bind(("127.0.0.1", 0))
|
|
29
|
+
_host, port = s.getsockname()
|
|
30
|
+
s.close()
|
|
31
|
+
return int(port)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Daemon:
|
|
35
|
+
"""Runs a2ald with a temp data dir and dynamic API port.
|
|
36
|
+
|
|
37
|
+
Usage (recommended — ensures cleanup on exit)::
|
|
38
|
+
|
|
39
|
+
with Daemon() as d:
|
|
40
|
+
c = Client(d.api_base, token=d.api_token)
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
Alternatively call ``start()`` / ``close()`` explicitly, or rely on the
|
|
44
|
+
``atexit`` handler registered by ``start()`` as a fallback.
|
|
45
|
+
|
|
46
|
+
Note: ``_free_port`` binds and releases a port before passing it to a2ald,
|
|
47
|
+
so a brief race window exists. In practice this is benign for local loopback.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
a2ald_exe: Optional[str] = None,
|
|
53
|
+
api_token: Optional[str] = None,
|
|
54
|
+
extra_args: Optional[list[str]] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
self._exe = a2ald_exe or os.environ.get("A2ALD_PATH") or _find_exe()
|
|
57
|
+
self._api_token = api_token if api_token is not None else os.environ.get("A2AL_API_TOKEN")
|
|
58
|
+
self._extra = extra_args or []
|
|
59
|
+
self._proc: Optional[subprocess.Popen[bytes]] = None
|
|
60
|
+
self._dd: Optional[str] = None
|
|
61
|
+
self.api_base: Optional[str] = None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def api_token(self) -> Optional[str]:
|
|
65
|
+
return self._api_token
|
|
66
|
+
|
|
67
|
+
def start(self, timeout: float = 45.0) -> None:
|
|
68
|
+
if self._proc is not None:
|
|
69
|
+
return
|
|
70
|
+
self._dd = tempfile.mkdtemp(prefix="a2al-")
|
|
71
|
+
port = _free_port()
|
|
72
|
+
self.api_base = f"http://127.0.0.1:{port}"
|
|
73
|
+
args = [
|
|
74
|
+
self._exe,
|
|
75
|
+
"--data-dir",
|
|
76
|
+
self._dd,
|
|
77
|
+
"--api-addr",
|
|
78
|
+
f"127.0.0.1:{port}",
|
|
79
|
+
*self._extra,
|
|
80
|
+
]
|
|
81
|
+
env = os.environ.copy()
|
|
82
|
+
if self._api_token:
|
|
83
|
+
env["A2AL_API_TOKEN"] = self._api_token
|
|
84
|
+
self._proc = subprocess.Popen(
|
|
85
|
+
args,
|
|
86
|
+
stdout=subprocess.DEVNULL,
|
|
87
|
+
stderr=subprocess.DEVNULL,
|
|
88
|
+
stdin=subprocess.DEVNULL,
|
|
89
|
+
env=env,
|
|
90
|
+
)
|
|
91
|
+
atexit.register(self.close)
|
|
92
|
+
deadline = time.time() + timeout
|
|
93
|
+
while time.time() < deadline:
|
|
94
|
+
if self._proc.poll() is not None:
|
|
95
|
+
raise RuntimeError("a2ald exited during startup")
|
|
96
|
+
try:
|
|
97
|
+
urllib.request.urlopen(self.api_base + "/health", timeout=1.0)
|
|
98
|
+
return
|
|
99
|
+
except (urllib.error.URLError, OSError):
|
|
100
|
+
time.sleep(0.15)
|
|
101
|
+
raise TimeoutError("a2ald /health not ready")
|
|
102
|
+
|
|
103
|
+
def close(self) -> None:
|
|
104
|
+
if self._proc is not None:
|
|
105
|
+
self._proc.terminate()
|
|
106
|
+
try:
|
|
107
|
+
self._proc.wait(timeout=8)
|
|
108
|
+
except subprocess.TimeoutExpired:
|
|
109
|
+
self._proc.kill()
|
|
110
|
+
self._proc = None
|
|
111
|
+
if self._dd:
|
|
112
|
+
shutil.rmtree(self._dd, ignore_errors=True)
|
|
113
|
+
self._dd = None
|
|
114
|
+
self.api_base = None
|
|
115
|
+
|
|
116
|
+
def __enter__(self) -> Daemon:
|
|
117
|
+
self.start()
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def __exit__(self, *exc: object) -> None:
|
|
121
|
+
self.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Client:
|
|
125
|
+
"""JSON REST client for a2ald (localhost)."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, base_url: str, token: Optional[str] = None) -> None:
|
|
128
|
+
self.base = base_url.rstrip("/")
|
|
129
|
+
self.token = token
|
|
130
|
+
|
|
131
|
+
def _request(
|
|
132
|
+
self,
|
|
133
|
+
method: str,
|
|
134
|
+
path: str,
|
|
135
|
+
body: Optional[Mapping[str, Any]] = None,
|
|
136
|
+
timeout: float = 120.0,
|
|
137
|
+
) -> Any:
|
|
138
|
+
data = None
|
|
139
|
+
headers: dict[str, str] = {"Content-Type": "application/json"}
|
|
140
|
+
if self.token:
|
|
141
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
142
|
+
if body is not None:
|
|
143
|
+
data = json.dumps(body).encode("utf-8")
|
|
144
|
+
req = urllib.request.Request(
|
|
145
|
+
self.base + path, data=data, headers=headers, method=method
|
|
146
|
+
)
|
|
147
|
+
try:
|
|
148
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
149
|
+
raw = resp.read().decode("utf-8")
|
|
150
|
+
if not raw:
|
|
151
|
+
return None
|
|
152
|
+
return json.loads(raw)
|
|
153
|
+
except urllib.error.HTTPError as exc:
|
|
154
|
+
body_bytes = exc.read()
|
|
155
|
+
try:
|
|
156
|
+
err_body = json.loads(body_bytes)
|
|
157
|
+
msg = err_body.get("error") or str(err_body)
|
|
158
|
+
except Exception:
|
|
159
|
+
msg = body_bytes.decode("utf-8", errors="replace")[:300]
|
|
160
|
+
raise RuntimeError(f"HTTP {exc.code}: {msg}") from exc
|
|
161
|
+
|
|
162
|
+
def health(self) -> Any:
|
|
163
|
+
return self._request("GET", "/health", None)
|
|
164
|
+
|
|
165
|
+
def config_get(self) -> Any:
|
|
166
|
+
return self._request("GET", "/config", None)
|
|
167
|
+
|
|
168
|
+
def agents_list(self) -> Any:
|
|
169
|
+
return self._request("GET", "/agents", None)
|
|
170
|
+
|
|
171
|
+
def identity_generate(self) -> Any:
|
|
172
|
+
return self._request("POST", "/identity/generate", {})
|
|
173
|
+
|
|
174
|
+
def agent_register(self, payload: Mapping[str, Any]) -> Any:
|
|
175
|
+
return self._request("POST", "/agents", dict(payload))
|
|
176
|
+
|
|
177
|
+
def agent_publish(self, aid: str) -> Any:
|
|
178
|
+
return self._request("POST", f"/agents/{aid}/publish", {})
|
|
179
|
+
|
|
180
|
+
def resolve(self, aid: str) -> Any:
|
|
181
|
+
return self._request("POST", f"/resolve/{aid}", {})
|
|
182
|
+
|
|
183
|
+
def connect(self, aid: str, local_aid: str = "") -> Any:
|
|
184
|
+
body: dict[str, str] = {}
|
|
185
|
+
if local_aid:
|
|
186
|
+
body["local_aid"] = local_aid
|
|
187
|
+
return self._request("POST", f"/connect/{aid}", body)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a2al
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for a2al: spawn a2ald and call its REST API
|
|
5
|
+
Author: A2AL Authors
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/a2al/a2al
|
|
8
|
+
Project-URL: Repository, https://github.com/a2al/a2al
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/a2al/a2al/issues
|
|
10
|
+
Keywords: a2al,a2a,agent,ai,sidecar
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# a2al (Python)
|
|
25
|
+
|
|
26
|
+
Python client for [a2al](https://github.com/a2al/a2al): spawns a local `a2ald` daemon and exposes a typed REST client for its API.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install a2al
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pre-built `a2ald` binaries are bundled inside platform wheels for:
|
|
35
|
+
|
|
36
|
+
| Platform | Architecture |
|
|
37
|
+
|----------|-------------|
|
|
38
|
+
| Linux | x86_64, arm64 |
|
|
39
|
+
| macOS | x86_64, arm64 |
|
|
40
|
+
| Windows | x86_64 |
|
|
41
|
+
|
|
42
|
+
On unsupported platforms, install `a2ald` manually and ensure it is on `PATH`, or set `A2ALD_PATH` to the executable path.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from a2al import Daemon, Client
|
|
48
|
+
|
|
49
|
+
with Daemon() as d:
|
|
50
|
+
c = Client(d.api_base, token=d.api_token)
|
|
51
|
+
print(c.health())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Environment Variables
|
|
55
|
+
|
|
56
|
+
| Variable | Description |
|
|
57
|
+
|----------|-------------|
|
|
58
|
+
| `A2ALD_PATH` | Override path to the `a2ald` executable |
|
|
59
|
+
| `A2AL_API_TOKEN` | Bearer token when the daemon enforces auth |
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
- Python 3.10+
|
|
64
|
+
- No third-party dependencies (standard library only)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a2al
|