a2a-lite 0.2.2__py3-none-any.whl → 0.2.4__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.
- a2a_lite/__init__.py +7 -36
- a2a_lite/agent.py +2 -64
- a2a_lite/cli.py +2 -43
- a2a_lite/decorators.py +0 -3
- a2a_lite/executor.py +1 -9
- a2a_lite/streaming.py +0 -29
- {a2a_lite-0.2.2.dist-info → a2a_lite-0.2.4.dist-info}/METADATA +40 -95
- a2a_lite-0.2.4.dist-info/RECORD +16 -0
- a2a_lite/discovery.py +0 -152
- a2a_lite/human_loop.py +0 -284
- a2a_lite/webhooks.py +0 -228
- a2a_lite-0.2.2.dist-info/RECORD +0 -19
- {a2a_lite-0.2.2.dist-info → a2a_lite-0.2.4.dist-info}/WHEEL +0 -0
- {a2a_lite-0.2.2.dist-info → a2a_lite-0.2.4.dist-info}/entry_points.txt +0 -0
a2a_lite/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
A2A Lite -
|
|
2
|
+
A2A Lite - Build A2A agents in 8 lines. Add enterprise features when you need them.
|
|
3
3
|
|
|
4
4
|
SIMPLE (8 lines):
|
|
5
5
|
from a2a_lite import Agent
|
|
@@ -43,14 +43,6 @@ WITH TASK TRACKING (opt-in):
|
|
|
43
43
|
await task.update("working", progress=0.5)
|
|
44
44
|
return "done"
|
|
45
45
|
|
|
46
|
-
WITH HUMAN-IN-THE-LOOP (opt-in):
|
|
47
|
-
from a2a_lite.human_loop import InteractionContext
|
|
48
|
-
|
|
49
|
-
@agent.skill("wizard")
|
|
50
|
-
async def wizard(ctx: InteractionContext) -> str:
|
|
51
|
-
name = await ctx.ask("What's your name?")
|
|
52
|
-
return f"Hello, {name}!"
|
|
53
|
-
|
|
54
46
|
WITH FILES (opt-in):
|
|
55
47
|
from a2a_lite.parts import FilePart
|
|
56
48
|
|
|
@@ -60,13 +52,12 @@ WITH FILES (opt-in):
|
|
|
60
52
|
return summarize(text)
|
|
61
53
|
"""
|
|
62
54
|
|
|
63
|
-
# Core
|
|
55
|
+
# Core
|
|
64
56
|
from .agent import Agent
|
|
65
57
|
from .decorators import SkillDefinition
|
|
66
|
-
from .discovery import AgentDiscovery, DiscoveredAgent
|
|
67
58
|
from .testing import AgentTestClient, AsyncAgentTestClient, TestResult
|
|
68
59
|
|
|
69
|
-
# Middleware
|
|
60
|
+
# Middleware
|
|
70
61
|
from .middleware import (
|
|
71
62
|
MiddlewareContext,
|
|
72
63
|
MiddlewareChain,
|
|
@@ -76,22 +67,13 @@ from .middleware import (
|
|
|
76
67
|
rate_limit_middleware,
|
|
77
68
|
)
|
|
78
69
|
|
|
79
|
-
#
|
|
80
|
-
from .webhooks import WebhookClient, WebhookConfig, NotificationManager
|
|
81
|
-
|
|
82
|
-
# Streaming - always available
|
|
83
|
-
from .streaming import StreamingResponse
|
|
84
|
-
|
|
85
|
-
# Parts - opt-in for multi-modal
|
|
70
|
+
# Parts (multi-modal)
|
|
86
71
|
from .parts import TextPart, FilePart, DataPart, Artifact
|
|
87
72
|
|
|
88
|
-
# Tasks
|
|
73
|
+
# Tasks
|
|
89
74
|
from .tasks import TaskContext, TaskState, TaskStatus, Task, TaskStore
|
|
90
75
|
|
|
91
|
-
#
|
|
92
|
-
from .human_loop import InteractionContext, ConversationMemory
|
|
93
|
-
|
|
94
|
-
# Auth - opt-in
|
|
76
|
+
# Auth
|
|
95
77
|
from .auth import (
|
|
96
78
|
AuthProvider,
|
|
97
79
|
AuthResult,
|
|
@@ -102,14 +84,12 @@ from .auth import (
|
|
|
102
84
|
require_auth,
|
|
103
85
|
)
|
|
104
86
|
|
|
105
|
-
__version__ = "0.2.
|
|
87
|
+
__version__ = "0.2.3"
|
|
106
88
|
|
|
107
89
|
__all__ = [
|
|
108
90
|
# Core
|
|
109
91
|
"Agent",
|
|
110
92
|
"SkillDefinition",
|
|
111
|
-
"AgentDiscovery",
|
|
112
|
-
"DiscoveredAgent",
|
|
113
93
|
# Testing
|
|
114
94
|
"AgentTestClient",
|
|
115
95
|
"AsyncAgentTestClient",
|
|
@@ -121,12 +101,6 @@ __all__ = [
|
|
|
121
101
|
"timing_middleware",
|
|
122
102
|
"retry_middleware",
|
|
123
103
|
"rate_limit_middleware",
|
|
124
|
-
# Webhooks
|
|
125
|
-
"WebhookClient",
|
|
126
|
-
"WebhookConfig",
|
|
127
|
-
"NotificationManager",
|
|
128
|
-
# Streaming
|
|
129
|
-
"StreamingResponse",
|
|
130
104
|
# Parts (multi-modal)
|
|
131
105
|
"TextPart",
|
|
132
106
|
"FilePart",
|
|
@@ -138,9 +112,6 @@ __all__ = [
|
|
|
138
112
|
"TaskStatus",
|
|
139
113
|
"Task",
|
|
140
114
|
"TaskStore",
|
|
141
|
-
# Human-in-the-loop
|
|
142
|
-
"InteractionContext",
|
|
143
|
-
"ConversationMemory",
|
|
144
115
|
# Auth
|
|
145
116
|
"AuthProvider",
|
|
146
117
|
"AuthResult",
|
a2a_lite/agent.py
CHANGED
|
@@ -29,7 +29,6 @@ from .decorators import SkillDefinition
|
|
|
29
29
|
from .utils import type_to_json_schema, extract_function_schemas, _is_or_subclass
|
|
30
30
|
from .middleware import MiddlewareChain, MiddlewareContext
|
|
31
31
|
from .streaming import is_generator_function
|
|
32
|
-
from .webhooks import NotificationManager, WebhookClient
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
@dataclass
|
|
@@ -73,12 +72,6 @@ class Agent:
|
|
|
73
72
|
async def process(data: str, task: TaskContext) -> str:
|
|
74
73
|
await task.update("working", progress=0.5)
|
|
75
74
|
return "done"
|
|
76
|
-
|
|
77
|
-
WITH HUMAN-IN-THE-LOOP (optional):
|
|
78
|
-
@agent.skill("wizard")
|
|
79
|
-
async def wizard(ctx: InteractionContext) -> str:
|
|
80
|
-
name = await ctx.ask("What's your name?")
|
|
81
|
-
return f"Hello, {name}!"
|
|
82
75
|
"""
|
|
83
76
|
name: str
|
|
84
77
|
description: str
|
|
@@ -98,10 +91,7 @@ class Agent:
|
|
|
98
91
|
self._on_startup: List[Callable] = []
|
|
99
92
|
self._on_shutdown: List[Callable] = []
|
|
100
93
|
self._on_complete: List[Callable] = []
|
|
101
|
-
self._discovery = None
|
|
102
94
|
self._middleware = MiddlewareChain()
|
|
103
|
-
self._notifications = NotificationManager()
|
|
104
|
-
self._webhook = WebhookClient()
|
|
105
95
|
self._has_streaming = False
|
|
106
96
|
|
|
107
97
|
# Setup optional task store
|
|
@@ -146,12 +136,6 @@ class Agent:
|
|
|
146
136
|
async def process(data: str, task: TaskContext) -> str:
|
|
147
137
|
await task.update("working", progress=0.5)
|
|
148
138
|
return "done"
|
|
149
|
-
|
|
150
|
-
With human-in-the-loop (opt-in):
|
|
151
|
-
@agent.skill("wizard")
|
|
152
|
-
async def wizard(ctx: InteractionContext) -> str:
|
|
153
|
-
name = await ctx.ask("What's your name?")
|
|
154
|
-
return f"Hello, {name}!"
|
|
155
139
|
"""
|
|
156
140
|
def decorator(func: Callable) -> Callable:
|
|
157
141
|
skill_name = name or func.__name__
|
|
@@ -169,14 +153,11 @@ class Agent:
|
|
|
169
153
|
# Detect special parameter types using proper type introspection
|
|
170
154
|
import typing
|
|
171
155
|
from .tasks import TaskContext as _TaskContext
|
|
172
|
-
from .human_loop import InteractionContext as _InteractionContext
|
|
173
156
|
from .auth import AuthResult as _AuthResult
|
|
174
157
|
|
|
175
158
|
needs_task_context = False
|
|
176
|
-
needs_interaction = False
|
|
177
159
|
needs_auth = False
|
|
178
160
|
task_context_param: str | None = None
|
|
179
|
-
interaction_param: str | None = None
|
|
180
161
|
auth_param: str | None = None
|
|
181
162
|
|
|
182
163
|
try:
|
|
@@ -190,9 +171,6 @@ class Agent:
|
|
|
190
171
|
if _is_or_subclass(hint, _TaskContext):
|
|
191
172
|
needs_task_context = True
|
|
192
173
|
task_context_param = param_name
|
|
193
|
-
elif _is_or_subclass(hint, _InteractionContext):
|
|
194
|
-
needs_interaction = True
|
|
195
|
-
interaction_param = param_name
|
|
196
174
|
elif _is_or_subclass(hint, _AuthResult):
|
|
197
175
|
needs_auth = True
|
|
198
176
|
auth_param = param_name
|
|
@@ -215,10 +193,8 @@ class Agent:
|
|
|
215
193
|
is_async=asyncio.iscoroutinefunction(func) or is_streaming,
|
|
216
194
|
is_streaming=is_streaming,
|
|
217
195
|
needs_task_context=needs_task_context,
|
|
218
|
-
needs_interaction=needs_interaction,
|
|
219
196
|
needs_auth=needs_auth,
|
|
220
197
|
task_context_param=task_context_param,
|
|
221
|
-
interaction_param=interaction_param,
|
|
222
198
|
auth_param=auth_param,
|
|
223
199
|
)
|
|
224
200
|
|
|
@@ -264,16 +240,6 @@ class Agent:
|
|
|
264
240
|
self._on_complete.append(func)
|
|
265
241
|
return func
|
|
266
242
|
|
|
267
|
-
@property
|
|
268
|
-
def webhook(self) -> WebhookClient:
|
|
269
|
-
"""Get the webhook client for sending notifications."""
|
|
270
|
-
return self._webhook
|
|
271
|
-
|
|
272
|
-
@property
|
|
273
|
-
def notifications(self) -> NotificationManager:
|
|
274
|
-
"""Get the notification manager for push notifications."""
|
|
275
|
-
return self._notifications
|
|
276
|
-
|
|
277
243
|
def build_agent_card(self, host: str = "localhost", port: int = 8787) -> AgentCard:
|
|
278
244
|
"""Generate A2A-compliant Agent Card from registered skills."""
|
|
279
245
|
skills = []
|
|
@@ -291,12 +257,6 @@ class Agent:
|
|
|
291
257
|
|
|
292
258
|
url = self.url or f"http://{host}:{port}"
|
|
293
259
|
|
|
294
|
-
# Check if any skills need human-in-the-loop
|
|
295
|
-
has_input_required = any(
|
|
296
|
-
getattr(s, 'needs_interaction', False)
|
|
297
|
-
for s in self._skills.values()
|
|
298
|
-
)
|
|
299
|
-
|
|
300
260
|
return AgentCard(
|
|
301
261
|
name=self.name,
|
|
302
262
|
description=self.description,
|
|
@@ -315,9 +275,7 @@ class Agent:
|
|
|
315
275
|
self,
|
|
316
276
|
host: str = "0.0.0.0",
|
|
317
277
|
port: int = 8787,
|
|
318
|
-
reload: bool = False,
|
|
319
278
|
log_level: str = "info",
|
|
320
|
-
enable_discovery: bool = False,
|
|
321
279
|
) -> None:
|
|
322
280
|
"""
|
|
323
281
|
Start the A2A server.
|
|
@@ -326,7 +284,7 @@ class Agent:
|
|
|
326
284
|
agent.run()
|
|
327
285
|
|
|
328
286
|
With options:
|
|
329
|
-
agent.run(port=9000
|
|
287
|
+
agent.run(port=9000)
|
|
330
288
|
"""
|
|
331
289
|
from rich.console import Console
|
|
332
290
|
from rich.panel import Panel
|
|
@@ -362,8 +320,7 @@ class Agent:
|
|
|
362
320
|
# Build display info
|
|
363
321
|
skills_list = "\n".join([
|
|
364
322
|
f" • {s.name}: {s.description}" +
|
|
365
|
-
(" [streaming]" if getattr(s, 'is_streaming', False) else "")
|
|
366
|
-
(" [interactive]" if getattr(s, 'needs_interaction', False) else "")
|
|
323
|
+
(" [streaming]" if getattr(s, 'is_streaming', False) else "")
|
|
367
324
|
for s in self._skills.values()
|
|
368
325
|
])
|
|
369
326
|
if not skills_list:
|
|
@@ -375,8 +332,6 @@ class Agent:
|
|
|
375
332
|
features.append(f"{len(self._middleware._middlewares)} middleware")
|
|
376
333
|
if self._has_streaming:
|
|
377
334
|
features.append("streaming")
|
|
378
|
-
if self._on_complete:
|
|
379
|
-
features.append("webhooks")
|
|
380
335
|
if self.auth:
|
|
381
336
|
features.append("auth")
|
|
382
337
|
if self._task_store:
|
|
@@ -405,17 +360,6 @@ class Agent:
|
|
|
405
360
|
if self._on_startup:
|
|
406
361
|
asyncio.run(_run_startup())
|
|
407
362
|
|
|
408
|
-
# Enable discovery if requested
|
|
409
|
-
if enable_discovery:
|
|
410
|
-
from .discovery import AgentDiscovery
|
|
411
|
-
self._discovery = AgentDiscovery()
|
|
412
|
-
self._discovery.register(
|
|
413
|
-
name=self.name,
|
|
414
|
-
port=port,
|
|
415
|
-
properties={"version": self.version},
|
|
416
|
-
)
|
|
417
|
-
console.print(f"[dim]mDNS discovery enabled for {self.name}[/]")
|
|
418
|
-
|
|
419
363
|
# Production mode warning
|
|
420
364
|
if self.production:
|
|
421
365
|
url_str = self.url or f"http://{display_host}:{port}"
|
|
@@ -431,8 +375,6 @@ class Agent:
|
|
|
431
375
|
# Add CORS middleware if configured
|
|
432
376
|
if self.cors_origins is not None:
|
|
433
377
|
from starlette.middleware.cors import CORSMiddleware
|
|
434
|
-
from starlette.middleware import Middleware as StarletteMiddleware
|
|
435
|
-
# Wrap the existing app with CORS
|
|
436
378
|
app.add_middleware(
|
|
437
379
|
CORSMiddleware,
|
|
438
380
|
allow_origins=self.cors_origins,
|
|
@@ -459,10 +401,6 @@ class Agent:
|
|
|
459
401
|
if self._on_shutdown:
|
|
460
402
|
asyncio.run(_run_shutdown())
|
|
461
403
|
|
|
462
|
-
# Unregister discovery
|
|
463
|
-
if self._discovery:
|
|
464
|
-
self._discovery.unregister()
|
|
465
|
-
|
|
466
404
|
async def call_remote(
|
|
467
405
|
self,
|
|
468
406
|
agent_url: str,
|
a2a_lite/cli.py
CHANGED
|
@@ -75,7 +75,7 @@ version = "0.1.0"
|
|
|
75
75
|
description = "A2A Agent: {name}"
|
|
76
76
|
requires-python = ">=3.10"
|
|
77
77
|
dependencies = [
|
|
78
|
-
"a2a-lite>=0.2.
|
|
78
|
+
"a2a-lite>=0.2.3",
|
|
79
79
|
]
|
|
80
80
|
'''
|
|
81
81
|
(project_path / "pyproject.toml").write_text(pyproject)
|
|
@@ -232,51 +232,10 @@ def test(
|
|
|
232
232
|
raise typer.Exit(1)
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
@app.command()
|
|
236
|
-
def discover(
|
|
237
|
-
timeout: float = typer.Option(5.0, help="Discovery timeout in seconds"),
|
|
238
|
-
):
|
|
239
|
-
"""
|
|
240
|
-
Discover A2A agents on the local network.
|
|
241
|
-
|
|
242
|
-
Uses mDNS to find agents advertising themselves.
|
|
243
|
-
"""
|
|
244
|
-
from .discovery import AgentDiscovery
|
|
245
|
-
|
|
246
|
-
async def _discover():
|
|
247
|
-
console.print("[dim]Scanning local network...[/]\n")
|
|
248
|
-
|
|
249
|
-
discovery = AgentDiscovery()
|
|
250
|
-
agents = await discovery.discover(timeout=timeout)
|
|
251
|
-
|
|
252
|
-
if not agents:
|
|
253
|
-
console.print("[yellow]No agents found.[/]")
|
|
254
|
-
console.print("[dim]Make sure agents are running with discovery enabled (enable_discovery=True).[/]")
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
table = Table(title=f"🔍 Found {len(agents)} Agent(s)")
|
|
258
|
-
table.add_column("Name", style="cyan")
|
|
259
|
-
table.add_column("URL", style="blue")
|
|
260
|
-
table.add_column("Properties", style="dim")
|
|
261
|
-
|
|
262
|
-
for agent in agents:
|
|
263
|
-
table.add_row(
|
|
264
|
-
agent.name,
|
|
265
|
-
agent.url,
|
|
266
|
-
json.dumps(agent.properties) if agent.properties else "-",
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
console.print(table)
|
|
270
|
-
|
|
271
|
-
asyncio.run(_discover())
|
|
272
|
-
|
|
273
|
-
|
|
274
235
|
@app.command()
|
|
275
236
|
def serve(
|
|
276
237
|
file: Path = typer.Argument(..., help="Python file containing the agent"),
|
|
277
238
|
port: int = typer.Option(8787, help="Port to run on"),
|
|
278
|
-
reload: bool = typer.Option(False, "--reload", "-r", help="Enable hot reload"),
|
|
279
|
-
discovery: bool = typer.Option(False, "--discovery", "-d", help="Enable mDNS discovery"),
|
|
280
239
|
):
|
|
281
240
|
"""
|
|
282
241
|
Run an agent from a Python file.
|
|
@@ -312,7 +271,7 @@ def serve(
|
|
|
312
271
|
raise typer.Exit(1)
|
|
313
272
|
|
|
314
273
|
agent = module.agent
|
|
315
|
-
agent.run(port=port
|
|
274
|
+
agent.run(port=port)
|
|
316
275
|
|
|
317
276
|
|
|
318
277
|
@app.command()
|
a2a_lite/decorators.py
CHANGED
|
@@ -17,10 +17,8 @@ class SkillDefinition:
|
|
|
17
17
|
is_async: bool = False
|
|
18
18
|
is_streaming: bool = False
|
|
19
19
|
needs_task_context: bool = False
|
|
20
|
-
needs_interaction: bool = False
|
|
21
20
|
needs_auth: bool = False
|
|
22
21
|
task_context_param: Optional[str] = None
|
|
23
|
-
interaction_param: Optional[str] = None
|
|
24
22
|
auth_param: Optional[str] = None
|
|
25
23
|
|
|
26
24
|
def to_dict(self) -> Dict[str, Any]:
|
|
@@ -32,5 +30,4 @@ class SkillDefinition:
|
|
|
32
30
|
"input_schema": self.input_schema,
|
|
33
31
|
"output_schema": self.output_schema,
|
|
34
32
|
"is_streaming": self.is_streaming,
|
|
35
|
-
"needs_interaction": self.needs_interaction,
|
|
36
33
|
}
|
a2a_lite/executor.py
CHANGED
|
@@ -168,13 +168,6 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
168
168
|
param_name = skill_def.task_context_param or "task"
|
|
169
169
|
params[param_name] = task_ctx
|
|
170
170
|
|
|
171
|
-
if skill_def.needs_interaction:
|
|
172
|
-
from .human_loop import InteractionContext
|
|
173
|
-
task_id = metadata.get("task_id", "unknown")
|
|
174
|
-
interaction_ctx = InteractionContext(task_id, event_queue)
|
|
175
|
-
param_name = skill_def.interaction_param or "ctx"
|
|
176
|
-
params[param_name] = interaction_ctx
|
|
177
|
-
|
|
178
171
|
if skill_def.needs_auth:
|
|
179
172
|
param_name = skill_def.auth_param or "auth"
|
|
180
173
|
params[param_name] = metadata.get("auth_result")
|
|
@@ -217,9 +210,8 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
217
210
|
|
|
218
211
|
# Skip special context types
|
|
219
212
|
from .tasks import TaskContext as _TaskContext
|
|
220
|
-
from .human_loop import InteractionContext as _InteractionContext
|
|
221
213
|
from .auth import AuthResult as _AuthResult
|
|
222
|
-
if _is_or_subclass(param_type, _TaskContext) or _is_or_subclass(param_type,
|
|
214
|
+
if _is_or_subclass(param_type, _TaskContext) or _is_or_subclass(param_type, _AuthResult):
|
|
223
215
|
continue
|
|
224
216
|
|
|
225
217
|
# Convert FilePart
|
a2a_lite/streaming.py
CHANGED
|
@@ -58,32 +58,3 @@ async def stream_generator(
|
|
|
58
58
|
await event_queue.enqueue_event(new_agent_text_message(text))
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
class StreamingResponse:
|
|
62
|
-
"""
|
|
63
|
-
Helper class for building streaming responses.
|
|
64
|
-
|
|
65
|
-
Example:
|
|
66
|
-
@agent.skill("count")
|
|
67
|
-
async def count(n: int):
|
|
68
|
-
stream = StreamingResponse()
|
|
69
|
-
for i in range(n):
|
|
70
|
-
stream.write(f"Count: {i}")
|
|
71
|
-
return stream
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
def __init__(self):
|
|
75
|
-
self._chunks: list[str] = []
|
|
76
|
-
|
|
77
|
-
def write(self, chunk: str) -> None:
|
|
78
|
-
"""Add a chunk to the stream."""
|
|
79
|
-
self._chunks.append(chunk)
|
|
80
|
-
|
|
81
|
-
def __iter__(self):
|
|
82
|
-
return iter(self._chunks)
|
|
83
|
-
|
|
84
|
-
def __aiter__(self):
|
|
85
|
-
return self._async_iter()
|
|
86
|
-
|
|
87
|
-
async def _async_iter(self):
|
|
88
|
-
for chunk in self._chunks:
|
|
89
|
-
yield chunk
|