smarta2a 0.4.26__py3-none-any.whl → 0.4.29__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.
@@ -37,7 +37,7 @@ class A2AHuman:
37
37
 
38
38
  @self.app.on_send_task(forward_to_webhook=True)
39
39
  async def on_send_task(request: SendTaskRequest, state: StateData):
40
- return "I am human and will take some time to respond, please standby"
40
+ return "I am human and take some time to respond, but I will definitely respond to your request"
41
41
 
42
42
  @self.app.webhook()
43
43
  async def on_webhook(request: WebhookRequest, state: StateData):
@@ -228,104 +228,11 @@ class OpenAIProvider(BaseLLMProvider):
228
228
  raise RuntimeError("Max tool iteration depth reached in generate().")
229
229
 
230
230
 
231
- async def generate_stream(self, state: StateData, **kwargs) -> AsyncGenerator[str, None]:
231
+ async def stream(self, state: StateData, **kwargs) -> AsyncGenerator[str, None]:
232
232
  """
233
- Stream response chunks, invoking tools as needed.
233
+ Stream a response, invoking tools as needed.
234
234
  """
235
- context_history = state.context_history
236
- # Normalize incoming messages to your Message model
237
- msgs = [
238
- msg if isinstance(msg, Message) else Message(**msg)
239
- for msg in context_history
240
- ]
241
- # Convert to OpenAI schema, including any prior tool results
242
- converted_messages = self._convert_messages(msgs)
243
- max_iterations = 30
244
-
245
- for _ in range(max_iterations):
246
- # Kick off the streaming completion
247
- stream = await self.client.chat.completions.create(
248
- model=self.model,
249
- messages=converted_messages,
250
- tools=self._format_openai_tools(),
251
- tool_choice="auto",
252
- stream=True,
253
- **kwargs
254
- )
255
-
256
- full_content = ""
257
- tool_calls: List[Dict[str, Any]] = []
258
-
259
- # As chunks arrive, yield them and collect any tool_call deltas
260
- async for chunk in stream:
261
- delta = chunk.choices[0].delta
262
-
263
- # 1) Stream content immediately
264
- if hasattr(delta, "content") and delta.content:
265
- yield delta.content
266
- full_content += delta.content
267
-
268
- # 2) Buffer up any function/tool calls for after the stream
269
- if hasattr(delta, "tool_calls") and delta.tool_calls:
270
- for d in delta.tool_calls:
271
- idx = d.index
272
- # Ensure list is long enough
273
- while len(tool_calls) <= idx:
274
- tool_calls.append({
275
- "id": "",
276
- "function": {"name": "", "arguments": ""}
277
- })
278
- if d.id:
279
- tool_calls[idx]["id"] = d.id
280
- if d.function.name:
281
- tool_calls[idx]["function"]["name"] = d.function.name
282
- if d.function.arguments:
283
- tool_calls[idx]["function"]["arguments"] += d.function.arguments
284
-
285
- # If the assistant didn't invoke any tools, we're done
286
- if not tool_calls:
287
- return
288
-
289
- # Otherwise, append the assistant's outgoing call and loop for tool execution
290
- converted_messages.append({
291
- "role": "assistant",
292
- "content": full_content,
293
- "tool_calls": [
294
- {
295
- "id": tc["id"],
296
- "type": "function",
297
- "function": {
298
- "name": tc["function"]["name"],
299
- "arguments": tc["function"]["arguments"]
300
- }
301
- }
302
- for tc in tool_calls
303
- ]
304
- })
305
-
306
- # Execute each tool in turn and append its result
307
- for tc in tool_calls:
308
- name = tc["function"]["name"]
309
- try:
310
- args = json.loads(tc["function"]["arguments"] or "{}")
311
- except json.JSONDecodeError:
312
- args = {}
313
- try:
314
- tool_res = await self.tools_manager.call_tool(name, args)
315
- result_content = getattr(tool_res, "content", None) or (
316
- tool_res.get("content") if isinstance(tool_res, dict) else str(tool_res)
317
- )
318
- except Exception as e:
319
- result_content = f"Error executing {name}: {e}"
320
-
321
- converted_messages.append({
322
- "role": "tool",
323
- "content": result_content,
324
- "tool_call_id": tc["id"]
325
- })
326
-
327
- raise RuntimeError("Max tool iteration depth reached in generate_stream().")
328
-
235
+ pass
329
236
 
