a2a-lite 0.1.0__tar.gz → 0.2.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.
Files changed (51) hide show
  1. a2a_lite-0.2.0/.claude/settings.local.json +12 -0
  2. a2a_lite-0.2.0/PKG-INFO +526 -0
  3. a2a_lite-0.2.0/README.md +495 -0
  4. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/09_testing.py +8 -8
  5. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/pyproject.toml +1 -1
  6. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/__init__.py +7 -6
  7. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/agent.py +70 -13
  8. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/auth.py +12 -4
  9. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/cli.py +1 -1
  10. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/decorators.py +3 -1
  11. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/discovery.py +4 -0
  12. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/executor.py +15 -8
  13. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/tasks.py +38 -29
  14. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/testing.py +56 -14
  15. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/utils.py +16 -0
  16. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_pydantic.py +7 -7
  17. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_tasks.py +36 -28
  18. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_testing.py +7 -7
  19. a2a_lite-0.1.0/PKG-INFO +0 -383
  20. a2a_lite-0.1.0/README.md +0 -352
  21. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/.gitignore +0 -0
  22. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/01_hello_world.py +0 -0
  23. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/02_calculator.py +0 -0
  24. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/03_async_agent.py +0 -0
  25. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/04_multi_agent/finance_agent.py +0 -0
  26. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/04_multi_agent/reporter_agent.py +0 -0
  27. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/04_multi_agent/run_demo.py +0 -0
  28. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/05_with_llm.py +0 -0
  29. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/06_pydantic_models.py +0 -0
  30. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/07_middleware.py +0 -0
  31. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/08_streaming.py +0 -0
  32. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/10_webhooks.py +0 -0
  33. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/11_human_in_the_loop.py +0 -0
  34. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/12_file_handling.py +0 -0
  35. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/13_task_tracking.py +0 -0
  36. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/examples/14_with_auth.py +0 -0
  37. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/human_loop.py +0 -0
  38. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/middleware.py +0 -0
  39. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/parts.py +0 -0
  40. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/streaming.py +0 -0
  41. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/src/a2a_lite/webhooks.py +0 -0
  42. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/__init__.py +0 -0
  43. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_agent.py +0 -0
  44. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_auth.py +0 -0
  45. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_decorators.py +0 -0
  46. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_discovery.py +0 -0
  47. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_human_loop.py +0 -0
  48. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_integration.py +0 -0
  49. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_middleware.py +0 -0
  50. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_parts.py +0 -0
  51. {a2a_lite-0.1.0 → a2a_lite-0.2.0}/tests/test_utils.py +0 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:pypi.org)",
