a2a-lite 0.2.1__py3-none-any.whl → 0.2.2__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.1"
105
+ __version__ = "0.2.2"
106
106
 
107
107
  __all__ = [
108
108
  # Core
a2a_lite/agent.py CHANGED
@@ -170,11 +170,14 @@ class Agent:
170
170
  import typing
171
171
  from .tasks import TaskContext as _TaskContext
172
172
  from .human_loop import InteractionContext as _InteractionContext
173
+ from .auth import AuthResult as _AuthResult
173
174
 
174
175
  needs_task_context = False
175
176
  needs_interaction = False
177
+ needs_auth = False
176
178
  task_context_param: str | None = None
177
179
  interaction_param: str | None = None
180
+ auth_param: str | None = None
178
181
 
179
182
  try:
180
183
  resolved_hints = typing.get_type_hints(func)
@@ -190,6 +193,14 @@ class Agent:
190
193
  elif _is_or_subclass(hint, _InteractionContext):
191
194
  needs_interaction = True
192
195
  interaction_param = param_name
196
+ elif _is_or_subclass(hint, _AuthResult):
197
+ needs_auth = True
198
+ auth_param = param_name
199
+
200
+ # Also detect require_auth decorator
201
+ if getattr(func, '__requires_auth__', False) and not needs_auth:
202
+ needs_auth = True
203
+ auth_param = auth_param or "auth"
193
204
 
194
205
  # Extract schemas
195
206
  input_schema, output_schema = extract_function_schemas(func)
@@ -205,8 +216,10 @@ class Agent:
205
216
  is_streaming=is_streaming,
206
217
  needs_task_context=needs_task_context,
207
218
  needs_interaction=needs_interaction,
219
+ needs_auth=needs_auth,
208
220
  task_context_param=task_context_param,
209
221
  interaction_param=interaction_param,
222
+ auth_param=auth_param,
210
223
  )
211
224
 
212
225
  self._skills[skill_name] = skill_def
@@ -383,11 +396,14 @@ class Agent:
383
396
  ))
384
397
 
385
398
  # Run startup hooks
386
- for hook in self._on_startup:
387
- if asyncio.iscoroutinefunction(hook):
388
- asyncio.get_event_loop().run_until_complete(hook())
389
- else:
390
- hook()
399
+ async def _run_startup():
400
+ for hook in self._on_startup:
401
+ if asyncio.iscoroutinefunction(hook):
402
+ await hook()
403
+ else:
404
+ hook()
405
+ if self._on_startup:
406
+ asyncio.run(_run_startup())
391
407
 
392
408
  # Enable discovery if requested
393
409
  if enable_discovery:
@@ -434,11 +450,14 @@ class Agent:
434
450
  )
435
451
  finally:
436
452
  # Run shutdown hooks
437
- for hook in self._on_shutdown:
438
- if asyncio.iscoroutinefunction(hook):
439
- asyncio.get_event_loop().run_until_complete(hook())
440
- else:
441
- hook()
453
+ async def _run_shutdown():
454
+ for hook in self._on_shutdown:
455
+ if asyncio.iscoroutinefunction(hook):
456
+ await hook()
457
+ else:
458
+ hook()
459
+ if self._on_shutdown:
460
+ asyncio.run(_run_shutdown())
442
461
 
443
462
  # Unregister discovery
444
463
  if self._discovery:
a2a_lite/auth.py CHANGED
@@ -222,7 +222,7 @@ class OAuth2Auth(AuthProvider):
222
222
  audience="my-agent",
223
223
  )
224
224
 