330
237
 
331
238
 
@@ -153,13 +153,6 @@ class RequestHandler:
153
153
  push_notification_config=push_notification_config if push_notification_config else state_data.push_notification_config,
154
154
  )
155
155
  )
156
-
157
- # If push_notification_config is set send the task to the push notification url
158
- if push_notification_config and forward_to_webhook:
159
- try:
160
- await self.a2a_aclient.send_to_webhook(webhook_url=push_notification_config.url,id=task_id,task=task.model_dump())
161
- except Exception as e:
162
- pass
163
156
 
164
157
 
165
158
  # Send the task back to the client
smarta2a/server/server.py CHANGED
@@ -108,7 +108,7 @@ class SmartA2A:
108
108
  # <-- Accept both SSE‐style responses:
109
109
  if isinstance(response, (EventSourceResponse, StreamingResponse)):
110
110
  return response
111
-
111
+ print(response)
112
112
  # <-- Everything else is a normal pydantic JSONRPCResponse
113
113
  return response.model_dump()
114
114
 
@@ -124,9 +124,6 @@ class SmartA2A:
124
124
  async def handle_webhook(request: Request):
125
125
  try:
126
126
  data = await request.json()
127
- print("--- In handle_webhook in server.py ---")
128
- print(data)
129
- print("--- end of handle_webhook in server.py ---")
130
127
  req = WebhookRequest.model_validate(data)
131
128
  except Exception as e:
132
129
  return WebhookResponse(accepted=False, error=str(e)).model_dump()
@@ -7,7 +7,7 @@ from smarta2a.state_stores.base_state_store import BaseStateStore
7
7
  from smarta2a.file_stores.base_file_store import BaseFileStore
8
8
  from smarta2a.history_update_strategies.history_update_strategy import HistoryUpdateStrategy
9
9
  from smarta2a.utils.types import Message, StateData, Task, TaskStatus, TaskState, PushNotificationConfig, Part, FilePart
10
- from smarta2a.server.nats_client import NATSClient
10
+ from smarta2a.client.nats_client import NATSClient
11
11
 
12
12
  class StateManager:
13
13
  def __init__(self, state_store: BaseStateStore, file_store: BaseFileStore, history_strategy: HistoryUpdateStrategy, nats_server_url: Optional[str] = "nats://localhost:4222", push_notification_config: Optional[PushNotificationConfig] = None):
@@ -112,7 +112,7 @@ class StateManager:
112
112
  for artifact in result.artifacts or []:
113
113
  if artifact.parts:
114
114
  artifact_message = Message(
115
- role="agent",
115
+ role="tool",
116
116
  parts=artifact.parts,
117
117
  metadata=artifact.metadata
118
118
  )
@@ -132,7 +132,9 @@ class StateManager:
132
132
 
133
133
  # Merge artifacts
134
134
  if result.artifacts:
135
- updated_state.task.artifacts = result.artifacts.copy()
135
+ if updated_state.task.artifacts is None:
136
+ updated_state.task.artifacts = []
137
+ updated_state.task.artifacts.extend(result.artifacts)
136
138
 
137
139
  # Merge metadata
138
140
  if result.metadata:
@@ -1,145 +1,73 @@
1
1
  # Library imports
2
2
  from typing import Callable, Any, Optional
3
- import copy
4
- from uuid import uuid4
5
3
 
6
4
  # Local imports
7
5
  from smarta2a.server.state_manager import StateManager
8
- from smarta2a.utils.types import WebhookRequest, WebhookResponse, StateData, Message, TaskState
9
- from smarta2a.utils.task_builder import TaskBuilder
6
+ from smarta2a.utils.types import WebhookRequest, WebhookResponse, StateData, Message
10
7
  from smarta2a.client.a2a_client import A2AClient
11
8
 
12
9
  class WebhookRequestProcessor:
13
10
  def __init__(self, webhook_fn: Callable[[WebhookRequest], Any], state_manager: Optional[StateManager] = None):
14
11
  self.webhook_fn = webhook_fn
15
12
  self.state_manager = state_manager
16
- self.task_builder = TaskBuilder(default_status=TaskState.COMPLETED)
17
13
  self.a2a_aclient = A2AClient()
