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/human_loop.py DELETED
@@ -1,284 +0,0 @@
1
- """
2
- Human-in-the-loop support (OPTIONAL).
3
-
4
- Allows agents to ask users for clarification mid-task.
5
- Uses A2A's "input-required" task state.
6
-
7
- Example (simple - no human loop needed):
8
- @agent.skill("greet")
9
- async def greet(name: str) -> str:
10
- return f"Hello, {name}!"
11
-
12
- Example (with human-in-the-loop - opt-in):
13
- @agent.skill("book_flight")
14
- async def book_flight(destination: str, ctx: InteractionContext) -> str:
15
- # Ask user for date
16
- date = await ctx.ask("What date do you want to travel?")
17
-
18
- # Ask for confirmation with options
19
- confirm = await ctx.ask(
20
- f"Book flight to {destination} on {date}?",
21
- options=["Yes", "No", "Change date"]
22
- )
23
-
24
- if confirm == "Yes":
25
- return f"Booked flight to {destination} on {date}!"
26
- elif confirm == "Change date":
27
- new_date = await ctx.ask("Enter new date:")
28
- return f"Booked flight to {destination} on {new_date}!"
29
- else:
30
- return "Booking cancelled."
31
- """
32
- from __future__ import annotations
33
-
34
- from dataclasses import dataclass, field
35
- from typing import Any, Callable, Dict, List, Optional, Union
36
- from uuid import uuid4
37
- import asyncio
38
-
39
-
40
- @dataclass
41
- class InputRequest:
42
- """Request for user input."""
43
- id: str
44
- prompt: str
45
- options: Optional[List[str]] = None
46
- input_type: str = "text" # text, choice, confirm, file
47
- required: bool = True
48
- default: Optional[str] = None
49
- metadata: Dict[str, Any] = field(default_factory=dict)
50
-
51
- def to_dict(self) -> Dict[str, Any]:
52
- return {
53
- "id": self.id,
54
- "prompt": self.prompt,
55
- "options": self.options,
56
- "type": self.input_type,
57
- "required": self.required,
58
- "default": self.default,
59
- "metadata": self.metadata,
60
- }
61
-
62
-
63
- @dataclass
64
- class InputResponse:
65
- """User's response to input request."""
66
- request_id: str
67
- value: Any
68
- metadata: Dict[str, Any] = field(default_factory=dict)
69
-
70
-
71
- class InteractionContext:
72
- """
73
- Context for human-in-the-loop interactions.
74
-
75
- Passed to skills that need to ask users questions.
76
-
77
- Example:
78
- @agent.skill("wizard")
79
- async def wizard(ctx: InteractionContext) -> str:
80
- name = await ctx.ask("What's your name?")
81
- age = await ctx.ask("How old are you?", input_type="number")
82
- confirm = await ctx.confirm(f"Create profile for {name}, age {age}?")
83
-
84
- if confirm:
85
- return f"Created profile for {name}!"
86
- return "Cancelled."
87
- """
88
-
89
- def __init__(
90
- self,
91
- task_id: str,
92
- event_queue=None,
93
- response_handler: Optional[Callable] = None,
94
- ):
95
- self.task_id = task_id
96
- self._event_queue = event_queue
97
- self._response_handler = response_handler
98
- self._pending_requests: Dict[str, asyncio.Future] = {}
99
-
100
- async def ask(
101
- self,
102
- prompt: str,
103
- options: Optional[List[str]] = None,
104
- input_type: str = "text",
105
- required: bool = True,
106
- default: Optional[str] = None,
107
- timeout: Optional[float] = None,
108
- ) -> str:
109
- """
110
- Ask the user a question and wait for response.
111
-
112
- Args:
113
- prompt: Question to ask
114
- options: List of options (for choice type)
115
- input_type: "text", "choice", "number", "file"
116
- required: Whether input is required
117
- default: Default value if not required
118
- timeout: Timeout in seconds
119
-
120
- Returns:
121
- User's response
122
-
123
- Example:
124
- name = await ctx.ask("What's your name?")
125
- choice = await ctx.ask("Pick one:", options=["A", "B", "C"])
126
- """
127
- request = InputRequest(
128
- id=uuid4().hex,
129
- prompt=prompt,
130
- options=options,
131
- input_type="choice" if options else input_type,
132
- required=required,
133
- default=default,
134
- )
135
-
136
- # Create future for response
137
- future: asyncio.Future = asyncio.Future()
138
- self._pending_requests[request.id] = future
139
-
140
- # Send input-required event
141
- await self._send_input_request(request)
142
-
143
- # Wait for response
144
- try:
145
- if timeout:
146
- response = await asyncio.wait_for(future, timeout=timeout)
147
- else:
148
- response = await future
149
- return response.value
150
- except asyncio.TimeoutError:
151
- if default is not None:
152
- return default
153
- raise TimeoutError(f"No response received for: {prompt}")
154
- finally:
155
- self._pending_requests.pop(request.id, None)
156
-
157
- async def confirm(
158
- self,
159
- prompt: str,
160
- yes_label: str = "Yes",
161
- no_label: str = "No",
162
- ) -> bool:
163
- """
164
- Ask for yes/no confirmation.
165
-
166
- Example:
167
- if await ctx.confirm("Are you sure?"):
168
- do_something()
169
- """
170
- response = await self.ask(
171
- prompt,
172
- options=[yes_label, no_label],
173
- input_type="choice",
174
- )
175
- return response == yes_label
176
-
177
- async def choose(
178
- self,
179
- prompt: str,
180
- options: List[str],
181
- allow_multiple: bool = False,
182
- ) -> Union[str, List[str]]:
183
- """
184
- Ask user to choose from options.
185
-
186
- Example:
187
- color = await ctx.choose("Pick a color:", ["Red", "Blue", "Green"])
188
- """
189
- response = await self.ask(prompt, options=options, input_type="choice")
190
- if allow_multiple and isinstance(response, str):
191
- return [response]
192
- return response
193
-
194
- async def _send_input_request(self, request: InputRequest) -> None:
195
- """Send input-required event via SSE."""
196
- if self._event_queue:
197
- from a2a.utils import new_agent_text_message
198
- import json
199
-
200
- msg = json.dumps({
201
- "_type": "input_required",
202
- "task_id": self.task_id,
203
- "request": request.to_dict(),
204
- })
205
- await self._event_queue.enqueue_event(new_agent_text_message(msg))
206
-
207
- def handle_response(self, request_id: str, value: Any) -> bool:
208
- """
209
- Handle incoming response from user.
210
-
211
- Called by the executor when user responds.
212
- """
213
- future = self._pending_requests.get(request_id)
214
- if future and not future.done():
215
- response = InputResponse(request_id=request_id, value=value)
216
- future.set_result(response)
217
- return True
218
- return False
219
-
220
-
221
- class ConversationMemory:
222
- """
223
- Simple conversation memory for multi-turn interactions.
224
-
225
- Example:
226
- @agent.skill("chat")
227
- async def chat(message: str, memory: ConversationMemory) -> str:
228
- # Get conversation history
229
- history = memory.get_messages()
230
-
231
- # Add user message
232
- memory.add_user(message)
233
-
234
- # Generate response (using history for context)
235
- response = await generate_response(message, history)
236
-
237
- # Add assistant response
238
- memory.add_assistant(response)
239
-
240
- return response
241
- """
242
-
243
- def __init__(self, max_messages: int = 100):
244
- self._messages: List[Dict[str, str]] = []
245
- self._max_messages = max_messages
246
- self._metadata: Dict[str, Any] = {}
247
-
248
- def add_user(self, content: str) -> None:
249
- """Add user message."""
250
- self._add_message("user", content)
251
-
252
- def add_assistant(self, content: str) -> None:
253
- """Add assistant message."""
254
- self._add_message("assistant", content)
255
-
256
- def add_system(self, content: str) -> None:
257
- """Add system message."""
258
- self._add_message("system", content)
259
-
260
- def _add_message(self, role: str, content: str) -> None:
261
- self._messages.append({"role": role, "content": content})
262
- # Trim if over limit
263
- if len(self._messages) > self._max_messages:
264
- self._messages = self._messages[-self._max_messages:]
265
-
266
- def get_messages(self) -> List[Dict[str, str]]:
267
- """Get all messages."""
268
- return list(self._messages)
269
-
270
- def get_last(self, n: int = 10) -> List[Dict[str, str]]:
271
- """Get last n messages."""
272
- return self._messages[-n:]
273
-
274
- def clear(self) -> None:
275
- """Clear all messages."""
276
- self._messages.clear()
277
-
278
- def set_metadata(self, key: str, value: Any) -> None:
279
- """Set metadata."""
280
- self._metadata[key] = value
281
-
282
- def get_metadata(self, key: str, default: Any = None) -> Any:
283
- """Get metadata."""
284
- return self._metadata.get(key, default)
a2a_lite/webhooks.py DELETED
@@ -1,228 +0,0 @@
1
- """
2
- Webhook and push notification support for A2A Lite.
3
-
4
- Simplifies sending notifications when tasks complete:
5
-
6
- @agent.on_complete
7
- async def notify(task_id, result):
8
- await webhook.post(callback_url, result)
9
- """
10
- from __future__ import annotations
11
-
12
- from dataclasses import dataclass, field
13
- from typing import Any, Callable, Dict, List, Optional
14
- import asyncio
15
- import json
16
-
17
-
18
- @dataclass
19
- class WebhookConfig:
20
- """Configuration for a webhook endpoint."""
21
- url: str
22
- headers: Dict[str, str] = field(default_factory=dict)
23
- retry_count: int = 3
24
- retry_delay: float = 1.0
25
- timeout: float = 30.0
26
-
27
-
28
- class WebhookClient:
29
- """
30
- Simple webhook client for sending notifications.
31
-
32
- Example:
33
- webhook = WebhookClient()
34
-
35
- # Send a notification
36
- await webhook.post("https://example.com/callback", {"status": "done"})
37
-
38
- # With retries
39
- await webhook.post(url, data, retry=3)
40
- """
41
-
42
- def __init__(self, default_headers: Optional[Dict[str, str]] = None):
43
- self.default_headers = default_headers or {}
44
-
45
- async def post(
46
- self,
47
- url: str,
48
- data: Any,
49
- headers: Optional[Dict[str, str]] = None,
50
- retry: int = 0,
51
- timeout: float = 30.0,
52
- ) -> Dict[str, Any]:
53
- """
54
- Send a POST request to a webhook URL.
55
-
56
- Args:
57
- url: The webhook URL
58
- data: Data to send (will be JSON serialized)
59
- headers: Additional headers
60
- retry: Number of retries on failure
61
- timeout: Request timeout in seconds
62
-
63
- Returns:
64
- Response data
65
- """
66
- import httpx
67
-
68
- all_headers = {**self.default_headers, **(headers or {})}
69
- all_headers.setdefault("Content-Type", "application/json")
70
-
71
- last_error = None
72
- for attempt in range(retry + 1):
73
- try:
74
- async with httpx.AsyncClient(timeout=timeout) as client:
75
- response = await client.post(
76
- url,
77
- json=data if isinstance(data, (dict, list)) else {"data": data},
78
- headers=all_headers,
79
- )
80
- response.raise_for_status()
81
- return {
82
- "status": response.status_code,
83
- "success": True,
84
- "data": response.json() if response.content else None,
85
- }
86
- except Exception as e:
87
- last_error = e
88
- if attempt < retry:
89
- await asyncio.sleep(1.0 * (attempt + 1))
90
-
91
- return {
92
- "success": False,
93
- "error": str(last_error),
94
- "attempts": retry + 1,
95
- }
96
-
97
- async def notify(
98
- self,
99
- config: WebhookConfig,
100
- event: str,
101
- data: Any,
102
- ) -> Dict[str, Any]:
103
- """
104
- Send a notification using a WebhookConfig.
105
-
106
- Args:
107
- config: Webhook configuration
108
- event: Event name
109
- data: Event data
110
-
111
- Returns:
112
- Response data
113
- """
114
- payload = {
115
- "event": event,
116
- "data": data,
117
- }
118
- return await self.post(
119
- config.url,
120
- payload,
121
- headers=config.headers,
122
- retry=config.retry_count,
123
- timeout=config.timeout,
124
- )
125
-
126
-
127
- class NotificationManager:
128
- """
129
- Manages push notifications for agent tasks.
130
-
131
- Example:
132
- notifications = NotificationManager()
133
-
134
- # Register a callback URL for a task
135
- notifications.register("task-123", "https://example.com/callback")
136
-
137
- # Later, when task completes
138
- await notifications.notify_complete("task-123", result)
139
- """
140
-
141
- def __init__(self):
142
- self._callbacks: Dict[str, WebhookConfig] = {}
143
- self._client = WebhookClient()
144
-
145
- def register(
146
- self,
147
- task_id: str,
148
- callback_url: str,
149
- headers: Optional[Dict[str, str]] = None,
150
- ) -> None:
151
- """Register a callback URL for a task."""
152
- self._callbacks[task_id] = WebhookConfig(
153
- url=callback_url,
154
- headers=headers,
155
- )
156
-
157
- def unregister(self, task_id: str) -> None:
158
- """Remove a callback registration."""
159
- self._callbacks.pop(task_id, None)
160
-
161
- async def notify_complete(
162
- self,
163
- task_id: str,
164
- result: Any,
165
- ) -> Optional[Dict[str, Any]]:
166
- """
167
- Notify that a task has completed.
168
-
169
- Args:
170
- task_id: The task ID
171
- result: The task result
172
-
173
- Returns:
174
- Webhook response or None if no callback registered
175
- """
176
- config = self._callbacks.get(task_id)
177
- if not config:
178
- return None
179
-
180
- response = await self._client.notify(
181
- config,
182
- event="task.completed",
183
- data={"task_id": task_id, "result": result},
184
- )
185
-
186
- # Clean up after notification
187
- self.unregister(task_id)
188
- return response
189
-
190
- async def notify_error(
191
- self,
192
- task_id: str,
193
- error: str,
194
- ) -> Optional[Dict[str, Any]]:
195
- """Notify that a task has failed."""
196
- config = self._callbacks.get(task_id)
197
- if not config:
198
- return None
199
-
200
- response = await self._client.notify(
201
- config,
202
- event="task.failed",
203
- data={"task_id": task_id, "error": error},
204
- )
205
-
206
- self.unregister(task_id)
207
- return response
208
-
209
- async def notify_progress(
210
- self,
211
- task_id: str,
212
- progress: float,
213
- message: Optional[str] = None,
214
- ) -> Optional[Dict[str, Any]]:
215
- """Notify task progress (0.0 to 1.0)."""
216
- config = self._callbacks.get(task_id)
217
- if not config:
218
- return None
219
-
220
- return await self._client.notify(
221
- config,
222
- event="task.progress",
223
- data={
224
- "task_id": task_id,
225
- "progress": progress,
226
- "message": message,
227
- },
228
- )
@@ -1,19 +0,0 @@
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,,