5
+ "Bash(pip install:*)",
6
+ "Bash(uv init:*)",
7
+ "Bash(uv add:*)",
8
+ "Bash(uv run pytest:*)",
9
+ "mcp__obsidian-tools__create_note"
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,526 @@
1
+ Metadata-Version: 2.4
2
+ Name: a2a-lite
3
+ Version: 0.2.0
4
+ Summary: Simplified wrapper for Google's A2A Protocol SDK
5
+ Author: A2A Lite Contributors
6
+ License-Expression: Apache-2.0
7
+ Keywords: a2a,agents,ai,protocol,sdk
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: a2a-sdk[http-server]>=0.2.6
18
+ Requires-Dist: httpx>=0.25.0
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: rich>=13.0
21
+ Requires-Dist: starlette>=0.40.0
22
+ Requires-Dist: typer>=0.9.0
23
+ Requires-Dist: uvicorn>=0.30.0
24
+ Requires-Dist: watchfiles>=0.20.0
25
+ Requires-Dist: zeroconf>=0.80.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: httpx>=0.25; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # A2A Lite - Python
33
+
34
+ **Build A2A agents in 8 lines. Add enterprise features when you need them.**
35
+
36
+ Wraps the official [A2A Python SDK](https://github.com/a2aproject/a2a-python) with a simple, intuitive API.
37
+
38
+ ```python
39
+ from a2a_lite import Agent
40
+
41
+ agent = Agent(name="Bot", description="My bot")
42
+
43
+ @agent.skill("greet")
44
+ async def greet(name: str) -> str:
45
+ return f"Hello, {name}!"
46
+
47
+ agent.run()
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install a2a-lite
56
+ # or
57
+ uv add a2a-lite
58
+ ```
59
+
60
+ **Requirements:** Python 3.10+
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ ### 1. Create an agent
67
+
68
+ ```python
69
+ from a2a_lite import Agent
70
+
71
+ agent = Agent(name="Calculator", description="Does math")
72
+
73
+ @agent.skill("add")
74
+ async def add(a: int, b: int) -> int:
75
+ return a + b
76
+
77
+ @agent.skill("multiply")
78
+ async def multiply(a: int, b: int) -> int:
79
+ return a * b
80
+
81
+ agent.run(port=8787)
82
+ ```
83
+
84
+ ### 2. Test it
85
+
86
+ ```python
87
+ from a2a_lite import Agent, AgentTestClient
88
+
89
+ agent = Agent(name="Calculator", description="Does math")
90
+
91
+ @agent.skill("add")
92
+ async def add(a: int, b: int) -> int:
93
+ return a + b
94
+
95
+ client = AgentTestClient(agent)
96
+ result = client.call("add", a=2, b=3)
97
+ assert result == 5
98
+ ```
99
+
100
+ ### 3. Call it
101
+
102
+ ```bash
103
+ curl -X POST http://localhost:8787/ \
104
+ -H "Content-Type: application/json" \
105
+ -d '{
106
+ "jsonrpc": "2.0",
107
+ "method": "message/send",
108
+ "id": "1",
109
+ "params": {
110
+ "message": {
111
+ "role": "user",
112
+ "parts": [{"type": "text", "text": "{\"skill\": \"add\", \"params\": {\"a\": 2, \"b\": 3}}"}],
113
+ "messageId": "msg-1"
114
+ }
115
+ }
116
+ }'
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Progressive Complexity
122
+
123
+ ### Level 1: Basic Skills
124
+
125
+ ```python
126
+ from a2a_lite import Agent
127
+
128
+ agent = Agent(name="Bot", description="A bot")
129
+
130
+ @agent.skill("greet")
131
+ async def greet(name: str) -> str:
132
+ return f"Hello, {name}!"
133
+
134
+ agent.run()
135
+ ```
136
+
137
+ ### Level 2: Pydantic Models (Just Works)
138
+
139
+ Pass dicts from callers — they're auto-converted to Pydantic models:
140
+
141
+ ```python
142
+ from pydantic import BaseModel
143
+
144
+ class User(BaseModel):
145
+ name: str
146
+ email: str
147
+
148
+ @agent.skill("create_user")
149
+ async def create_user(user: User) -> dict:
150
+ # 'user' is already a User instance — auto-converted from dict!
151
+ return {"id": 1, "name": user.name}
152
+ ```
153
+
154
+ Lists of models work too:
155
+
156
+ ```python
157
+ from typing import List
158
+
159
+ @agent.skill("count_users")
160
+ async def count_users(users: List[User]) -> int:
161
+ return len(users)
162
+ ```
163
+
164
+ ### Level 3: Streaming (Just Yield)
165
+
166
+ ```python
167
+ @agent.skill("chat", streaming=True)
168
+ async def chat(message: str):
169
+ for word in message.split():
170
+ yield word + " "
171
+ ```
172
+
173
+ ### Level 4: Middleware
174
+
175
+ ```python
176
+ @agent.middleware
177
+ async def log_requests(ctx, next):
178
+ print(f"Calling: {ctx.skill}")
179
+ result = await next()
180
+ print(f"Result: {result}")
181
+ return result
182
+ ```
183
+
184
+ Built-in middleware:
185
+
186
+ ```python
187
+ from a2a_lite import logging_middleware, timing_middleware, retry_middleware, rate_limit_middleware
188
+
189
+ agent.use(logging_middleware)
190
+ agent.use(timing_middleware)
191
+ agent.use(rate_limit_middleware(max_per_minute=60))
192
+ agent.use(retry_middleware(max_retries=3))
193
+ ```
194
+
195
+ ### Level 5: Human-in-the-Loop
196
+
197
+ ```python
198
+ from a2a_lite import InteractionContext
199
+
200
+ @agent.skill("wizard")
201
+ async def wizard(ctx: InteractionContext) -> dict:
202
+ name = await ctx.ask("What's your name?")
203
+ role = await ctx.ask("Role?", options=["Dev", "Manager"])
204
+
205
+ if await ctx.confirm(f"Create {name} as {role}?"):
206
+ return {"created": name}
207
+ return {"cancelled": True}
208
+ ```
209
+
210
+ ### Level 6: File Handling
211
+
212
+ ```python
213
+ from a2a_lite import FilePart
214
+
215
+ @agent.skill("summarize")
216
+ async def summarize(doc: FilePart) -> str:
217
+ content = await doc.read_text()
218
+ return f"Summary: {content[:100]}..."
219
+ ```
220
+
221
+ ### Level 7: Task Tracking
222
+
223
+ ```python
224
+ from a2a_lite import TaskContext
225
+
226
+ agent = Agent(name="Bot", description="A bot", task_store="memory")
227
+
228
+ @agent.skill("process")
229
+ async def process(data: str, task: TaskContext) -> str:
230
+ await task.update("working", "Starting...", progress=0.0)
231
+
232
+ for i in range(10):
233
+ await task.update("working", f"Step {i}/10", progress=i/10)
234
+
235
+ return "Done!"
236
+ ```
237
+
238
+ ### Level 8: Authentication
239
+
240
+ ```python
241
+ from a2a_lite import Agent, APIKeyAuth
242
+
243
+ agent = Agent(
244
+ name="SecureBot",
245
+ description="A secure bot",
246
+ auth=APIKeyAuth(keys=["secret-key-1", "secret-key-2"]),
247
+ )
248
+ ```
249
+
250
+ API keys are hashed in memory using SHA-256 — plaintext keys are never stored.
251
+
252
+ Other auth providers:
253
+
254
+ ```python
255
+ from a2a_lite.auth import BearerAuth, OAuth2Auth
256
+
257
+ # Bearer/JWT
258
+ agent = Agent(
259
+ name="Bot", description="A bot",
260
+ auth=BearerAuth(secret="your-jwt-secret"),
261
+ )
262
+
263
+ # OAuth2
264
+ agent = Agent(
265
+ name="Bot", description="A bot",
266
+ auth=OAuth2Auth(issuer="https://auth.example.com", audience="my-api"),
267
+ )
268
+ ```
269
+
270
+ ### Level 9: CORS and Production Mode
271
+
272
+ ```python
273
+ agent = Agent(
274
+ name="Bot",
275
+ description="A bot",
276
+ cors_origins=["https://myapp.com", "https://admin.myapp.com"],
277
+ production=True, # Warns if running over HTTP
278
+ )
279
+ ```
280
+
281
+ ### Level 10: Webhooks
282
+
283
+ ```python
284
+ @agent.on_complete
285
+ async def notify(skill_name, result):
286
+ print(f"Skill {skill_name} completed with: {result}")
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Testing
292
+
293
+ ### AgentTestClient
294
+
295
+ The synchronous test client for use with pytest:
296
+
297
+ ```python
298
+ from a2a_lite import Agent, AgentTestClient
299
+
300
+ agent = Agent(name="Bot", description="Test")
301
+
302
+ @agent.skill("greet")
303
+ async def greet(name: str) -> str:
304
+ return f"Hello, {name}!"
305
+
306
+ @agent.skill("info")
307
+ async def info(name: str, age: int) -> dict:
308
+ return {"name": name, "age": age}
309
+
310
+
311
+ def test_simple_result():
312
+ client = AgentTestClient(agent)
313
+ result = client.call("greet", name="World")
314
+ # Simple values support direct equality
315
+ assert result == "Hello, World!"
316
+
317
+
318
+ def test_dict_result():
319
+ client = AgentTestClient(agent)
320
+ result = client.call("info", name="Alice", age=30)
321
+ # Access dict results via .data
322
+ assert result.data["name"] == "Alice"
323
+ assert result.data["age"] == 30
324
+
325
+
326
+ def test_text_access():
327
+ client = AgentTestClient(agent)
328
+ result = client.call("greet", name="World")
329
+ # .text gives the raw string
330
+ assert result.text == '"Hello, World!"'
331
+
332
+
333
+ def test_list_skills():
334
+ client = AgentTestClient(agent)
335
+ skills = client.list_skills()
336
+ assert "greet" in skills
337
+ assert "info" in skills
338
+ ```
339
+
340
+ ### TestResult
341
+
342
+ Every `client.call()` returns a `TestResult` with:
343
+
344
+ | Property | Description |
345
+ |----------|-------------|
346
+ | `.data` | Parsed Python object (dict, list, int, str, etc.) |
347
+ | `.text` | Raw text string from the response |
348
+ | `.json()` | Parse text as JSON (raises on invalid JSON) |
349
+ | `.raw_response` | Full A2A response dict |
350
+
351
+ `TestResult` supports direct equality comparison for simple values (`result == 5`), but use `.data` for subscripting (`result.data["key"]`).
352
+
353
+ ### AsyncAgentTestClient
354
+
355
+ For async test frameworks:
356
+
357
+ ```python
358
+ import pytest
359
+ from a2a_lite import AsyncAgentTestClient
360
+
361
+ @pytest.mark.asyncio
362
+ async def test_async():
363
+ client = AsyncAgentTestClient(agent)
364
+ result = await client.call("greet", name="World")
365
+ assert result == "Hello, World!"
366
+ await client.close()
367
+ ```
368
+
369
+ ### Streaming Tests
370
+
371
+ ```python
372
+ def test_streaming():
373
+ client = AgentTestClient(agent)
374
+ results = client.stream("chat", message="hello world")
375
+ assert len(results) == 2
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Task Store
381
+
382
+ The `TaskStore` provides async-safe task lifecycle management:
383
+
384
+ ```python
385
+ from a2a_lite.tasks import TaskStore, TaskStatus
386
+
387
+ store = TaskStore()
388
+
389
+ # All operations are async and thread-safe
390
+ task = await store.create(task_id="task-1", skill="process")
391
+ task = await store.get("task-1")
392
+ await store.update("task-1", status=TaskStatus.WORKING, progress=0.5)
393
+ tasks = await store.list()
394
+ await store.delete("task-1")
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Agent Discovery
400
+
401
+ Find agents on your local network via mDNS:
402
+
403
+ ```python
404
+ from a2a_lite import AgentDiscovery
405
+
406
+ # Advertise your agent
407
+ agent.run(port=8787, enable_discovery=True)
408
+
409
+ # Discover other agents
410
+ discovery = AgentDiscovery()
411
+ agents = await discovery.discover(timeout=5.0)
412
+ for a in agents:
413
+ print(f"{a.name} at {a.url}")
414
+ ```
415
+
416
+ ---
417
+
418
+ ## CLI
419
+
420
+ ```bash
421
+ a2a-lite init my-agent # Create new project
422
+ a2a-lite serve agent.py # Run agent from file
423
+ a2a-lite serve agent.py -r # Run with hot reload
424
+ a2a-lite inspect http://... # View agent capabilities
425
+ a2a-lite test http://... skill # Test a skill
426
+ a2a-lite discover # Find local agents
427
+ a2a-lite version # Show version
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Full API Reference
433
+
434
+ ### Agent
435
+
436
+ ```python
437
+ Agent(
438
+ name: str, # Required
439
+ description: str, # Required
440
+ version: str = "1.0.0",
441
+ url: str = None, # Override auto-detected URL
442
+ auth: AuthProvider = None, # Authentication provider
443
+ task_store: str | TaskStore = None, # "memory" or custom TaskStore
444
+ cors_origins: List[str] = None, # CORS allowed origins
445
+ production: bool = False, # Enable production warnings
446
+ enable_discovery: bool = False, # mDNS discovery
447
+ )
448
+ ```
449
+
450
+ **Methods:**
451
+
452
+ | Method | Description |
453
+ |--------|-------------|
454
+ | `@agent.skill(name, **config)` | Register a skill via decorator |
455
+ | `@agent.middleware` | Register middleware via decorator |
456
+ | `agent.use(middleware)` | Register middleware function |
457
+ | `@agent.on_complete` | Register completion hook |
458
+ | `agent.run(port=8787)` | Start the server |
459
+ | `agent.get_app()` | Get the ASGI app (for custom deployment) |
460
+
461
+ ### Skill Decorator
462
+
463
+ ```python
464
+ @agent.skill(
465
+ name: str, # Skill name (required)
466
+ description: str = None, # Human-readable description
467
+ tags: List[str] = None, # Categorization tags
468
+ streaming: bool = False, # Enable streaming
469
+ )
470
+ ```
471
+
472
+ ### Auth Providers
473
+
474
+ | Provider | Usage |
475
+ |----------|-------|
476
+ | `APIKeyAuth(keys=[...])` | API key auth (keys hashed with SHA-256) |
477
+ | `BearerAuth(secret=...)` | JWT/Bearer token auth |
478
+ | `OAuth2Auth(issuer=..., audience=...)` | OAuth2 auth |
479
+ | `NoAuth()` | No auth (default) |
480
+
481
+ ### Special Parameter Types
482
+
483
+ These are auto-injected when detected in skill signatures:
484
+
485
+ | Type | Description |
486
+ |------|-------------|
487
+ | `TaskContext` | Task lifecycle management (requires `task_store`) |
488
+ | `InteractionContext` | Human-in-the-loop interactions |
489
+ | `FilePart` | File upload handling |
490
+
491
+ ---
492
+
493
+ ## Examples
494
+
495
+ | Example | What it shows |
496
+ |---------|---------------|
497
+ | [01_hello_world.py](examples/01_hello_world.py) | Simplest agent (8 lines) |
498
+ | [02_calculator.py](examples/02_calculator.py) | Multiple skills |
499
+ | [06_pydantic_models.py](examples/06_pydantic_models.py) | Auto Pydantic conversion |
500
+ | [08_streaming.py](examples/08_streaming.py) | Streaming responses |
501
+ | [09_testing.py](examples/09_testing.py) | Testing your agents |
502
+ | [11_human_in_the_loop.py](examples/11_human_in_the_loop.py) | Ask user questions |
503
+ | [12_file_handling.py](examples/12_file_handling.py) | Handle files |
504
+ | [13_task_tracking.py](examples/13_task_tracking.py) | Progress updates |
505
+ | [14_with_auth.py](examples/14_with_auth.py) | Authentication |
506
+
507
+ ---
508
+
509
+ ## 100% A2A Protocol Compatible
510
+
511
+ A2A Lite wraps the official A2A Python SDK. Every feature maps to real A2A protocol concepts:
512
+
513
+ | A2A Lite | A2A Protocol |
514
+ |----------|--------------|
515
+ | `@agent.skill()` | Agent Skills |
516
+ | `streaming=True` | SSE Streaming |
517
+ | `InteractionContext.ask()` | `input-required` state |
518
+ | `TaskContext.update()` | Task lifecycle states |
519
+ | `FilePart` | A2A File parts |
520
+ | `APIKeyAuth` / `BearerAuth` | Security schemes |
521
+
522
+ ---
523
+
524
+ ## License
525
+
526
+ Apache 2.0