18
14
 
19
15
  async def process_request(self, request: WebhookRequest) -> WebhookResponse:
20
16
  if self.state_manager:
21
- state_data = await self.state_manager.get_and_update_state_from_webhook(request.id, request.result)
17
+ state_data = self.state_manager.get_and_update_state_from_webhook(request.id, request.result)
22
18
  return await self._webhook_handler(request, state_data)
23
19
  else:
24
20
  return await self._webhook_handler(request)
25
-
21
+
26
22
 
27
23
  async def _webhook_handler(self, request: WebhookRequest, state_data: Optional[StateData] = None) -> WebhookResponse:
28
- print("--- _webhook_handler ---")
29
- print(request)
30
- print("--- end of _webhook_handler ---")
31
- print("--- state_data ---")
32
- print(state_data)
33
- print("--- end of state_data ---")
34
24
  try:
35
- # Extract parameters from request
36
- task_id = request.id
37
- task = request.result
25
+ # --- Step 1: Process Incoming Task ---
26
+ if request.result:
27
+ incoming_task = request.result
28
+
29
+ # Initialize state_data if missing
30
+ if not state_data:
31
+ state_data = StateData(
32
+ task_id=incoming_task.id,
33
+ task=incoming_task.copy(update={"artifacts": incoming_task.artifacts}),
34
+ context_history=[],
35
+ push_notification_config=None
36
+ )
38
37
 
39
- if state_data:
40
- session_id = task.sessionId if task and task.sessionId else state_data.task.sessionId
41
- task_history = task.history if task and task.history is not None else state_data.task.history.copy() if state_data.task.history else []
42
- context_history = state_data.context_history.copy()
43
- metadata = task.metadata if task and task.metadata is not None else state_data.task.metadata.copy() if state_data.task.metadata else {}
44
- # Deep copy of push_notification_config
45
- push_notification_config = copy.deepcopy(state_data.push_notification_config) if state_data.push_notification_config else None
46
- else:
47
- # No state_data so just assign based on task from the request
48
- session_id = task.sessionId if task and task.sessionId else str(uuid4())
49
- task_history = task.history if task and task.history else []
50
- context_history = []
51
- metadata = task.metadata if task and task.metadata else {}
52
- push_notification_config = None
53
-
54
- # Call webhook handler
55
- if state_data:
56
- # Call webhook_fn with state_data
57
- raw_result = await self.webhook_fn(request, state_data)
58
- else:
59
- # Call webhook_fn with request
60
- raw_result = await self.webhook_fn(request)
61
-
62
- print("--- raw_result ---")
63
- print(raw_result)
64
- print("--- end of raw_result ---")
65
-
66
- # Handle direct WebhookResponse returns
67
- if isinstance(raw_result, WebhookResponse):
68
- return raw_result
69
-
70
- # Process webhook_response in a way that is similar to handle_send_task
71
- # Build task with updated history
72
- updated_task = self.task_builder.build(
73
- content=raw_result,
74
- task_id=task_id,
75
- session_id=session_id,
76
- metadata=metadata,
77
- history=task_history
78
- )
79
-
80
- # Process messages through strategy (similar to handle_send_task)
81
- messages = []
82
- if updated_task.artifacts:
83
- agent_parts = [p for a in updated_task.artifacts for p in a.parts]
84
- agent_message = Message(
85
- role="agent",
86
- parts=agent_parts,
87
- metadata=updated_task.metadata
88
- )
89
- messages.append(agent_message)
38
+ # --- Step 2: Call Webhook Function ---
39
+ webhook_response = await self.webhook_fn(request, state_data) if state_data else await self.webhook_fn(request)
90
40
 