225
- Requires: pip install pyjwt[crypto]
225
+ Requires: pip install a2a-lite[oauth]
226
226
  """
227
227
 
228
228
  def __init__(
@@ -277,7 +277,7 @@ class OAuth2Auth(AuthProvider):
277
277
 
278
278
  except ImportError:
279
279
  return AuthResult.failure(
280
- "OAuth2 requires pyjwt: pip install pyjwt[crypto]"
280
+ "OAuth2 requires pyjwt: pip install a2a-lite[oauth]"
281
281
  )
282
282
  except Exception as e:
283
283
  return AuthResult.failure(f"Token validation failed: {str(e)}")
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.1",
78
+ "a2a-lite>=0.2.2",
79
79
  ]
80
80
  '''
81
81
  (project_path / "pyproject.toml").write_text(pyproject)
a2a_lite/decorators.py CHANGED
@@ -18,8 +18,10 @@ class SkillDefinition:
18
18
  is_streaming: bool = False
19
19
  needs_task_context: bool = False
20
20
  needs_interaction: bool = False
21
+ needs_auth: bool = False
21
22
  task_context_param: Optional[str] = None
22
23
  interaction_param: Optional[str] = None
24
+ auth_param: Optional[str] = None
23
25
 
24
26
  def to_dict(self) -> Dict[str, Any]:
25
27
  """Convert to dictionary for serialization."""
a2a_lite/executor.py CHANGED
@@ -59,21 +59,22 @@ class LiteAgentExecutor(AgentExecutor):
59
59
  from a2a.utils import new_agent_text_message
60
60
 
61
61
  try:
62
- # Authenticate the request
62
+ # Authenticate the request (always run to produce auth_result for injection)
63
+ auth_result = None
63
64
  if self.auth_provider:
64
65
  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
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
+ # Reject unauthenticated requests (unless NoAuth)
72
+ if not isinstance(self.auth_provider, NoAuth) and not auth_result.authenticated:
73
+ error_msg = json.dumps({
74
+ "error": auth_result.error or "Authentication failed",
75
+ })
76
+ await event_queue.enqueue_event(new_agent_text_message(error_msg))
77
+ return
77
78
 
78
79
  # Extract message and parts
79
80
  message, parts = self._extract_message_and_parts(context)
@@ -88,9 +89,10 @@ class LiteAgentExecutor(AgentExecutor):
88
89
  message=message,
89
90
  )
90
91
 
91
- # Store parts in metadata for skill access
92
+ # Store parts and auth result in metadata for skill access
92
93
  ctx.metadata["parts"] = parts
93
94
  ctx.metadata["event_queue"] = event_queue
95
+ ctx.metadata["auth_result"] = auth_result
94
96
 
95
97
  # Define final handler
96
98
  async def final_handler(ctx: MiddlewareContext) -> Any:
@@ -173,6 +175,10 @@ class LiteAgentExecutor(AgentExecutor):
173
175
  param_name = skill_def.interaction_param or "ctx"
174
176
  params[param_name] = interaction_ctx
175
177
 
178
+ if skill_def.needs_auth:
179
+ param_name = skill_def.auth_param or "auth"
180
+ params[param_name] = metadata.get("auth_result")
181
+
176
182
  # Call the handler
177
183
  handler = skill_def.handler
178
184
 
@@ -212,7 +218,8 @@ class LiteAgentExecutor(AgentExecutor):
212
218
  # Skip special context types
213
219
  from .tasks import TaskContext as _TaskContext
214
220
  from .human_loop import InteractionContext as _InteractionContext
215
- if _is_or_subclass(param_type, _TaskContext) or _is_or_subclass(param_type, _InteractionContext):
221
+ from .auth import AuthResult as _AuthResult
222
+ if _is_or_subclass(param_type, _TaskContext) or _is_or_subclass(param_type, _InteractionContext) or _is_or_subclass(param_type, _AuthResult):
216
223
  continue
217
224
 
218
225
  # Convert FilePart
@@ -345,7 +352,7 @@ class LiteAgentExecutor(AgentExecutor):
345
352
  if asyncio.iscoroutinefunction(handler):
346
353
  return await handler(*args, **kwargs)
347
354
  else:
348
- loop = asyncio.get_event_loop()
355
+ loop = asyncio.get_running_loop()
349
356
  return await loop.run_in_executor(
350
357
  None,
351
358
  lambda: handler(*args, **kwargs)
a2a_lite/testing.py CHANGED
@@ -214,7 +214,16 @@ class AgentTestClient:
214
214
  result = await gen
215
215
  results.append(result)
216
216
 
217
- asyncio.run(run_handler())
217
+ # Handle both sync and async calling contexts
218
+ try:
219
+ asyncio.get_running_loop()
220
+ # Already in an async context — run in a separate thread
221
+ import concurrent.futures
222
+ with concurrent.futures.ThreadPoolExecutor(1) as pool:
223
+ pool.submit(asyncio.run, run_handler()).result()
224
+ except RuntimeError:
225
+ # No running loop — safe to use asyncio.run()
226
+ asyncio.run(run_handler())
218
227
  return results
219
228
 
220
229
 
a2a_lite/utils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Helper functions for A2A Lite.
3
3
  """
