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.
Files changed (61) hide show
  1. a2akit-0.0.1/.github/workflows/ci.yml +50 -0
  2. a2akit-0.0.1/.github/workflows/publish.yml +46 -0
  3. a2akit-0.0.1/.gitignore +11 -0
  4. a2akit-0.0.1/CHANGELOG.md +23 -0
  5. a2akit-0.0.1/LICENSE +21 -0
  6. a2akit-0.0.1/PKG-INFO +126 -0
  7. a2akit-0.0.1/README.md +93 -0
  8. a2akit-0.0.1/examples/echo.py +20 -0
  9. a2akit-0.0.1/examples/langgraph_worker.py +90 -0
  10. a2akit-0.0.1/examples/streaming.py +35 -0
  11. a2akit-0.0.1/pyproject.toml +99 -0
  12. a2akit-0.0.1/src/a2akit/__init__.py +56 -0
  13. a2akit-0.0.1/src/a2akit/agent_card.py +106 -0
  14. a2akit-0.0.1/src/a2akit/broker/__init__.py +20 -0
  15. a2akit-0.0.1/src/a2akit/broker/base.py +169 -0
  16. a2akit-0.0.1/src/a2akit/broker/memory.py +154 -0
  17. a2akit-0.0.1/src/a2akit/cancel.py +104 -0
  18. a2akit-0.0.1/src/a2akit/endpoints.py +285 -0
  19. a2akit-0.0.1/src/a2akit/event_bus/__init__.py +9 -0
  20. a2akit-0.0.1/src/a2akit/event_bus/base.py +76 -0
  21. a2akit-0.0.1/src/a2akit/event_bus/memory.py +95 -0
  22. a2akit-0.0.1/src/a2akit/event_emitter.py +100 -0
  23. a2akit-0.0.1/src/a2akit/py.typed +0 -0
  24. a2akit-0.0.1/src/a2akit/schema.py +29 -0
  25. a2akit-0.0.1/src/a2akit/server.py +181 -0
  26. a2akit-0.0.1/src/a2akit/storage/__init__.py +31 -0
  27. a2akit-0.0.1/src/a2akit/storage/base.py +274 -0
  28. a2akit-0.0.1/src/a2akit/storage/memory.py +254 -0
  29. a2akit-0.0.1/src/a2akit/task_manager.py +355 -0
  30. a2akit-0.0.1/src/a2akit/worker/__init__.py +23 -0
  31. a2akit-0.0.1/src/a2akit/worker/adapter.py +282 -0
  32. a2akit-0.0.1/src/a2akit/worker/base.py +775 -0
  33. a2akit-0.0.1/src/a2akit/worker/context_factory.py +105 -0
  34. a2akit-0.0.1/tests/conftest.py +262 -0
  35. a2akit-0.0.1/tests/test_agent_card.py +31 -0
  36. a2akit-0.0.1/tests/test_agent_card_build.py +46 -0
  37. a2akit-0.0.1/tests/test_artifacts.py +83 -0
  38. a2akit-0.0.1/tests/test_auth_required.py +46 -0
  39. a2akit-0.0.1/tests/test_broker.py +43 -0
  40. a2akit-0.0.1/tests/test_cancel.py +39 -0
  41. a2akit-0.0.1/tests/test_cancel_registry.py +39 -0
  42. a2akit-0.0.1/tests/test_cancel_storage.py +151 -0
  43. a2akit-0.0.1/tests/test_context.py +74 -0
  44. a2akit-0.0.1/tests/test_direct_reply.py +42 -0
  45. a2akit-0.0.1/tests/test_echo.py +57 -0
  46. a2akit-0.0.1/tests/test_endpoints_coverage.py +380 -0
  47. a2akit-0.0.1/tests/test_error_handling.py +119 -0
  48. a2akit-0.0.1/tests/test_event_bus.py +85 -0
  49. a2akit-0.0.1/tests/test_health.py +10 -0
  50. a2akit-0.0.1/tests/test_idempotency.py +35 -0
  51. a2akit-0.0.1/tests/test_input_required.py +63 -0
  52. a2akit-0.0.1/tests/test_server_coverage.py +253 -0
  53. a2akit-0.0.1/tests/test_storage.py +113 -0
  54. a2akit-0.0.1/tests/test_streaming.py +69 -0
  55. a2akit-0.0.1/tests/test_task_manager_coverage.py +490 -0
  56. a2akit-0.0.1/tests/test_task_queries.py +136 -0
  57. a2akit-0.0.1/tests/test_version_header.py +60 -0
  58. a2akit-0.0.1/tests/test_worker_adapter.py +50 -0
  59. a2akit-0.0.1/tests/test_worker_adapter_coverage.py +401 -0
  60. a2akit-0.0.1/tests/test_worker_base_coverage.py +418 -0
  61. 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/
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .claude/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .coverage
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/a2akit)](https://pypi.org/project/a2akit/)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
38
+ [![Python](https://img.shields.io/pypi/pyversions/a2akit)](https://pypi.org/project/a2akit/)
39
+ [![CI](https://github.com/markuslang1987/a2akit/actions/workflows/ci.yml/badge.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/a2akit)](https://pypi.org/project/a2akit/)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Python](https://img.shields.io/pypi/pyversions/a2akit)](https://pypi.org/project/a2akit/)
6
+ [![CI](https://github.com/markuslang1987/a2akit/actions/workflows/ci.yml/badge.svg)](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
+ ]