a2a-lite 0.2.0__py3-none-any.whl → 0.2.1__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 +1 -1
- a2a_lite/agent.py +5 -0
- a2a_lite/auth.py +15 -4
- a2a_lite/cli.py +1 -1
- a2a_lite/executor.py +35 -7
- a2a_lite/middleware.py +6 -1
- a2a_lite/tasks.py +5 -5
- a2a_lite/testing.py +1 -1
- {a2a_lite-0.2.0.dist-info → a2a_lite-0.2.1.dist-info}/METADATA +1 -1
- a2a_lite-0.2.1.dist-info/RECORD +19 -0
- a2a_lite-0.2.0.dist-info/RECORD +0 -19
- {a2a_lite-0.2.0.dist-info → a2a_lite-0.2.1.dist-info}/WHEEL +0 -0
- {a2a_lite-0.2.0.dist-info → a2a_lite-0.2.1.dist-info}/entry_points.txt +0 -0
a2a_lite/__init__.py
CHANGED
a2a_lite/agent.py
CHANGED
|
@@ -332,6 +332,10 @@ class Agent:
|
|
|
332
332
|
task_store=self._task_store,
|
|
333
333
|
)
|
|
334
334
|
|
|
335
|
+
# The SDK's InMemoryTaskStore handles protocol-level task lifecycle
|
|
336
|
+
# (task creation, state transitions per the A2A spec). This is separate
|
|
337
|
+
# from self._task_store which provides application-level tracking
|
|
338
|
+
# (progress updates, custom status) exposed via TaskContext to skills.
|
|
335
339
|
request_handler = DefaultRequestHandler(
|
|
336
340
|
agent_executor=executor,
|
|
337
341
|
task_store=InMemoryTaskStore(),
|
|
@@ -485,6 +489,7 @@ class Agent:
|
|
|
485
489
|
task_store=self._task_store,
|
|
486
490
|
)
|
|
487
491
|
|
|
492
|
+
# SDK task store for protocol-level lifecycle (separate from app-level self._task_store)
|
|
488
493
|
request_handler = DefaultRequestHandler(
|
|
489
494
|
agent_executor=executor,
|
|
490
495
|
task_store=InMemoryTaskStore(),
|
a2a_lite/auth.py
CHANGED
|
@@ -60,6 +60,17 @@ class AuthRequest:
|
|
|
60
60
|
method: str = "POST"
|
|
61
61
|
path: str = "/"
|
|
62
62
|
|
|
63
|
+
def get_header(self, name: str) -> Optional[str]:
|
|
64
|
+
"""Get a header value (case-insensitive)."""
|
|
65
|
+
# Try exact match first, then case-insensitive
|
|
66
|
+
if name in self.headers:
|
|
67
|
+
return self.headers[name]
|
|
68
|
+
lower = name.lower()
|
|
69
|
+
for k, v in self.headers.items():
|
|
70
|
+
if k.lower() == lower:
|
|
71
|
+
return v
|
|
72
|
+
return None
|
|
73
|
+
|
|
63
74
|
|
|
64
75
|
@dataclass
|
|
65
76
|
class AuthResult:
|
|
@@ -128,8 +139,8 @@ class APIKeyAuth(AuthProvider):
|
|
|
128
139
|
return hashlib.sha256(key.encode()).hexdigest()
|
|
129
140
|
|
|
130
141
|
async def authenticate(self, request: AuthRequest) -> AuthResult:
|
|
131
|
-
# Check header
|
|
132
|
-
key = request.
|
|
142
|
+
# Check header (case-insensitive)
|
|
143
|
+
key = request.get_header(self.header)
|
|
133
144
|
|
|
134
145
|
# Check query param
|
|
135
146
|
if not key and self.query_param:
|
|
@@ -179,7 +190,7 @@ class BearerAuth(AuthProvider):
|
|
|
179
190
|
self.header = header
|
|
180
191
|
|
|
181
192
|
async def authenticate(self, request: AuthRequest) -> AuthResult:
|
|
182
|
-
auth_header = request.
|
|
193
|
+
auth_header = request.get_header(self.header) or ""
|
|
183
194
|
|
|
184
195
|
if not auth_header.startswith("Bearer "):
|
|
185
196
|
return AuthResult.failure("Bearer token required")
|
|
@@ -228,7 +239,7 @@ class OAuth2Auth(AuthProvider):
|
|
|
228
239
|
self._jwks_client = None
|
|
229
240
|
|
|
230
241
|
async def authenticate(self, request: AuthRequest) -> AuthResult:
|
|
231
|
-
auth_header = request.
|
|
242
|
+
auth_header = request.get_header("Authorization") or ""
|
|
232
243
|
|
|
233
244
|
if not auth_header.startswith("Bearer "):
|
|
234
245
|
return AuthResult.failure("Bearer token required")
|
a2a_lite/cli.py
CHANGED
a2a_lite/executor.py
CHANGED
|
@@ -59,6 +59,22 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
59
59
|
from a2a.utils import new_agent_text_message
|
|
60
60
|
|
|
61
61
|
try:
|
|
62
|
+
# Authenticate the request
|
|
63
|
+
if self.auth_provider:
|
|
64
|
+
from .auth import AuthRequest, NoAuth
|
|
65
|
+
if not isinstance(self.auth_provider, NoAuth):
|
|
66
|
+
headers = {}
|
|
67
|
+
if context.call_context and context.call_context.state:
|
|
68
|
+
headers = context.call_context.state.get('headers', {})
|
|
69
|
+
auth_request = AuthRequest(headers=headers)
|
|
70
|
+
auth_result = await self.auth_provider.authenticate(auth_request)
|
|
71
|
+
if not auth_result.authenticated:
|
|
72
|
+
error_msg = json.dumps({
|
|
73
|
+
"error": auth_result.error or "Authentication failed",
|
|
74
|
+
})
|
|
75
|
+
await event_queue.enqueue_event(new_agent_text_message(error_msg))
|
|
76
|
+
return
|
|
77
|
+
|
|
62
78
|
# Extract message and parts
|
|
63
79
|
message, parts = self._extract_message_and_parts(context)
|
|
64
80
|
|
|
@@ -120,7 +136,14 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
120
136
|
if skill_name is None:
|
|
121
137
|
if not self.skills:
|
|
122
138
|
return {"error": "No skills registered"}
|
|
123
|
-
|
|
139
|
+
# Only auto-select if there's exactly one skill
|
|
140
|
+
if len(self.skills) == 1:
|
|
141
|
+
skill_name = list(self.skills.keys())[0]
|
|
142
|
+
else:
|
|
143
|
+
return {
|
|
144
|
+
"error": "No skill specified. Use {\"skill\": \"name\", \"params\": {...}} format.",
|
|
145
|
+
"available_skills": list(self.skills.keys()),
|
|
146
|
+
}
|
|
124
147
|
|
|
125
148
|
if skill_name not in self.skills:
|
|
126
149
|
return {
|
|
@@ -167,11 +190,19 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
167
190
|
metadata: Dict[str, Any],
|
|
168
191
|
) -> Dict[str, Any]:
|
|
169
192
|
"""Convert parameters to Pydantic models and file parts if needed."""
|
|
193
|
+
import typing
|
|
170
194
|
handler = skill_def.handler
|
|
171
|
-
|
|
195
|
+
try:
|
|
196
|
+
hints = typing.get_type_hints(handler)
|
|
197
|
+
except Exception:
|
|
198
|
+
hints = getattr(handler, '__annotations__', {})
|
|
199
|
+
|
|
200
|
+
from .parts import FilePart, DataPart
|
|
172
201
|
|
|
173
202
|
converted = {}
|
|
174
203
|
for param_name, value in params.items():
|
|
204
|
+
if param_name == 'return':
|
|
205
|
+
continue
|
|
175
206
|
param_type = hints.get(param_name)
|
|
176
207
|
|
|
177
208
|
if param_type is None:
|
|
@@ -185,9 +216,7 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
185
216
|
continue
|
|
186
217
|
|
|
187
218
|
# Convert FilePart
|
|
188
|
-
|
|
189
|
-
if "FilePart" in type_name:
|
|
190
|
-
from .parts import FilePart
|
|
219
|
+
if _is_or_subclass(param_type, FilePart):
|
|
191
220
|
if isinstance(value, dict):
|
|
192
221
|
# Handle both A2A format and simple dict format
|
|
193
222
|
if "file" in value:
|
|
@@ -208,8 +237,7 @@ class LiteAgentExecutor(AgentExecutor):
|
|
|
208
237
|
continue
|
|
209
238
|
|
|
210
239
|
# Convert DataPart
|
|
211
|
-
if
|
|
212
|
-
from .parts import DataPart
|
|
240
|
+
if _is_or_subclass(param_type, DataPart):
|
|
213
241
|
if isinstance(value, dict):
|
|
214
242
|
# Handle both A2A format and simple dict format
|
|
215
243
|
if "type" in value and value.get("type") == "data":
|
a2a_lite/middleware.py
CHANGED
|
@@ -159,7 +159,12 @@ def retry_middleware(max_retries: int = 3, delay: float = 1.0):
|
|
|
159
159
|
|
|
160
160
|
def rate_limit_middleware(requests_per_minute: int = 60):
|
|
161
161
|
"""
|
|
162
|
-
Create a simple rate limiting middleware.
|
|
162
|
+
Create a simple in-process rate limiting middleware.
|
|
163
|
+
|
|
164
|
+
Note: This rate limiter is per-process. Under multi-worker uvicorn
|
|
165
|
+
(e.g., ``--workers 4``), each worker tracks limits independently.
|
|
166
|
+
For shared rate limiting across workers, use an external store
|
|
167
|
+
(Redis, etc.) and a custom middleware.
|
|
163
168
|
|
|
164
169
|
Example:
|
|
165
170
|
agent.add_middleware(rate_limit_middleware(requests_per_minute=100))
|
a2a_lite/tasks.py
CHANGED
|
@@ -30,7 +30,7 @@ from __future__ import annotations
|
|
|
30
30
|
import asyncio
|
|
31
31
|
import logging
|
|
32
32
|
from dataclasses import dataclass, field
|
|
33
|
-
from datetime import datetime
|
|
33
|
+
from datetime import datetime, timezone
|
|
34
34
|
from enum import Enum
|
|
35
35
|
from typing import Any, Callable, Dict, List, Optional
|
|
36
36
|
from uuid import uuid4
|
|
@@ -55,7 +55,7 @@ class TaskStatus:
|
|
|
55
55
|
state: TaskState
|
|
56
56
|
message: Optional[str] = None
|
|
57
57
|
progress: Optional[float] = None # 0.0 to 1.0
|
|
58
|
-
timestamp: datetime = field(default_factory=datetime.
|
|
58
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
59
59
|
|
|
60
60
|
def to_dict(self) -> Dict[str, Any]:
|
|
61
61
|
return {
|
|
@@ -77,8 +77,8 @@ class Task:
|
|
|
77
77
|
error: Optional[str] = None
|
|
78
78
|
artifacts: List[Any] = field(default_factory=list)
|
|
79
79
|
history: List[TaskStatus] = field(default_factory=list)
|
|
80
|
-
created_at: datetime = field(default_factory=datetime.
|
|
81
|
-
updated_at: datetime = field(default_factory=datetime.
|
|
80
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
81
|
+
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
82
82
|
|
|
83
83
|
def update_status(
|
|
84
84
|
self,
|
|
@@ -89,7 +89,7 @@ class Task:
|
|
|
89
89
|
"""Update task status."""
|
|
90
90
|
self.history.append(self.status)
|
|
91
91
|
self.status = TaskStatus(state=state, message=message, progress=progress)
|
|
92
|
-
self.updated_at = datetime.
|
|
92
|
+
self.updated_at = datetime.now(timezone.utc)
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class TaskContext:
|
a2a_lite/testing.py
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
a2a_lite/__init__.py,sha256=5bFZw7LapphdqfvMvyKfyEthe_kH4iigeGChd5h3TI4,3520
|
|
2
|
+
a2a_lite/agent.py,sha256=WyHe9aA_BYkzIXRjN5byoRc-vItCyL6YLDiyfX5gfPU,17053
|
|
3
|
+
a2a_lite/auth.py,sha256=bjPu_xnAxmfK5_dkqYz5mHouAjHqJFWlfYMqn-S1-A4,10177
|
|
4
|
+
a2a_lite/cli.py,sha256=D46mGduJb8AcZz8WRczNx5OzvIGxqZ7H-4fLCy2LpX8,9043
|
|
5
|
+
a2a_lite/decorators.py,sha256=VdinkddmF61IS8TkjdSfHGdn3nDKwepcIjWZPZBKg3w,1050
|
|
6
|
+
a2a_lite/discovery.py,sha256=BxpiJAUDxIyI2gvsLhjmHte5c9ax5Qf1hbBQnyAmxLQ,4508
|
|
7
|
+
a2a_lite/executor.py,sha256=zkTotenRNa7wlHh6pnIcS546fx5laSgoVIxZizE7SQI,13218
|
|
8
|
+
a2a_lite/human_loop.py,sha256=XAqxp-k8I7TNyuLqqNmLEqABHqcAUiKYCL8n3W5StaY,8685
|
|
9
|
+
a2a_lite/middleware.py,sha256=c6jb9aFfyTf-JY6KjqaSgFJmpzqbHLC6Q1h9NNteqzo,5545
|
|
10
|
+
a2a_lite/parts.py,sha256=qVRiD-H9_NlMPk-R0gTUiGVQ77E2poiuBWAUyAyAoTI,6177
|
|
11
|
+
a2a_lite/streaming.py,sha256=RFv9EJYnhwkT0h1Wovkj4EXwFzCgHdaA-h7WpPaaONo,2329
|
|
12
|
+
a2a_lite/tasks.py,sha256=UpmDP-VGIQ1LodBNq4zx2pJElQ31gOJOAduHFBVyxOA,7039
|
|
13
|
+
a2a_lite/testing.py,sha256=blugOpPKNThlbFTTSCyBVHb7tgQH79RH8z7Vo45hGbg,8953
|
|
14
|
+
a2a_lite/utils.py,sha256=CnkO6HH9oKEXymbJG2ohdt1ESxldQ3fkmcYVO-o9R_k,3841
|
|
15
|
+
a2a_lite/webhooks.py,sha256=t6ebT3jVBEKFpjhBnPI-nuQWIUKQUbJm24phXOBnNKA,6158
|
|
16
|
+
a2a_lite-0.2.1.dist-info/METADATA,sha256=5QLYvcVJh3qwri1zR3Gg-so5YLaAXXAIr-TvvhAg9UM,12583
|
|
17
|
+
a2a_lite-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
18
|
+
a2a_lite-0.2.1.dist-info/entry_points.txt,sha256=BONfFqZbCntNal2iwlTJAE09gCUvurfvqslMYVYh4is,46
|
|
19
|
+
a2a_lite-0.2.1.dist-info/RECORD,,
|
a2a_lite-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
a2a_lite/__init__.py,sha256=HfTauky7g1ugDu1OQO65qcCi8bzbHeSaAK_k6ENrB8k,3520
|
|
2
|
-
a2a_lite/agent.py,sha256=ZdRGi1w1VYDUd7DxlGneZ_1Y5JSAIyur_zU6RZIrmp0,16647
|
|
3
|
-
a2a_lite/auth.py,sha256=vRAYOj-4VOiaXQ_-hKk8YFkideyU5b8JFj2lEp-rW8s,9772
|
|
4
|
-
a2a_lite/cli.py,sha256=beffnCU-FJR7kVCCJBwHYazUQzwi4g7HiSD20lgrpAM,9043
|
|
5
|
-
a2a_lite/decorators.py,sha256=VdinkddmF61IS8TkjdSfHGdn3nDKwepcIjWZPZBKg3w,1050
|
|
6
|
-
a2a_lite/discovery.py,sha256=BxpiJAUDxIyI2gvsLhjmHte5c9ax5Qf1hbBQnyAmxLQ,4508
|
|
7
|
-
a2a_lite/executor.py,sha256=tNtrIbwAImadMyJFtrat1iaXkqrACckrqjt3x39IPOc,11891
|
|
8
|
-
a2a_lite/human_loop.py,sha256=XAqxp-k8I7TNyuLqqNmLEqABHqcAUiKYCL8n3W5StaY,8685
|
|
9
|
-
a2a_lite/middleware.py,sha256=ciR2z5y85uYIc4YFGNTR2litjRehuU7jsohYMJDGHkI,5282
|
|
10
|
-
a2a_lite/parts.py,sha256=qVRiD-H9_NlMPk-R0gTUiGVQ77E2poiuBWAUyAyAoTI,6177
|
|
11
|
-
a2a_lite/streaming.py,sha256=RFv9EJYnhwkT0h1Wovkj4EXwFzCgHdaA-h7WpPaaONo,2329
|
|
12
|
-
a2a_lite/tasks.py,sha256=VWHhRwi4ajrr4vMNSJVpFjSUqMOdAPM-PnVUeCnYbww,6963
|
|
13
|
-
a2a_lite/testing.py,sha256=kmQGkFucUAv2zPkrko0ZWn7S1q3bkz0v7FXpO6J47mU,8985
|
|
14
|
-
a2a_lite/utils.py,sha256=CnkO6HH9oKEXymbJG2ohdt1ESxldQ3fkmcYVO-o9R_k,3841
|
|
15
|
-
a2a_lite/webhooks.py,sha256=t6ebT3jVBEKFpjhBnPI-nuQWIUKQUbJm24phXOBnNKA,6158
|
|
16
|
-
a2a_lite-0.2.0.dist-info/METADATA,sha256=CR6_O8Uu-M8whAcFH9bGetWql2ZzmSRZwFCncjIg2tU,12583
|
|
17
|
-
a2a_lite-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
18
|
-
a2a_lite-0.2.0.dist-info/entry_points.txt,sha256=BONfFqZbCntNal2iwlTJAE09gCUvurfvqslMYVYh4is,46
|
|
19
|
-
a2a_lite-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|