4
+ import typing
4
5
  from typing import Any, Dict, Type, get_origin, get_args, Union
5
6
  import inspect
6
7
 
@@ -103,7 +104,10 @@ def extract_function_schemas(func) -> tuple[Dict[str, Any], Dict[str, Any]]:
103
104
  Tuple of (input_schema, output_schema)
104
105
  """
105
106
  sig = inspect.signature(func)
106
- hints = getattr(func, '__annotations__', {})
107
+ try:
108
+ hints = typing.get_type_hints(func)
109
+ except Exception:
110
+ hints = getattr(func, '__annotations__', {})
107
111
 
108
112
  # Build input schema from parameters
109
113
  properties = {}
a2a_lite/webhooks.py CHANGED
@@ -9,7 +9,7 @@ Simplifies sending notifications when tasks complete:
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
- from dataclasses import dataclass
12
+ from dataclasses import dataclass, field
13
13
  from typing import Any, Callable, Dict, List, Optional
14
14
  import asyncio
15
15
  import json
@@ -19,15 +19,11 @@ import json
19
19
  class WebhookConfig:
20
20
  """Configuration for a webhook endpoint."""
21
21
  url: str
22
- headers: Dict[str, str] = None
22
+ headers: Dict[str, str] = field(default_factory=dict)
23
23
  retry_count: int = 3
24
24
  retry_delay: float = 1.0
25
25
  timeout: float = 30.0
26
26
 
27
- def __post_init__(self):
28
- if self.headers is None:
29
- self.headers = {}
30
-
31
27
 
32
28
  class WebhookClient:
33
29
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: a2a-lite
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Simplified wrapper for Google's A2A Protocol SDK
5
5
  Author: A2A Lite Contributors
6
6
  License-Expression: Apache-2.0
@@ -27,6 +27,8 @@ Provides-Extra: dev
27
27
  Requires-Dist: httpx>=0.25; extra == 'dev'
28
28
  Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
29
29
  Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Provides-Extra: oauth
31
+ Requires-Dist: pyjwt[crypto]>=2.0; extra == 'oauth'
30
32
  Description-Content-Type: text/markdown
31
33
 
32
34
  # A2A Lite - Python
@@ -0,0 +1,19 @@
1
+ a2a_lite/__init__.py,sha256=-uZfl-CoWKt4xFSmda3TEBmPB7pRA71u9C4HkK8Pavs,3520
2
+ a2a_lite/agent.py,sha256=OrIzIaJLv7O55G1jrSl81pNem9S7kD47QDOzxISAmw0,17793
3
+ a2a_lite/auth.py,sha256=A1AMncM0cuWEAcysjumAhjd0lI_jMLmJpeYWuAPj30A,10181
4
+ a2a_lite/cli.py,sha256=chLmUrWPxXmXU75LO8yhN5HP0rct3yN8W_i_txTvoJA,9043
5
+ a2a_lite/decorators.py,sha256=RDekZdDJQR8124zX0lvTinaU7iJdNjNHaNPop17_gmg,1116
6
+ a2a_lite/discovery.py,sha256=BxpiJAUDxIyI2gvsLhjmHte5c9ax5Qf1hbBQnyAmxLQ,4508
7
+ a2a_lite/executor.py,sha256=gC_9mzoGalUJMZUio-SQeXcoZvOU4LR69eK9ikncJwQ,13628
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=M9IbLA6oUz1DokJ9Sc_r0gK43NNkU78IVkiBRuDFFCU,9393
14
+ a2a_lite/utils.py,sha256=AFLYQ4J-F7H_HeYWAeg8H3p9EOdDv4dOpju_ebrU5PI,3934
15
+ a2a_lite/webhooks.py,sha256=TNhDlG84rrP_gC2pQ-op5xo01p5Z9sm_ZgQIrJRI7OY,6095
16
+ a2a_lite-0.2.2.dist-info/METADATA,sha256=o74kDuPcSpG1A82qVROqwqMMTTvR95vigXwtx64OeCU,12657
17
+ a2a_lite-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ a2a_lite-0.2.2.dist-info/entry_points.txt,sha256=BONfFqZbCntNal2iwlTJAE09gCUvurfvqslMYVYh4is,46
19
+ a2a_lite-0.2.2.dist-info/RECORD,,
@@ -1,19 +0,0 @@
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,,