agno 2.3.21__py3-none-any.whl → 2.3.23__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.
Files changed (74) hide show
  1. agno/agent/agent.py +48 -2
  2. agno/agent/remote.py +234 -73
  3. agno/client/a2a/__init__.py +10 -0
  4. agno/client/a2a/client.py +554 -0
  5. agno/client/a2a/schemas.py +112 -0
  6. agno/client/a2a/utils.py +369 -0
  7. agno/db/migrations/utils.py +19 -0
  8. agno/db/migrations/v1_to_v2.py +54 -16
  9. agno/db/migrations/versions/v2_3_0.py +92 -53
  10. agno/db/mysql/async_mysql.py +5 -7
  11. agno/db/mysql/mysql.py +5 -7
  12. agno/db/mysql/schemas.py +39 -21
  13. agno/db/postgres/async_postgres.py +172 -42
  14. agno/db/postgres/postgres.py +186 -38
  15. agno/db/postgres/schemas.py +39 -21
  16. agno/db/postgres/utils.py +6 -2
  17. agno/db/singlestore/schemas.py +41 -21
  18. agno/db/singlestore/singlestore.py +14 -3
  19. agno/db/sqlite/async_sqlite.py +7 -2
  20. agno/db/sqlite/schemas.py +36 -21
  21. agno/db/sqlite/sqlite.py +3 -7
  22. agno/knowledge/chunking/document.py +3 -2
  23. agno/knowledge/chunking/markdown.py +8 -3
  24. agno/knowledge/chunking/recursive.py +2 -2
  25. agno/models/base.py +4 -0
  26. agno/models/google/gemini.py +27 -4
  27. agno/models/openai/chat.py +1 -1
  28. agno/models/openai/responses.py +14 -7
  29. agno/os/middleware/jwt.py +66 -27
  30. agno/os/routers/agents/router.py +3 -3
  31. agno/os/routers/evals/evals.py +2 -2
  32. agno/os/routers/knowledge/knowledge.py +5 -5
  33. agno/os/routers/knowledge/schemas.py +1 -1
  34. agno/os/routers/memory/memory.py +4 -4
  35. agno/os/routers/session/session.py +2 -2
  36. agno/os/routers/teams/router.py +4 -4
  37. agno/os/routers/traces/traces.py +3 -3
  38. agno/os/routers/workflows/router.py +3 -3
  39. agno/os/schema.py +1 -1
  40. agno/reasoning/deepseek.py +11 -1
  41. agno/reasoning/gemini.py +6 -2
  42. agno/reasoning/groq.py +8 -3
  43. agno/reasoning/openai.py +2 -0
  44. agno/remote/base.py +106 -9
  45. agno/skills/__init__.py +17 -0
  46. agno/skills/agent_skills.py +370 -0
  47. agno/skills/errors.py +32 -0
  48. agno/skills/loaders/__init__.py +4 -0
  49. agno/skills/loaders/base.py +27 -0
  50. agno/skills/loaders/local.py +216 -0
  51. agno/skills/skill.py +65 -0
  52. agno/skills/utils.py +107 -0
  53. agno/skills/validator.py +277 -0
  54. agno/team/remote.py +220 -60
  55. agno/team/team.py +41 -3
  56. agno/tools/brandfetch.py +27 -18
  57. agno/tools/browserbase.py +150 -13
  58. agno/tools/function.py +6 -1
  59. agno/tools/mcp/mcp.py +300 -17
  60. agno/tools/mcp/multi_mcp.py +269 -14
  61. agno/tools/toolkit.py +89 -21
  62. agno/utils/mcp.py +49 -8
  63. agno/utils/string.py +43 -1
  64. agno/workflow/condition.py +4 -2
  65. agno/workflow/loop.py +20 -1
  66. agno/workflow/remote.py +173 -33
  67. agno/workflow/router.py +4 -1
  68. agno/workflow/steps.py +4 -0
  69. agno/workflow/workflow.py +14 -0
  70. {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/METADATA +13 -14
  71. {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/RECORD +74 -60
  72. {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/WHEEL +0 -0
  73. {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/licenses/LICENSE +0 -0
  74. {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -61,6 +61,9 @@ from agno.run.agent import (
61
61
  RunOutput,
62
62
  RunOutputEvent,
63
63
  )
64
+ from agno.run.cancel import (
65
+ acancel_run as acancel_run_global,
66
+ )
64
67
  from agno.run.cancel import (
65
68
  acleanup_run,
66
69
  araise_if_cancelled,
@@ -77,6 +80,7 @@ from agno.run.requirement import RunRequirement
77
80
  from agno.run.team import TeamRunOutputEvent
78
81
  from agno.session import AgentSession, SessionSummaryManager, TeamSession, WorkflowSession
79
82
  from agno.session.summary import SessionSummary
83
+ from agno.skills import Skills
80
84
  from agno.tools import Toolkit
81
85
  from agno.tools.function import Function
82
86
  from agno.utils.agent import (
@@ -264,6 +268,10 @@ class Agent:
264
268
  knowledge_retriever: Optional[Callable[..., Optional[List[Union[Dict, str]]]]] = None
265
269
  references_format: Literal["json", "yaml"] = "json"
266
270
 
271
+ # --- Skills ---
272
+ # Skills provide structured instructions, reference docs, and scripts for agents
273
+ skills: Optional[Skills] = None
274
+
267
275
  # --- Agent Tools ---
268
276
  # A list of tools provided to the Model.
269
277
  # Tools are functions the model may generate JSON inputs for.
@@ -487,6 +495,7 @@ class Agent:
487
495
  add_knowledge_to_context: bool = False,
488
496
  knowledge_retriever: Optional[Callable[..., Optional[List[Union[Dict, str]]]]] = None,
489
497
  references_format: Literal["json", "yaml"] = "json",
498
+ skills: Optional[Skills] = None,
490
499
  metadata: Optional[Dict[str, Any]] = None,
491
500
  tools: Optional[Sequence[Union[Toolkit, Callable, Function, Dict]]] = None,
492
501
  tool_call_limit: Optional[int] = None,
@@ -610,6 +619,8 @@ class Agent:
610
619
  self.knowledge_retriever = knowledge_retriever
611
620
  self.references_format = references_format
612
621
 
622
+ self.skills = skills
623
+
613
624
  self.metadata = metadata
614
625
 
615
626
  self.tools = list(tools) if tools else []
@@ -2022,6 +2033,7 @@ class Agent:
2022
2033
  run_response=run_response,
2023
2034
  run_context=run_context,
2024
2035
  session=agent_session,
2036
+ async_mode=True,
2025
2037
  )
2026
2038
 
2027
2039
  # 6. Prepare run messages
@@ -2369,6 +2381,7 @@ class Agent:
2369
2381
  run_response=run_response,
2370
2382
  run_context=run_context,
2371
2383
  session=agent_session,
2384
+ async_mode=True,
2372
2385
  )
2373
2386
 
2374
2387
  # 6. Prepare run messages
@@ -3979,6 +3992,7 @@ class Agent:
3979
3992
  run_response=run_response,
3980
3993
  run_context=run_context,
3981
3994
  session=agent_session,
3995
+ async_mode=True,
3982
3996
  )
3983
3997
 
3984
3998
  # 6. Prepare run messages
@@ -4285,6 +4299,7 @@ class Agent:
4285
4299
  run_response=run_response,
4286
4300
  run_context=run_context,
4287
4301
  session=agent_session,
4302
+ async_mode=True,
4288
4303
  )
4289
4304
 
4290
4305
  # 6. Prepare run messages
@@ -6396,6 +6411,10 @@ class Agent:
6396
6411
  if self.update_knowledge:
6397
6412
  agent_tools.append(self.add_to_knowledge)
6398
6413
 
6414
+ # Add tools for accessing skills
6415
+ if self.skills is not None:
6416
+ agent_tools.extend(self.skills.get_tools())
6417
+
6399
6418
  return agent_tools
6400
6419
 
6401
6420
  async def aget_tools(
@@ -6489,6 +6508,10 @@ class Agent:
6489
6508
  if self.update_knowledge:
6490
6509
  agent_tools.append(self.add_to_knowledge)
6491
6510
 
6511
+ # Add tools for accessing skills
6512
+ if self.skills is not None:
6513
+ agent_tools.extend(self.skills.get_tools())
6514
+
6492
6515
  return agent_tools
6493
6516
 
6494
6517
  def _determine_tools_for_model(
@@ -6498,6 +6521,7 @@ class Agent:
6498
6521
  run_response: RunOutput,
6499
6522
  run_context: RunContext,
6500
6523
  session: AgentSession,
6524
+ async_mode: bool = False,
6501
6525
  ) -> List[Union[Function, dict]]:
6502
6526
  _function_names = []
6503
6527
  _functions: List[Union[Function, dict]] = []
@@ -6528,7 +6552,8 @@ class Agent:
6528
6552
 
6529
6553
  elif isinstance(tool, Toolkit):
6530
6554
  # For each function in the toolkit and process entrypoint
6531
- for name, _func in tool.functions.items():
6555
+ toolkit_functions = tool.get_async_functions() if async_mode else tool.get_functions()
6556
+ for name, _func in toolkit_functions.items():
6532
6557
  if name in _function_names:
6533
6558
  continue
6534
6559
  _function_names.append(name)
@@ -6758,6 +6783,18 @@ class Agent:
6758
6783
  """
6759
6784
  return cancel_run_global(run_id)
6760
6785
 
6786
+ @staticmethod
6787
+ async def acancel_run(run_id: str) -> bool:
6788
+ """Cancel a running agent execution (async version).
6789
+
6790
+ Args:
6791
+ run_id (str): The run_id to cancel.
6792
+
6793
+ Returns:
6794
+ bool: True if the run was found and marked for cancellation, False otherwise.
6795
+ """
6796
+ return await acancel_run_global(run_id)
6797
+
6761
6798
  # -*- Session Database Functions
6762
6799
  def _read_session(
6763
6800
  self, session_id: str, session_type: SessionType = SessionType.AGENT
@@ -7171,7 +7208,6 @@ class Agent:
7171
7208
  """
7172
7209
  if self._has_async_db():
7173
7210
  raise ValueError("Async database not supported for save_session")
7174
-
7175
7211
  # If the agent is a member of a team, do not save the session to the database
7176
7212
  if (
7177
7213
  self.db is not None
@@ -7937,6 +7973,11 @@ class Agent:
7937
7973
  # 3.3.8 Then add additional context
7938
7974
  if self.additional_context is not None:
7939
7975
  system_message_content += f"{self.additional_context}\n"
7976
+ # 3.3.8.1 Then add skills to the system prompt
7977
+ if self.skills is not None:
7978
+ skills_snippet = self.skills.get_system_prompt_snippet()
7979
+ if skills_snippet:
7980
+ system_message_content += f"\n{skills_snippet}\n"
7940
7981
  # 3.3.9 Then add memories to the system prompt
7941
7982
  if self.add_memories_to_context:
7942
7983
  _memory_manager_not_set = False
@@ -8281,6 +8322,11 @@ class Agent:
8281
8322
  # 3.3.8 Then add additional context
8282
8323
  if self.additional_context is not None:
8283
8324
  system_message_content += f"{self.additional_context}\n"
8325
+ # 3.3.8.1 Then add skills to the system prompt
8326
+ if self.skills is not None:
8327
+ skills_snippet = self.skills.get_system_prompt_snippet()
8328
+ if skills_snippet:
8329
+ system_message_content += f"\n{skills_snippet}\n"
8284
8330
  # 3.3.9 Then add memories to the system prompt
8285
8331
  if self.add_memories_to_context:
8286
8332
  _memory_manager_not_set = False
agno/agent/remote.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import json
2
- import time
3
2
  from dataclasses import dataclass, field
4
3
  from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Literal, Optional, Sequence, Tuple, Union, overload
5
4
 
@@ -29,19 +28,25 @@ class RemoteAgent(BaseRemote):
29
28
  base_url: str,
30
29
  agent_id: str,
31
30
  timeout: float = 60.0,
31
+ protocol: Literal["agentos", "a2a"] = "agentos",
32
+ a2a_protocol: Literal["json-rpc", "rest"] = "rest",
32
33
  config_ttl: float = 300.0,
33
34
  ):
34
- """Initialize AgentOSRunner for local or remote execution.
35
+ """Initialize RemoteAgent for remote execution.
35
36
 
36
- For remote execution, provide base_url and agent_id.
37
+ Supports two protocols:
38
+ - "agentos": Agno's proprietary AgentOS REST API (default)
39
+ - "a2a": A2A (Agent-to-Agent) protocol for cross-framework communication
37
40
 
38
41
  Args:
39
- base_url: Base URL for remote AgentOS instance (e.g., "http://localhost:7777")
40
- agent_id: ID of remote agent
42
+ base_url: Base URL for remote instance (e.g., "http://localhost:7777")
43
+ agent_id: ID of remote agent on the remote server
41
44
  timeout: Request timeout in seconds (default: 60)
45
+ protocol: Communication protocol - "agentos" (default) or "a2a"
46
+ a2a_protocol: For A2A protocol only - Whether to use JSON-RPC or REST protocol.
42
47
  config_ttl: Time-to-live for cached config in seconds (default: 300)
43
48
  """
44
- super().__init__(base_url, timeout, config_ttl)
49
+ super().__init__(base_url, timeout, protocol, a2a_protocol, config_ttl)
45
50
  self.agent_id = agent_id
46
51
  self._cached_agent_config = None
47
52
 
@@ -50,14 +55,48 @@ class RemoteAgent(BaseRemote):
50
55
  return self.agent_id
51
56
 
52
57
  async def get_agent_config(self) -> "AgentResponse":
53
- """Get the agent config from remote (always fetches fresh)."""
54
- return await self.client.aget_agent(self.agent_id)
58
+ """
59
+ Get the agent config from remote.
60
+
61
+ For A2A protocol, returns a minimal AgentResponse since A2A servers
62
+ don't expose the same config endpoints as AgentOS. For AgentOS, always fetches fresh config.
63
+ """
64
+ from agno.os.routers.agents.schema import AgentResponse
65
+
66
+ if self.a2a_client:
67
+ from agno.client.a2a.schemas import AgentCard
68
+
69
+ agent_card: Optional[AgentCard] = await self.a2a_client.aget_agent_card()
70
+
71
+ return AgentResponse(
72
+ id=self.agent_id,
73
+ name=agent_card.name if agent_card else self.agent_id,
74
+ description=agent_card.description if agent_card else f"A2A agent: {self.agent_id}",
75
+ )
76
+
77
+ return await self.agentos_client.aget_agent(self.agent_id) # type: ignore
55
78
 
56
79
  @property
57
- def _agent_config(self) -> "AgentResponse":
58
- """Get the agent config from remote, cached with TTL."""
80
+ def _agent_config(self) -> Optional["AgentResponse"]:
81
+ """
82
+ Get the agent config from remote, cached with TTL.
83
+ Returns None for A2A protocol since A2A servers don't expose agent config endpoints.
84
+ """
85
+ import time
86
+
59
87
  from agno.os.routers.agents.schema import AgentResponse
60
88
 
89
+ if self.a2a_client:
90
+ from agno.client.a2a.schemas import AgentCard
91
+
92
+ agent_card: Optional[AgentCard] = self.a2a_client.get_agent_card()
93
+
94
+ return AgentResponse(
95
+ id=self.agent_id,
96
+ name=agent_card.name if agent_card else self.agent_id,
97
+ description=agent_card.description if agent_card else f"A2A agent: {self.agent_id}",
98
+ )
99
+
61
100
  current_time = time.time()
62
101
 
63
102
  # Check if cache is valid
@@ -67,15 +106,24 @@ class RemoteAgent(BaseRemote):
67
106
  return config
68
107
 
69
108
  # Fetch fresh config
70
- config: AgentResponse = self.client.get_agent(self.agent_id) # type: ignore
109
+ config: AgentResponse = self.agentos_client.get_agent(self.agent_id) # type: ignore
71
110
  self._cached_agent_config = (config, current_time)
72
111
  return config
73
112
 
74
- def refresh_config(self) -> "AgentResponse":
75
- """Force refresh the cached agent config."""
113
+ async def refresh_config(self) -> Optional["AgentResponse"]:
114
+ """
115
+ Force refresh the cached agent config.
116
+ Returns None for A2A protocol.
117
+ """
118
+ import time
119
+
76
120
  from agno.os.routers.agents.schema import AgentResponse
77
121
 
78
- config: AgentResponse = self.client.get_agent(self.agent_id)
122
+ if self.a2a_client:
123
+ self._cached_agent_config = None
124
+ return None
125
+
126
+ config: AgentResponse = await self.agentos_client.aget_agent(self.agent_id) # type: ignore
79
127
  self._cached_agent_config = (config, time.time())
80
128
  return config
81
129
 
@@ -91,7 +139,6 @@ class RemoteAgent(BaseRemote):
91
139
  return self._agent_config.description
92
140
  return ""
93
141
 
94
- @property
95
142
  def role(self) -> Optional[str]:
96
143
  if self._agent_config is not None:
97
144
  return self._agent_config.role
@@ -109,23 +156,27 @@ class RemoteAgent(BaseRemote):
109
156
 
110
157
  @property
111
158
  def db(self) -> Optional[RemoteDb]:
112
- if self._agent_config is not None and self._agent_config.db_id is not None:
159
+ if (
160
+ self.agentos_client
161
+ and self._config
162
+ and self._agent_config is not None
163
+ and self._agent_config.db_id is not None
164
+ ):
113
165
  return RemoteDb.from_config(
114
166
  db_id=self._agent_config.db_id,
115
- client=self.client,
167
+ client=self.agentos_client,
116
168
  config=self._config,
117
169
  )
118
170
  return None
119
171
 
120
172
  @property
121
173
  def knowledge(self) -> Optional[RemoteKnowledge]:
122
- """Whether the agent has knowledge enabled."""
123
- if self._agent_config is not None and self._agent_config.knowledge is not None:
174
+ if self.agentos_client and self._agent_config is not None and self._agent_config.knowledge is not None:
124
175
  return RemoteKnowledge(
125
- client=self.client,
176
+ client=self.agentos_client,
126
177
  contents_db=RemoteDb(
127
178
  id=self._agent_config.knowledge.get("db_id"), # type: ignore
128
- client=self.client,
179
+ client=self.agentos_client,
129
180
  knowledge_table_name=self._agent_config.knowledge.get("knowledge_table"),
130
181
  )
131
182
  if self._agent_config.knowledge.get("db_id") is not None
@@ -139,7 +190,7 @@ class RemoteAgent(BaseRemote):
139
190
  return None
140
191
 
141
192
  async def aget_tools(self, **kwargs: Any) -> List[Dict]:
142
- if self._agent_config.tools is not None:
193
+ if self._agent_config is not None and self._agent_config.tools is not None:
143
194
  return json.loads(self._agent_config.tools["tools"])
144
195
  return []
145
196
 
@@ -222,52 +273,156 @@ class RemoteAgent(BaseRemote):
222
273
  serialized_input = serialize_input(validated_input)
223
274
  headers = self._get_auth_headers(auth_token)
224
275
 
225
- if stream:
226
- # Handle streaming response
227
- return self.get_client().run_agent_stream(
228
- agent_id=self.agent_id,
276
+ # A2A protocol path
277
+ if self.a2a_client:
278
+ return self._arun_a2a( # type: ignore[return-value]
229
279
  message=serialized_input,
230
- session_id=session_id,
280
+ stream=stream or False,
231
281
  user_id=user_id,
282
+ context_id=session_id, # Map session_id → context_id for A2A
232
283
  audio=audio,
233
284
  images=images,
234
285
  videos=videos,
235
286
  files=files,
236
- session_state=session_state,
237
- stream_events=stream_events,
238
- retries=retries,
239
- knowledge_filters=knowledge_filters,
240
- add_history_to_context=add_history_to_context,
241
- add_dependencies_to_context=add_dependencies_to_context,
242
- add_session_state_to_context=add_session_state_to_context,
243
- dependencies=dependencies,
244
- metadata=metadata,
245
287
  headers=headers,
246
- **kwargs,
247
288
  )
289
+
290
+ # AgentOS protocol path (default)
291
+ if self.agentos_client:
292
+ if stream:
293
+ # Handle streaming response
294
+ return self.agentos_client.run_agent_stream(
295
+ agent_id=self.agent_id,
296
+ message=serialized_input,
297
+ session_id=session_id,
298
+ user_id=user_id,
299
+ audio=audio,
300
+ images=images,
301
+ videos=videos,
302
+ files=files,
303
+ session_state=session_state,
304
+ stream_events=stream_events,
305
+ retries=retries,
306
+ knowledge_filters=knowledge_filters,
307
+ add_history_to_context=add_history_to_context,
308
+ add_dependencies_to_context=add_dependencies_to_context,
309
+ add_session_state_to_context=add_session_state_to_context,
310
+ dependencies=dependencies,
311
+ metadata=metadata,
312
+ headers=headers,
313
+ **kwargs,
314
+ )
315
+ else:
316
+ return self.agentos_client.run_agent( # type: ignore
317
+ agent_id=self.agent_id,
318
+ message=serialized_input,
319
+ session_id=session_id,
320
+ user_id=user_id,
321
+ audio=audio,
322
+ images=images,
323
+ videos=videos,
324
+ files=files,
325
+ session_state=session_state,
326
+ stream_events=stream_events,
327
+ retries=retries,
328
+ knowledge_filters=knowledge_filters,
329
+ add_history_to_context=add_history_to_context,
330
+ add_dependencies_to_context=add_dependencies_to_context,
331
+ add_session_state_to_context=add_session_state_to_context,
332
+ dependencies=dependencies,
333
+ metadata=metadata,
334
+ headers=headers,
335
+ **kwargs,
336
+ )
248
337
  else:
249
- return self.get_client().run_agent( # type: ignore
250
- agent_id=self.agent_id,
251
- message=serialized_input,
252
- session_id=session_id,
338
+ raise ValueError("No client available")
339
+
340
+ def _arun_a2a(
341
+ self,
342
+ message: str,
343
+ stream: bool,
344
+ user_id: Optional[str],
345
+ context_id: Optional[str],
346
+ audio: Optional[Sequence[Audio]],
347
+ images: Optional[Sequence[Image]],
348
+ videos: Optional[Sequence[Video]],
349
+ files: Optional[Sequence[File]],
350
+ headers: Optional[Dict[str, str]],
351
+ ) -> Union[RunOutput, AsyncIterator[RunOutputEvent]]:
352
+ """Execute via A2A protocol.
353
+
354
+ Args:
355
+ message: Serialized message string
356
+ stream: Whether to stream the response
357
+ user_id: User identifier
358
+ context_id: Session/context ID (maps to session_id)
359
+ audio: Audio files to include
360
+ images: Images to include
361
+ videos: Videos to include
362
+ files: Files to include
363
+ headers: HTTP headers to include in the request (optional)
364
+
365
+ Returns:
366
+ RunOutput for non-streaming, AsyncIterator[RunOutputEvent] for streaming
367
+ """
368
+ if not self.a2a_client:
369
+ raise ValueError("A2A client not available")
370
+ from agno.client.a2a.utils import map_stream_events_to_run_events
371
+
372
+ if stream:
373
+ # Return async generator for streaming
374
+ event_stream = self.a2a_client.stream_message(
375
+ message=message,
376
+ context_id=context_id,
253
377
  user_id=user_id,
378
+ images=list(images) if images else None,
379
+ audio=list(audio) if audio else None,
380
+ videos=list(videos) if videos else None,
381
+ files=list(files) if files else None,
382
+ headers=headers,
383
+ )
384
+ return map_stream_events_to_run_events(event_stream, agent_id=self.agent_id)
385
+ else:
386
+ # Return coroutine for non-streaming
387
+ return self._arun_a2a_send( # type: ignore[return-value]
388
+ message=message,
389
+ user_id=user_id,
390
+ context_id=context_id,
254
391
  audio=audio,
255
392
  images=images,
256
393
  videos=videos,
257
394
  files=files,
258
- session_state=session_state,
259
- stream_events=stream_events,
260
- retries=retries,
261
- knowledge_filters=knowledge_filters,
262
- add_history_to_context=add_history_to_context,
263
- add_dependencies_to_context=add_dependencies_to_context,
264
- add_session_state_to_context=add_session_state_to_context,
265
- dependencies=dependencies,
266
- metadata=metadata,
267
395
  headers=headers,
268
- **kwargs,
269
396
  )
270
397
 
398
+ async def _arun_a2a_send(
399
+ self,
400
+ message: str,
401
+ user_id: Optional[str],
402
+ context_id: Optional[str],
403
+ audio: Optional[Sequence[Audio]],
404
+ images: Optional[Sequence[Image]],
405
+ videos: Optional[Sequence[Video]],
406
+ files: Optional[Sequence[File]],
407
+ headers: Optional[Dict[str, str]],
408
+ ) -> RunOutput:
409
+ """Send a non-streaming A2A message and convert response to RunOutput."""
410
+ if not self.a2a_client:
411
+ raise ValueError("A2A client not available")
412
+ from agno.client.a2a.utils import map_task_result_to_run_output
413
+
414
+ task_result = await self.a2a_client.send_message(
415
+ message=message,
416
+ context_id=context_id,
417
+ user_id=user_id,
418
+ images=list(images) if images else None,
419
+ audio=list(audio) if audio else None,
420
+ videos=list(videos) if videos else None,
421
+ files=list(files) if files else None,
422
+ headers=headers,
423
+ )
424
+ return map_task_result_to_run_output(task_result, agent_id=self.agent_id, user_id=user_id)
425
+
271
426
  @overload
272
427
  async def acontinue_run(
273
428
  self,
@@ -307,29 +462,33 @@ class RemoteAgent(BaseRemote):
307
462
  ]:
308
463
  headers = self._get_auth_headers(auth_token)
309
464
 
310
- if stream:
311
- # Handle streaming response
312
- return self.get_client().continue_agent_run_stream( # type: ignore
313
- agent_id=self.agent_id,
314
- run_id=run_id,
315
- user_id=user_id,
316
- session_id=session_id,
317
- tools=updated_tools,
318
- headers=headers,
319
- **kwargs,
320
- )
465
+ if self.agentos_client:
466
+ if stream:
467
+ # Handle streaming response
468
+ return self.agentos_client.continue_agent_run_stream( # type: ignore
469
+ agent_id=self.agent_id,
470
+ run_id=run_id,
471
+ user_id=user_id,
472
+ session_id=session_id,
473
+ tools=updated_tools,
474
+ headers=headers,
475
+ **kwargs,
476
+ )
477
+ else:
478
+ return self.agentos_client.continue_agent_run( # type: ignore
479
+ agent_id=self.agent_id,
480
+ run_id=run_id,
481
+ tools=updated_tools,
482
+ user_id=user_id,
483
+ session_id=session_id,
484
+ headers=headers,
485
+ **kwargs,
486
+ )
487
+
321
488
  else:
322
- return self.get_client().continue_agent_run( # type: ignore
323
- agent_id=self.agent_id,
324
- run_id=run_id,
325
- tools=updated_tools,
326
- user_id=user_id,
327
- session_id=session_id,
328
- headers=headers,
329
- **kwargs,
330
- )
489
+ raise ValueError("No client available")
331
490
 
332
- async def cancel_run(self, run_id: str, auth_token: Optional[str] = None) -> bool:
491
+ async def acancel_run(self, run_id: str, auth_token: Optional[str] = None) -> bool:
333
492
  """Cancel a running agent execution.
334
493
 
335
494
  Args:
@@ -340,8 +499,10 @@ class RemoteAgent(BaseRemote):
340
499
  bool: True if the run was successfully cancelled, False otherwise.
341
500
  """
342
501
  headers = self._get_auth_headers(auth_token)
502
+ if not self.agentos_client:
503
+ raise ValueError("AgentOS client not available")
343
504
  try:
344
- await self.get_client().cancel_agent_run(
505
+ await self.agentos_client.cancel_agent_run(
345
506
  agent_id=self.agent_id,
346
507
  run_id=run_id,
347
508
  headers=headers,
@@ -0,0 +1,10 @@
1
+ from agno.client.a2a.client import A2AClient
2
+ from agno.client.a2a.schemas import AgentCard, Artifact, StreamEvent, TaskResult
3
+
4
+ __all__ = [
5
+ "A2AClient",
6
+ "AgentCard",
7
+ "Artifact",
8
+ "StreamEvent",
9
+ "TaskResult",
10
+ ]