a2akit 0.0.1__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.
- a2akit-0.0.1/.github/workflows/ci.yml +50 -0
- a2akit-0.0.1/.github/workflows/publish.yml +46 -0
- a2akit-0.0.1/.gitignore +11 -0
- a2akit-0.0.1/CHANGELOG.md +23 -0
- a2akit-0.0.1/LICENSE +21 -0
- a2akit-0.0.1/PKG-INFO +126 -0
- a2akit-0.0.1/README.md +93 -0
- a2akit-0.0.1/examples/echo.py +20 -0
- a2akit-0.0.1/examples/langgraph_worker.py +90 -0
- a2akit-0.0.1/examples/streaming.py +35 -0
- a2akit-0.0.1/pyproject.toml +99 -0
- a2akit-0.0.1/src/a2akit/__init__.py +56 -0
- a2akit-0.0.1/src/a2akit/agent_card.py +106 -0
- a2akit-0.0.1/src/a2akit/broker/__init__.py +20 -0
- a2akit-0.0.1/src/a2akit/broker/base.py +169 -0
- a2akit-0.0.1/src/a2akit/broker/memory.py +154 -0
- a2akit-0.0.1/src/a2akit/cancel.py +104 -0
- a2akit-0.0.1/src/a2akit/endpoints.py +285 -0
- a2akit-0.0.1/src/a2akit/event_bus/__init__.py +9 -0
- a2akit-0.0.1/src/a2akit/event_bus/base.py +76 -0
- a2akit-0.0.1/src/a2akit/event_bus/memory.py +95 -0
- a2akit-0.0.1/src/a2akit/event_emitter.py +100 -0
- a2akit-0.0.1/src/a2akit/py.typed +0 -0
- a2akit-0.0.1/src/a2akit/schema.py +29 -0
- a2akit-0.0.1/src/a2akit/server.py +181 -0
- a2akit-0.0.1/src/a2akit/storage/__init__.py +31 -0
- a2akit-0.0.1/src/a2akit/storage/base.py +274 -0
- a2akit-0.0.1/src/a2akit/storage/memory.py +254 -0
- a2akit-0.0.1/src/a2akit/task_manager.py +355 -0
- a2akit-0.0.1/src/a2akit/worker/__init__.py +23 -0
- a2akit-0.0.1/src/a2akit/worker/adapter.py +282 -0
- a2akit-0.0.1/src/a2akit/worker/base.py +775 -0
- a2akit-0.0.1/src/a2akit/worker/context_factory.py +105 -0
- a2akit-0.0.1/tests/conftest.py +262 -0
- a2akit-0.0.1/tests/test_agent_card.py +31 -0
- a2akit-0.0.1/tests/test_agent_card_build.py +46 -0
- a2akit-0.0.1/tests/test_artifacts.py +83 -0
- a2akit-0.0.1/tests/test_auth_required.py +46 -0
- a2akit-0.0.1/tests/test_broker.py +43 -0
- a2akit-0.0.1/tests/test_cancel.py +39 -0
- a2akit-0.0.1/tests/test_cancel_registry.py +39 -0
- a2akit-0.0.1/tests/test_cancel_storage.py +151 -0
- a2akit-0.0.1/tests/test_context.py +74 -0
- a2akit-0.0.1/tests/test_direct_reply.py +42 -0
- a2akit-0.0.1/tests/test_echo.py +57 -0
- a2akit-0.0.1/tests/test_endpoints_coverage.py +380 -0
- a2akit-0.0.1/tests/test_error_handling.py +119 -0
- a2akit-0.0.1/tests/test_event_bus.py +85 -0
- a2akit-0.0.1/tests/test_health.py +10 -0
- a2akit-0.0.1/tests/test_idempotency.py +35 -0
- a2akit-0.0.1/tests/test_input_required.py +63 -0
- a2akit-0.0.1/tests/test_server_coverage.py +253 -0
- a2akit-0.0.1/tests/test_storage.py +113 -0
- a2akit-0.0.1/tests/test_streaming.py +69 -0
- a2akit-0.0.1/tests/test_task_manager_coverage.py +490 -0
- a2akit-0.0.1/tests/test_task_queries.py +136 -0
- a2akit-0.0.1/tests/test_version_header.py +60 -0
- a2akit-0.0.1/tests/test_worker_adapter.py +50 -0
- a2akit-0.0.1/tests/test_worker_adapter_coverage.py +401 -0
- a2akit-0.0.1/tests/test_worker_base_coverage.py +418 -0
- a2akit-0.0.1/uv.lock +1751 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
name: Lint & Format
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v4
|
|
19
|
+
with: { version: "latest" }
|
|
20
|
+
- run: uv python install 3.11
|
|
21
|
+
- run: uv sync --dev
|
|
22
|
+
- run: uv run ruff check src/ tests/
|
|
23
|
+
- run: uv run ruff format --check src/ tests/
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
strategy:
|
|
29
|
+
matrix:
|
|
30
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v4
|
|
34
|
+
with: { version: "latest" }
|
|
35
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
36
|
+
- run: uv sync --dev
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: |
|
|
39
|
+
uv run pytest \
|
|
40
|
+
--cov=a2akit \
|
|
41
|
+
--cov-report=term-missing \
|
|
42
|
+
--cov-report=xml:coverage.xml \
|
|
43
|
+
--cov-fail-under=90 \
|
|
44
|
+
--junitxml=results.xml
|
|
45
|
+
- name: Upload coverage
|
|
46
|
+
if: matrix.python-version == '3.11'
|
|
47
|
+
uses: actions/upload-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: coverage-report
|
|
50
|
+
path: coverage.xml
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
name: Build & Publish
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
environment: pypi
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Verify tag matches pyproject.toml
|
|
21
|
+
run: |
|
|
22
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
23
|
+
PKG=$(grep -m1 '^version' pyproject.toml | sed 's/.*"\(.*\)"/\1/')
|
|
24
|
+
if [ "$TAG" != "$PKG" ]; then
|
|
25
|
+
echo "::error::Tag v$TAG does not match pyproject.toml version $PKG"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
- uses: astral-sh/setup-uv@v4
|
|
30
|
+
with: { version: "latest" }
|
|
31
|
+
- run: uv python install 3.11
|
|
32
|
+
- run: uv sync --dev
|
|
33
|
+
|
|
34
|
+
- name: Lint
|
|
35
|
+
run: uv run ruff check src/ tests/
|
|
36
|
+
|
|
37
|
+
- name: Test
|
|
38
|
+
run: uv run pytest --cov=a2akit --cov-fail-under=90
|
|
39
|
+
|
|
40
|
+
- name: Build
|
|
41
|
+
run: uv build
|
|
42
|
+
|
|
43
|
+
- name: Publish
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
45
|
+
with:
|
|
46
|
+
packages-dir: dist/
|
a2akit-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
|
+
|
|
7
|
+
## [0.0.1] — 2025-XX-XX
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial release.
|
|
11
|
+
- `A2AServer` with one-liner FastAPI setup.
|
|
12
|
+
- `Worker` ABC with `TaskContext` for agent logic.
|
|
13
|
+
- Full A2A v0.3.0 HTTP+JSON transport (REST endpoints).
|
|
14
|
+
- Streaming via SSE (`message:stream`, `tasks:subscribe`).
|
|
15
|
+
- Cooperative and force-cancel with timeout fallback.
|
|
16
|
+
- Multi-turn support (`request_input`, `request_auth`).
|
|
17
|
+
- Direct reply mode (`reply_directly`).
|
|
18
|
+
- Artifact streaming with append semantics.
|
|
19
|
+
- Context persistence (`load_context`, `update_context`).
|
|
20
|
+
- `InMemoryStorage`, `InMemoryBroker`, `InMemoryEventBus`, `InMemoryCancelRegistry`.
|
|
21
|
+
- Agent card discovery (`/.well-known/agent-card.json`).
|
|
22
|
+
- Optimistic concurrency control on all storage writes.
|
|
23
|
+
- Idempotent task creation via `messageId`.
|
a2akit-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Markus Lang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
a2akit-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a2akit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A2A agent framework in one import.
|
|
5
|
+
Project-URL: Homepage, https://github.com/markuslang1987/a2akit
|
|
6
|
+
Project-URL: Documentation, https://github.com/markuslang1987/a2akit#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/markuslang1987/a2akit
|
|
8
|
+
Project-URL: Changelog, https://github.com/markuslang1987/a2akit/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/markuslang1987/a2akit/issues
|
|
10
|
+
Author: Markus Lang
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: a2a,agent,agent-to-agent,async,framework
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: a2a-sdk<1,>=0.3
|
|
25
|
+
Requires-Dist: anyio>=4.4
|
|
26
|
+
Requires-Dist: fastapi>=0.110
|
|
27
|
+
Requires-Dist: pydantic>=2.7
|
|
28
|
+
Requires-Dist: sse-starlette>=2.0
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
30
|
+
Provides-Extra: langgraph
|
|
31
|
+
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# a2akit
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/a2akit/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://pypi.org/project/a2akit/)
|
|
39
|
+
[](https://github.com/markuslang1987/a2akit/actions)
|
|
40
|
+
|
|
41
|
+
**A2A agent framework in one import.**
|
|
42
|
+
|
|
43
|
+
Build [Agent-to-Agent (A2A)](https://google.github.io/A2A/) protocol
|
|
44
|
+
agents with minimal boilerplate. Streaming, cancellation, multi-turn
|
|
45
|
+
conversations, and artifact handling — batteries included.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install a2akit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from a2akit import A2AServer, AgentCardConfig, TaskContext, Worker
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EchoWorker(Worker):
|
|
60
|
+
async def handle(self, ctx: TaskContext) -> None:
|
|
61
|
+
await ctx.complete(f"Echo: {ctx.user_text}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
server = A2AServer(
|
|
65
|
+
worker=EchoWorker(),
|
|
66
|
+
agent_card=AgentCardConfig(
|
|
67
|
+
name="Echo Agent",
|
|
68
|
+
description="Echoes your input back.",
|
|
69
|
+
version="0.1.0",
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
app = server.as_fastapi_app()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run it:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uvicorn my_agent:app --reload
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Test it:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
curl -X POST http://localhost:8000/v1/message:send \
|
|
85
|
+
-H "Content-Type: application/json" \
|
|
86
|
+
-d '{"message":{"role":"user","parts":[{"text":"hello"}],"messageId":"1"}}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
- **One-liner setup** — `A2AServer` wires storage, broker, event bus, and endpoints
|
|
92
|
+
- **Streaming** — word-by-word artifact streaming via SSE
|
|
93
|
+
- **Cancellation** — cooperative and force-cancel with timeout fallback
|
|
94
|
+
- **Multi-turn** — `request_input()` / `request_auth()` for conversational flows
|
|
95
|
+
- **Direct reply** — `reply_directly()` for simple request/response without task tracking
|
|
96
|
+
- **Pluggable backends** — swap in Redis, PostgreSQL, RabbitMQ (coming soon)
|
|
97
|
+
- **Type-safe** — full type hints, `py.typed` marker, PEP 561 compliant
|
|
98
|
+
|
|
99
|
+
## A2A Protocol Version
|
|
100
|
+
|
|
101
|
+
a2akit implements [A2A v0.3.0](https://google.github.io/A2A/).
|
|
102
|
+
|
|
103
|
+
## Roadmap
|
|
104
|
+
|
|
105
|
+
Planned features for upcoming releases. Priorities may shift based on feedback.
|
|
106
|
+
|
|
107
|
+
| Feature | Target |
|
|
108
|
+
|---|---|
|
|
109
|
+
| Task middleware / hooks | v0.1.0 |
|
|
110
|
+
| Lifecycle hooks + dependency injection | v0.1.0 |
|
|
111
|
+
| Documentation website | v0.1.0 |
|
|
112
|
+
| Redis EventBus | v0.2.0 |
|
|
113
|
+
| Redis Broker | v0.2.0 |
|
|
114
|
+
| PostgreSQL Storage | v0.2.0 |
|
|
115
|
+
| SQLite Storage | v0.2.0 |
|
|
116
|
+
| Backend conformance test suite | v0.2.0 |
|
|
117
|
+
| OpenTelemetry integration | v0.2.0 |
|
|
118
|
+
| RabbitMQ Broker | v0.3.0+ |
|
|
119
|
+
| JSON-RPC transport | v0.3.0+ |
|
|
120
|
+
| gRPC transport | v0.4.0+ |
|
|
121
|
+
|
|
122
|
+
This roadmap is subject to change.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
a2akit-0.0.1/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# a2akit
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/a2akit/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://pypi.org/project/a2akit/)
|
|
6
|
+
[](https://github.com/markuslang1987/a2akit/actions)
|
|
7
|
+
|
|
8
|
+
**A2A agent framework in one import.**
|
|
9
|
+
|
|
10
|
+
Build [Agent-to-Agent (A2A)](https://google.github.io/A2A/) protocol
|
|
11
|
+
agents with minimal boilerplate. Streaming, cancellation, multi-turn
|
|
12
|
+
conversations, and artifact handling — batteries included.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install a2akit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from a2akit import A2AServer, AgentCardConfig, TaskContext, Worker
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EchoWorker(Worker):
|
|
27
|
+
async def handle(self, ctx: TaskContext) -> None:
|
|
28
|
+
await ctx.complete(f"Echo: {ctx.user_text}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
server = A2AServer(
|
|
32
|
+
worker=EchoWorker(),
|
|
33
|
+
agent_card=AgentCardConfig(
|
|
34
|
+
name="Echo Agent",
|
|
35
|
+
description="Echoes your input back.",
|
|
36
|
+
version="0.1.0",
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
app = server.as_fastapi_app()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run it:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uvicorn my_agent:app --reload
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Test it:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -X POST http://localhost:8000/v1/message:send \
|
|
52
|
+
-H "Content-Type: application/json" \
|
|
53
|
+
-d '{"message":{"role":"user","parts":[{"text":"hello"}],"messageId":"1"}}'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **One-liner setup** — `A2AServer` wires storage, broker, event bus, and endpoints
|
|
59
|
+
- **Streaming** — word-by-word artifact streaming via SSE
|
|
60
|
+
- **Cancellation** — cooperative and force-cancel with timeout fallback
|
|
61
|
+
- **Multi-turn** — `request_input()` / `request_auth()` for conversational flows
|
|
62
|
+
- **Direct reply** — `reply_directly()` for simple request/response without task tracking
|
|
63
|
+
- **Pluggable backends** — swap in Redis, PostgreSQL, RabbitMQ (coming soon)
|
|
64
|
+
- **Type-safe** — full type hints, `py.typed` marker, PEP 561 compliant
|
|
65
|
+
|
|
66
|
+
## A2A Protocol Version
|
|
67
|
+
|
|
68
|
+
a2akit implements [A2A v0.3.0](https://google.github.io/A2A/).
|
|
69
|
+
|
|
70
|
+
## Roadmap
|
|
71
|
+
|
|
72
|
+
Planned features for upcoming releases. Priorities may shift based on feedback.
|
|
73
|
+
|
|
74
|
+
| Feature | Target |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Task middleware / hooks | v0.1.0 |
|
|
77
|
+
| Lifecycle hooks + dependency injection | v0.1.0 |
|
|
78
|
+
| Documentation website | v0.1.0 |
|
|
79
|
+
| Redis EventBus | v0.2.0 |
|
|
80
|
+
| Redis Broker | v0.2.0 |
|
|
81
|
+
| PostgreSQL Storage | v0.2.0 |
|
|
82
|
+
| SQLite Storage | v0.2.0 |
|
|
83
|
+
| Backend conformance test suite | v0.2.0 |
|
|
84
|
+
| OpenTelemetry integration | v0.2.0 |
|
|
85
|
+
| RabbitMQ Broker | v0.3.0+ |
|
|
86
|
+
| JSON-RPC transport | v0.3.0+ |
|
|
87
|
+
| gRPC transport | v0.4.0+ |
|
|
88
|
+
|
|
89
|
+
This roadmap is subject to change.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Simple echo worker — returns the user's input back."""
|
|
2
|
+
|
|
3
|
+
from a2akit import A2AServer, AgentCardConfig, TaskContext, Worker
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EchoWorker(Worker):
|
|
7
|
+
"""Echoes the user's message back as-is."""
|
|
8
|
+
|
|
9
|
+
async def handle(self, ctx: TaskContext) -> None:
|
|
10
|
+
"""Echo the user text back."""
|
|
11
|
+
await ctx.complete(f"Echo: {ctx.user_text}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
server = A2AServer(
|
|
15
|
+
worker=EchoWorker(),
|
|
16
|
+
agent_card=AgentCardConfig(
|
|
17
|
+
name="Echo", description="Echoes input", version="0.1.0"
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
app = server.as_fastapi_app()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""LangGraph worker — runs a graph with custom streaming, no LLM needed."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from langgraph.config import get_stream_writer
|
|
7
|
+
from langgraph.graph import END, START, StateGraph
|
|
8
|
+
|
|
9
|
+
from a2akit import A2AServer, AgentCardConfig, TaskContext, Worker
|
|
10
|
+
|
|
11
|
+
TOTAL = 10
|
|
12
|
+
BROKEN = {4, 7}
|
|
13
|
+
DELAY = 0.5
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FileProcessingState(TypedDict):
|
|
17
|
+
"""Empty state -- the graph communicates via custom stream events."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def process_node(state: FileProcessingState):
|
|
21
|
+
"""Simulate processing files, emitting progress via stream writer."""
|
|
22
|
+
writer = get_stream_writer()
|
|
23
|
+
succeeded = 0
|
|
24
|
+
failed = 0
|
|
25
|
+
|
|
26
|
+
for i in range(1, TOTAL + 1):
|
|
27
|
+
name = f"report_{i:03d}.csv"
|
|
28
|
+
await asyncio.sleep(DELAY)
|
|
29
|
+
|
|
30
|
+
if i in BROKEN:
|
|
31
|
+
failed += 1
|
|
32
|
+
writer({"type": "error", "file": name, "index": i, "total": TOTAL})
|
|
33
|
+
else:
|
|
34
|
+
succeeded += 1
|
|
35
|
+
writer({"type": "done", "file": name, "index": i, "total": TOTAL})
|
|
36
|
+
|
|
37
|
+
writer(
|
|
38
|
+
{"type": "summary", "succeeded": succeeded, "failed": failed, "total": TOTAL}
|
|
39
|
+
)
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
graph = (
|
|
44
|
+
StateGraph(FileProcessingState)
|
|
45
|
+
.add_node("process", process_node)
|
|
46
|
+
.add_edge(START, "process")
|
|
47
|
+
.add_edge("process", END)
|
|
48
|
+
.compile()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LangGraphWorker(Worker):
|
|
53
|
+
"""Runs a LangGraph file-processing pipeline and streams results via A2A."""
|
|
54
|
+
|
|
55
|
+
async def handle(self, ctx: TaskContext) -> None:
|
|
56
|
+
"""Execute the graph and forward custom stream events as A2A artifacts."""
|
|
57
|
+
await ctx.send_status("Starting file processing pipeline...")
|
|
58
|
+
lines: list[str] = []
|
|
59
|
+
|
|
60
|
+
async for _mode, chunk in graph.astream({}, stream_mode=["custom"]):
|
|
61
|
+
evt_type = chunk.get("type", "")
|
|
62
|
+
|
|
63
|
+
if evt_type == "done":
|
|
64
|
+
line = f"[{chunk['index']}/{chunk['total']}] {chunk['file']}"
|
|
65
|
+
lines.append(line)
|
|
66
|
+
await ctx.send_status(line)
|
|
67
|
+
|
|
68
|
+
elif evt_type == "error":
|
|
69
|
+
line = f"[{chunk['index']}/{chunk['total']}] {chunk['file']} - FAILED"
|
|
70
|
+
lines.append(line)
|
|
71
|
+
await ctx.send_status(line)
|
|
72
|
+
|
|
73
|
+
elif evt_type == "summary":
|
|
74
|
+
lines.append(
|
|
75
|
+
f"Summary: {chunk['succeeded']}/{chunk['total']} succeeded, "
|
|
76
|
+
f"{chunk['failed']} failed"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
await ctx.complete("\n".join(lines))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
server = A2AServer(
|
|
83
|
+
worker=LangGraphWorker(),
|
|
84
|
+
agent_card=AgentCardConfig(
|
|
85
|
+
name="File Processor",
|
|
86
|
+
description="LangGraph pipeline that processes files with streaming status",
|
|
87
|
+
version="0.1.0",
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
app = server.as_fastapi_app()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Streaming worker — emits word-by-word artifacts with progress updates."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from a2akit import A2AServer, AgentCardConfig, TaskContext, Worker
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StreamingWorker(Worker):
|
|
9
|
+
"""Streams the user's input back word by word."""
|
|
10
|
+
|
|
11
|
+
async def handle(self, ctx: TaskContext) -> None:
|
|
12
|
+
"""Split user text into words and emit each as a streaming artifact chunk."""
|
|
13
|
+
words = ctx.user_text.split()
|
|
14
|
+
await ctx.send_status(f"Streaming {len(words)} words...")
|
|
15
|
+
|
|
16
|
+
for i, word in enumerate(words):
|
|
17
|
+
is_last = i == len(words) - 1
|
|
18
|
+
await ctx.emit_text_artifact(
|
|
19
|
+
text=word + ("" if is_last else " "),
|
|
20
|
+
artifact_id="stream",
|
|
21
|
+
append=(i > 0),
|
|
22
|
+
last_chunk=is_last,
|
|
23
|
+
)
|
|
24
|
+
await asyncio.sleep(0.1)
|
|
25
|
+
|
|
26
|
+
await ctx.complete()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
server = A2AServer(
|
|
30
|
+
worker=StreamingWorker(),
|
|
31
|
+
agent_card=AgentCardConfig(
|
|
32
|
+
name="Streamer", description="Word-by-word streaming", version="0.1.0"
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
app = server.as_fastapi_app()
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "a2akit"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "A2A agent framework in one import."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Markus Lang" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["a2a", "agent", "agent-to-agent", "framework", "async"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Framework :: FastAPI",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"a2a-sdk>=0.3,<1",
|
|
29
|
+
"fastapi>=0.110",
|
|
30
|
+
"uvicorn[standard]>=0.29",
|
|
31
|
+
"sse-starlette>=2.0",
|
|
32
|
+
"anyio>=4.4",
|
|
33
|
+
"pydantic>=2.7",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
langgraph = ["langgraph>=0.2.0"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/markuslang1987/a2akit"
|
|
41
|
+
Documentation = "https://github.com/markuslang1987/a2akit#readme"
|
|
42
|
+
Repository = "https://github.com/markuslang1987/a2akit"
|
|
43
|
+
Changelog = "https://github.com/markuslang1987/a2akit/blob/main/CHANGELOG.md"
|
|
44
|
+
Issues = "https://github.com/markuslang1987/a2akit/issues"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8.0",
|
|
49
|
+
"pytest-asyncio>=0.24",
|
|
50
|
+
"pytest-cov>=5.0",
|
|
51
|
+
"httpx>=0.27",
|
|
52
|
+
"ruff>=0.8",
|
|
53
|
+
"asgi-lifespan>=2.1.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.wheel]
|
|
57
|
+
packages = ["src/a2akit"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
addopts = [
|
|
63
|
+
"--strict-markers",
|
|
64
|
+
"--tb=short",
|
|
65
|
+
"-q",
|
|
66
|
+
"--cov=a2akit",
|
|
67
|
+
"--cov-report=term-missing",
|
|
68
|
+
"--cov-fail-under=90",
|
|
69
|
+
]
|
|
70
|
+
markers = [
|
|
71
|
+
"slow: Tests that take >2s",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.ruff]
|
|
75
|
+
target-version = "py311"
|
|
76
|
+
line-length = 99
|
|
77
|
+
src = ["src", "tests"]
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint]
|
|
80
|
+
select = [
|
|
81
|
+
"F", "E", "W", "I", "N", "UP", "B", "A", "C4",
|
|
82
|
+
"SIM", "TCH", "RUF", "PT", "ASYNC", "T20", "PIE",
|
|
83
|
+
"RSE", "RET", "TID",
|
|
84
|
+
]
|
|
85
|
+
ignore = ["E501", "RET504", "SIM108"]
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint.per-file-ignores]
|
|
88
|
+
"tests/**" = ["T20"]
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint.isort]
|
|
91
|
+
known-first-party = ["a2akit"]
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
94
|
+
ban-relative-imports = "all"
|
|
95
|
+
|
|
96
|
+
[tool.ruff.format]
|
|
97
|
+
quote-style = "double"
|
|
98
|
+
indent-style = "space"
|
|
99
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""a2akit — A2A agent framework in one import."""
|
|
2
|
+
|
|
3
|
+
from a2akit.agent_card import AgentCardConfig, ExtensionConfig, SkillConfig
|
|
4
|
+
from a2akit.broker import (
|
|
5
|
+
Broker,
|
|
6
|
+
CancelRegistry,
|
|
7
|
+
InMemoryBroker,
|
|
8
|
+
InMemoryCancelRegistry,
|
|
9
|
+
)
|
|
10
|
+
from a2akit.event_bus import EventBus, InMemoryEventBus
|
|
11
|
+
from a2akit.event_emitter import DefaultEventEmitter, EventEmitter
|
|
12
|
+
from a2akit.server import A2AServer
|
|
13
|
+
from a2akit.storage import (
|
|
14
|
+
ArtifactWrite,
|
|
15
|
+
ContextMismatchError,
|
|
16
|
+
InMemoryStorage,
|
|
17
|
+
Storage,
|
|
18
|
+
TaskNotAcceptingMessagesError,
|
|
19
|
+
TaskNotCancelableError,
|
|
20
|
+
TaskNotFoundError,
|
|
21
|
+
TaskTerminalStateError,
|
|
22
|
+
UnsupportedOperationError,
|
|
23
|
+
)
|
|
24
|
+
from a2akit.storage.base import ListTasksQuery, ListTasksResult
|
|
25
|
+
from a2akit.task_manager import TaskManager
|
|
26
|
+
from a2akit.worker import FileInfo, TaskContext, Worker
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"A2AServer",
|
|
30
|
+
"AgentCardConfig",
|
|
31
|
+
"ArtifactWrite",
|
|
32
|
+
"Broker",
|
|
33
|
+
"CancelRegistry",
|
|
34
|
+
"ContextMismatchError",
|
|
35
|
+
"DefaultEventEmitter",
|
|
36
|
+
"EventBus",
|
|
37
|
+
"EventEmitter",
|
|
38
|
+
"ExtensionConfig",
|
|
39
|
+
"FileInfo",
|
|
40
|
+
"InMemoryBroker",
|
|
41
|
+
"InMemoryCancelRegistry",
|
|
42
|
+
"InMemoryEventBus",
|
|
43
|
+
"InMemoryStorage",
|
|
44
|
+
"ListTasksQuery",
|
|
45
|
+
"ListTasksResult",
|
|
46
|
+
"SkillConfig",
|
|
47
|
+
"Storage",
|
|
48
|
+
"TaskContext",
|
|
49
|
+
"TaskManager",
|
|
50
|
+
"TaskNotAcceptingMessagesError",
|
|
51
|
+
"TaskNotCancelableError",
|
|
52
|
+
"TaskNotFoundError",
|
|
53
|
+
"TaskTerminalStateError",
|
|
54
|
+
"UnsupportedOperationError",
|
|
55
|
+
"Worker",
|
|
56
|
+
]
|