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 CHANGED
@@ -102,7 +102,7 @@ from .auth import (
102
102
  require_auth,
103
103
  )
104
104
 
105
- __version__ = "0.2.0"
105
+ __version__ = "0.2.1"
106
106
 
107
107
  __all__ = [
108
108
  # Core
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.headers.get(self.header)
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.headers.get(self.header, "")
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.headers.get("Authorization", "")
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
@@ -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.0",
78
+ "a2a-lite>=0.2.1",
79
79
  ]
80
80
  '''
81
81
  (project_path / "pyproject.toml").write_text(pyproject)
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
- skill_name = list(self.skills.keys())[0]
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
- hints = getattr(handler, '__annotations__', {})
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
- type_name = str(param_type)
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 "DataPart" in type_name:
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.utcnow)
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.utcnow)
81
- updated_at: datetime = field(default_factory=datetime.utcnow)
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.utcnow()
92
+ self.updated_at = datetime.now(timezone.utc)
93
93
 
94
94
 
95
95
  class TaskContext:
a2a_lite/testing.py CHANGED
@@ -214,7 +214,7 @@ class AgentTestClient:
214
214
  result = await gen
215
215
  results.append(result)
216
216
 
217
- asyncio.get_event_loop().run_until_complete(run_handler())
217
+ asyncio.run(run_handler())
218
218
  return results
219
219
 
220
220
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: a2a-lite
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Simplified wrapper for Google's A2A Protocol SDK
5
5
  Author: A2A Lite Contributors
6
6
  License-Expression: Apache-2.0
@@ -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,,
@@ -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,,