91
- # Update Task history with a simple append
92
- task_history.extend(messages)
93
-
94
- if state_data:
95
- # Update context history with a strategy
96
- history_strategy = self.state_manager.get_history_strategy()
97
- context_history = history_strategy.update_history(
98
- existing_history=context_history,
99
- new_messages=messages
100
- )
101
-
102
- print("--- task_history ---")
103
- print(task_history)
104
- print("--- end of task_history ---")
105
- print("--- context_history ---")
106
- print(context_history)
107
- print("--- end of context_history ---")
108
- # Update task with final state
109
- updated_task.history = task_history
110
-
111
- # State store update (if enabled)
112
- if state_data:
113
- await self.state_manager.update_state(
114
- state_data=StateData(
115
- task_id=task_id,
116
- task=updated_task,
117
- context_history=context_history,
118
- push_notification_config=push_notification_config,
41
+ # --- Step 3: Process Webhook Response ---
42
+ if webhook_response.result:
43
+ updated_task = webhook_response.result
44
+ existing_task = state_data.task
45
+
46
+ # Overwrite artifacts from response
47
+ existing_task.artifacts = updated_task.artifacts.copy() if updated_task.artifacts else []
48
+
49
+ # Merge metadata
50
+ existing_task.metadata = {**(existing_task.metadata or {}), **(updated_task.metadata or {})}
51
+
52
+ # Build messages from updated artifacts
53
+ updated_parts = [part for artifact in updated_task.artifacts for part in artifact.parts] if updated_task.artifacts else []
54
+ updated_messages = [Message(role="agent", parts=updated_parts, metadata=updated_task.metadata)]
55
+
56
+ # Update context history again
57
+ if self.state_manager:
58
+ history_strategy = self.state_manager.get_history_strategy()
59
+ state_data.context_history = history_strategy.update_history(
60
+ existing_history=state_data.context_history,
61
+ new_messages=updated_messages
119
62
  )
120
- )
121
- print("--- push_notification_config ---")
122
- print(push_notification_config)
123
- print("--- end of push_notification_config ---")
124
- # If push_notification_config is set send the task to the push notification url
125
- if push_notification_config:
126
- try:
127
- await self.a2a_aclient.send_to_webhook(webhook_url=push_notification_config.url,id=task_id,task=updated_task.model_dump())
128
- except Exception as e:
129
- pass
63
+ await self.state_manager.update_state(state_data)
130
64
 
131
- # Return the updated task
65
+
66
+ # --- Step 5: Return Final Response ---
132
67
  return WebhookResponse(
133
68
  id=request.id,
134
- result=updated_task
69
+ result=state_data.task if state_data else None
135
70
  )
136
-
71
+
137
72
  except Exception as e:
138
- print("--- EXCEPTION CAUGHT ---")
139
- print(e)
140
- print("--- END OF EXCEPTION CAUGHT ---")
141
- # Handle exceptions
142
- return WebhookResponse(
143
- id=request.id,
144
- error=str(e)
145
- )
73
+ return WebhookResponse(id=request.id, error=f"Internal error: {str(e)}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smarta2a
3
- Version: 0.4.26
3
+ Version: 0.4.29
4
4
  Summary: a Python framework that helps you build servers and AI agents that communicate using the A2A protocol
5
5
  Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
6
6
  Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
@@ -64,7 +64,7 @@ SmartA2A makes it easy to build interoperable, communication-ready AI systems—
64
64
  ## Installation
65
65
 
66
66
  ```bash
67
- pip install -U smarta2a
67
+ pip install -U smarta2a==0.3.1
68
68
  ```
69
69
 
70
70
  ## Examples
@@ -389,16 +389,6 @@ This example demonstrates:
389
389
 
390
390
  ---
391
391
 
392
- ## Roadmap
393
-
394
- There's still a lot more to come ...
395
-
396
- - Documentation site with tutorials, explanation of concepts, blog and a contribution guide
397
- - Support for more LLM providers
398
- - Support for more memory types
399
- - Authorization and observability framework built-in
400
- - Many more !
401
-
402
392
  ## License
403
393
 
404
394
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,6 +1,6 @@
1
1
  smarta2a/__init__.py,sha256=T_EECYqWrxshix0FbgUv22zlKRX22HFU-HKXcYTOb3w,175
2
2
  smarta2a/agent/a2a_agent.py,sha256=EurcxpV14e3OPWCMutYL0EXMHb5ZKQqAHEGZZF6pNgg,1892
3
- smarta2a/agent/a2a_human.py,sha256=Bz4M9rhEwQEtIdDnV7VTL2n14nTT2wACyPBhDMph4FQ,1645
3
+ smarta2a/agent/a2a_human.py,sha256=yAW9naxyqStQhWwDamXRZIlw-mT_7RW2wcHyhy3rR6Y,1671
4
4
  smarta2a/agent/a2a_mcp_server.py,sha256=X_mxkgYgCA_dSNtCvs0rSlOoWYc-8d3Qyxv0e-a7NKY,1015
5
5
  smarta2a/archive/smart_mcp_client.py,sha256=0s2OWFKWSv-_UF7rb9fOrsh1OIYsYOsGukkXXp_E1cU,4158
6
6
  smarta2a/archive/subscription_service.py,sha256=vftmZD94HbdjPFa_1UBvsBm-WkW-s3ZCVq60fF7OCgA,4109
@@ -8,6 +8,7 @@ smarta2a/archive/task_service.py,sha256=ptf-oMHy98Rw4XSxyK1-lpqc1JtkCkEEHTmwAaun
8
8
  smarta2a/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  smarta2a/client/a2a_client.py,sha256=apDkKFtq61T79LpkbkzVTKWA0mSjR_eTNdGPUYozyvk,12100
10
10
  smarta2a/client/mcp_client.py,sha256=JeXhBqxM9TYAArpExLRtEr3lZeQZMcnTmGFl6XKsdu8,3797
11
+ smarta2a/client/nats_client.py,sha256=K97e8awvAxsqgs3BxsTZU_mB3JPPZ0yjLX1LnZZjfXE,1450
11
12
  smarta2a/file_stores/base_file_store.py,sha256=fcwFIOoFjLQiIKb8lIRVujnV6udyuI9Dq8cEc2ldmIQ,591
12
13
  smarta2a/file_stores/local_file_store.py,sha256=4GLDrsKxSoLWn2Oha4OD-P2r5vBpfV-8ePvZ5bhP1e8,2616
13
14
  smarta2a/history_update_strategies/__init__.py,sha256=x5WtiE9rG5ze8d8hA6E6wJOciBhWHa_ZgGgoIAZcXEQ,213
@@ -16,16 +17,14 @@ smarta2a/history_update_strategies/history_update_strategy.py,sha256=n2sfIGu8ztK
16
17
  smarta2a/history_update_strategies/rolling_window_strategy.py,sha256=7Ch042JWt4TM_r1-sFKlSIxHj8VX1P3ZoqjCvIdeSqA,540
17
18
  smarta2a/model_providers/__init__.py,sha256=hJj0w00JjqTiBgJmHmOWwL6MU_hwmro9xTiX3XYf6ts,148
18
19
  smarta2a/model_providers/base_llm_provider.py,sha256=iQUqjnypl0f2M929iU0WhHoxAE4ek-NUFJPbEnNQ8-4,412
19
- smarta2a/model_providers/openai_provider.py,sha256=cqAAhoImHJtBVOGeIERphXqiNlq6MSe_p3-3gImhAeM,12221
20
+ smarta2a/model_providers/openai_provider.py,sha256=iL4qgRhQQs1hFT2KblAYoS-4UUQL8z2z5YLGzfaRDfw,8348
20
21
  smarta2a/server/__init__.py,sha256=f2X454Ll4vJc02V4JLJHTN-h8u0TBm4d_FkiO4t686U,53
21
22
  smarta2a/server/handler_registry.py,sha256=OVRG5dTvxB7qUNXgsqWxVNxIyRljUShSYxb1gtbi5XM,820
22
23
  smarta2a/server/json_rpc_request_processor.py,sha256=qRB3sfj_n9ImkIOCdaUKMsDmKcO7CiMhaZ4VdQS7Mb4,6993
23
- smarta2a/server/nats_client.py,sha256=K97e8awvAxsqgs3BxsTZU_mB3JPPZ0yjLX1LnZZjfXE,1450
24
- smarta2a/server/request_handler.py,sha256=tUAoxkrJtOnLPJxdUDMThGPFAxYnNFFofS3QD80g_8M,26184
25
- smarta2a/server/send_task_handler.py,sha256=fiBeCCHCu9c2H4EJOUc0t3EZgpHVFJy4B_6qZOC140s,6336
26
- smarta2a/server/server.py,sha256=L0vh1i-s8bAQKarTPkqzZRdZuqFUjoqeGOCfo8wUvgU,6782
27
- smarta2a/server/state_manager.py,sha256=SKXK02g0o5-r3lYOJdCQaciYUnFTMGtB5MFdfgTc_ug,7962
28
- smarta2a/server/webhook_request_processor.py,sha256=EVIU6J6gd5eCl-QJER-dDxd8dSczFfXN7o_qA-fD5sc,6387
24
+ smarta2a/server/request_handler.py,sha256=zw3nRHZCX0xBx0x0sK5t8GjJRwOtUil-uVYhD3J0R7E,25807
25
+ smarta2a/server/server.py,sha256=0qpIQ1KXTUtFygcl-2SPIVTRVooobW_EmdXq83wvAPw,6637
26
+ smarta2a/server/state_manager.py,sha256=fdMOypRpaHaaexlMo0lk_MQX_FGYNzOLqVB0a4efiug,8063
27
+ smarta2a/server/webhook_request_processor.py,sha256=XQUsfOhXwDiKOdpRa7BmN2C5tgBbgtXATgzA4sM38dU,3431
29
28
  smarta2a/state_stores/__init__.py,sha256=vafxAqpwvag_cYFH2XKGk3DPmJIWJr4Ioey30yLFkVQ,220
30
29
  smarta2a/state_stores/base_state_store.py,sha256=_3LInM-qepKwwdypJTDNs9-DozBNrKVycwPwUm7bYdU,512
31
30
  smarta2a/state_stores/inmemory_state_store.py,sha256=nEBBUiiqhEluP2MYJjFUImcjIwLJEvL8BWwMbLCb8Fw,1268
@@ -36,7 +35,7 @@ smarta2a/utils/task_builder.py,sha256=wqSyfVHNTaXuGESu09dhlaDi7D007gcN3-8tH-nPQ4
36
35
  smarta2a/utils/task_request_builder.py,sha256=6cOGOqj2Rg43xWM03GRJQzlIZHBptsMCJRp7oD-TDAQ,3362
37
36
  smarta2a/utils/tools_manager.py,sha256=oR5cbwzPZ36hQAsWAgb-c6wFv5BthmCPraD7DSv-Bv8,4332
38
37
  smarta2a/utils/types.py,sha256=kzA6Vv5xXfu1sJuxhEXrglI9e9S6eZVIljMnsrQVyN0,13650
39
- smarta2a-0.4.26.dist-info/METADATA,sha256=iZsYh0J38iorZ7XeHEMJuStHsHbYRrAT5ujtdoSjcf0,13051
40
- smarta2a-0.4.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- smarta2a-0.4.26.dist-info/licenses/LICENSE,sha256=lDbqrxVnzDMY5KJ8JS1WhvkWE8TJaw-O-CHDy-ecsJA,2095
42
- smarta2a-0.4.26.dist-info/RECORD,,
38
+ smarta2a-0.4.29.dist-info/METADATA,sha256=dTQkVsjbmETIm4CE45NwU0TtdD7-9UCh7QX3UPDb7RI,12783
39
+ smarta2a-0.4.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
+ smarta2a-0.4.29.dist-info/licenses/LICENSE,sha256=lDbqrxVnzDMY5KJ8JS1WhvkWE8TJaw-O-CHDy-ecsJA,2095
41
+ smarta2a-0.4.29.dist-info/RECORD,,
@@ -1,174 +0,0 @@
1
- # Library imports
2
- from typing import Optional
3
- from uuid import uuid4
4
- import json
5
- from pydantic import ValidationError
6
-
7
- # Local imports
8
- from smarta2a.utils.types import (
9
- JSONRPCRequest,
10
- SendTaskRequest,
11
- SendTaskResponse,
12
- StateData,
13
- Task,
14
- TaskStatus,
15
- TaskState,
16
- Message,
17
- JSONRPCError,
18
- InvalidRequestError,
19
- JSONParseError,
20
- InternalError,
21
- MethodNotFoundError
22
- )
23
-
24
- class SendTaskHandler:
25
-
26
- @classmethod
27
- async def _handle_send_task(self, request_data: JSONRPCRequest, state_data: Optional[StateData] = None) -> SendTaskResponse:
28
- try:
29
- # Validate request format
30
- request = SendTaskRequest.model_validate(request_data.model_dump())
31
- handler = self.registry.get_handler("tasks/send")
32
-
33
- if not handler:
34
- return SendTaskResponse(
35
- id=request.id,
36
- error=MethodNotFoundError()
37
- )
38
-
39
- # Extract parameters from request
40
- task_id = request.params.id
41
- session_id = request.params.sessionId or str(uuid4())
42
- raw = request.params.message
43
- user_message = Message.model_validate(raw)
44
- request_metadata = request.params.metadata or {}
45
-
46
- if state_data:
47
- task_history = state_data.task.history.copy() or []
48
- context_history = state_data.context_history.copy() or []
49
- metadata = state_data.task.metadata or {}
50
-
51
- # Call handler with state data
52
- raw_result = await handler(request, state_data)
53
-
54
- # Handle direct SendTaskResponse returns
55
- if isinstance(raw_result, SendTaskResponse):
56
- return raw_result
57
-
58
- # Build task with updated history (before agent response)
59
- task = self.task_builder.build(
60
- content=raw_result,
61
- task_id=task_id,
62
- session_id=session_id,
63
- metadata=metadata,
64
- history=task_history
65
- )
66
-
67
- # Process messages through strategy
68
- messages = []
69
- if task.artifacts:
70
- agent_parts = [p for a in task.artifacts for p in a.parts]
71
- agent_message = Message(
72
- role="agent",
73
- parts=agent_parts,
74
- metadata=task.metadata
75
- )
76
- messages.append(agent_message)
77
-
78
- # Update Task history with a simple append
79
- task_history.extend(messages)
80
-
81
- # Update context history with a strategy - this is the history that will be passed to an LLM call
82
- history_strategy = self.state_mgr.get_history_strategy()
83
- context_history = history_strategy.update_history(
84
- existing_history=context_history,
85
- new_messages=messages
86
- )
87
-
88
- # Update task with final state
89
- task.history = task_history
90
-
91
- # State store update (if enabled)
92
- if self.state_mgr:
93
- state_store = self.state_mgr.get_store()
94
- state_store.update_state(
95
- task_id=task_id,
96
- state_data=StateData(
97
- task_id=task_id,
98
- task=task,
99
- context_history=context_history,
100
- metadata=metadata # Use merged metadata
101
- )
102
- )
103
-
104
- else:
105
- # There is no state manager, so we need to build a task from scratch
106
- task = Task(
107
- id=task_id,
108
- sessionId=session_id,
109
- status=TaskStatus(state=TaskState.WORKING),
110
- history=[user_message],
111
- metadata=request_metadata
112
- )
113
- task_history = task.history.copy()
114
- metadata = request_metadata.copy()
115
-
116
- # Call handler without state data
117
- raw_result = await handler(request)
118
-
119
- # Handle direct SendTaskResponse returns
120
- if isinstance(raw_result, SendTaskResponse):
121
- return raw_result
122
-
123
- # Build task with updated history (before agent response)
124
- task = self.task_builder.build(
125
- content=raw_result,
126
- task_id=task_id,
127
- session_id=session_id,
128
- metadata=metadata,
129
- history=task_history
130
- )
131
-
132
- # Process messages through strategy
133
- messages = []
134
- if task.artifacts:
135
- agent_parts = [p for a in task.artifacts for p in a.parts]
136
- agent_message = Message(
137
- role="agent",
138
- parts=agent_parts,
139
- metadata=task.metadata
140
- )
141
- messages.append(agent_message)
142
-
143
- # Update Task history with a simple append
144
- task_history.extend(messages)
145
-
146
- # Update task with final state
147
- task.history = task_history
148
-
149
-
150
- return SendTaskResponse(
151
- id=request.id,
152
- result=task
153
- )
154
- except ValidationError as e:
155
- return SendTaskResponse(
156
- id=request_data.id,
157
- error=InvalidRequestError(data=e.errors())
158
- )
159
- except json.JSONDecodeError as e:
160
- return SendTaskResponse(
161
- id=request_data.id,
162
- error=JSONParseError(data=str(e))
163
- )
164
- except Exception as e:
165
- # Handle case where handler returns SendTaskResponse with error
166
- if isinstance(e, JSONRPCError):
167
- return SendTaskResponse(
168
- id=request.id,
169
- error=e
170
- )
171
- return SendTaskResponse(
172
- id=request.id,
173
- error=InternalError(data=str(e))
174
- )
File without changes