agno 2.0.10__py3-none-any.whl → 2.1.0__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 (85) hide show
  1. agno/agent/agent.py +608 -175
  2. agno/db/in_memory/in_memory_db.py +42 -29
  3. agno/db/postgres/postgres.py +6 -4
  4. agno/exceptions.py +62 -1
  5. agno/guardrails/__init__.py +6 -0
  6. agno/guardrails/base.py +19 -0
  7. agno/guardrails/openai.py +144 -0
  8. agno/guardrails/pii.py +94 -0
  9. agno/guardrails/prompt_injection.py +51 -0
  10. agno/knowledge/embedder/aws_bedrock.py +9 -4
  11. agno/knowledge/embedder/azure_openai.py +54 -0
  12. agno/knowledge/embedder/base.py +2 -0
  13. agno/knowledge/embedder/cohere.py +184 -5
  14. agno/knowledge/embedder/google.py +79 -1
  15. agno/knowledge/embedder/huggingface.py +9 -4
  16. agno/knowledge/embedder/jina.py +63 -0
  17. agno/knowledge/embedder/mistral.py +78 -11
  18. agno/knowledge/embedder/ollama.py +5 -0
  19. agno/knowledge/embedder/openai.py +18 -54
  20. agno/knowledge/embedder/voyageai.py +69 -16
  21. agno/knowledge/knowledge.py +5 -4
  22. agno/knowledge/reader/pdf_reader.py +4 -3
  23. agno/knowledge/reader/website_reader.py +3 -2
  24. agno/models/base.py +125 -32
  25. agno/models/cerebras/cerebras.py +1 -0
  26. agno/models/cerebras/cerebras_openai.py +1 -0
  27. agno/models/dashscope/dashscope.py +1 -0
  28. agno/models/google/gemini.py +27 -5
  29. agno/models/litellm/chat.py +17 -0
  30. agno/models/openai/chat.py +13 -4
  31. agno/models/perplexity/perplexity.py +2 -3
  32. agno/models/requesty/__init__.py +5 -0
  33. agno/models/requesty/requesty.py +49 -0
  34. agno/models/vllm/vllm.py +1 -0
  35. agno/models/xai/xai.py +1 -0
  36. agno/os/app.py +167 -148
  37. agno/os/interfaces/whatsapp/router.py +2 -0
  38. agno/os/mcp.py +1 -1
  39. agno/os/middleware/__init__.py +7 -0
  40. agno/os/middleware/jwt.py +233 -0
  41. agno/os/router.py +181 -45
  42. agno/os/routers/home.py +2 -2
  43. agno/os/routers/memory/memory.py +23 -1
  44. agno/os/routers/memory/schemas.py +1 -1
  45. agno/os/routers/session/session.py +20 -3
  46. agno/os/utils.py +172 -8
  47. agno/run/agent.py +120 -77
  48. agno/run/team.py +115 -72
  49. agno/run/workflow.py +5 -15
  50. agno/session/summary.py +9 -10
  51. agno/session/team.py +2 -1
  52. agno/team/team.py +720 -168
  53. agno/tools/firecrawl.py +4 -4
  54. agno/tools/function.py +42 -2
  55. agno/tools/knowledge.py +3 -3
  56. agno/tools/searxng.py +2 -2
  57. agno/tools/serper.py +2 -2
  58. agno/tools/spider.py +2 -2
  59. agno/tools/workflow.py +4 -5
  60. agno/utils/events.py +66 -1
  61. agno/utils/hooks.py +57 -0
  62. agno/utils/media.py +11 -9
  63. agno/utils/print_response/agent.py +43 -5
  64. agno/utils/print_response/team.py +48 -12
  65. agno/vectordb/cassandra/cassandra.py +44 -4
  66. agno/vectordb/chroma/chromadb.py +79 -8
  67. agno/vectordb/clickhouse/clickhousedb.py +43 -6
  68. agno/vectordb/couchbase/couchbase.py +76 -5
  69. agno/vectordb/lancedb/lance_db.py +38 -3
  70. agno/vectordb/llamaindex/__init__.py +3 -0
  71. agno/vectordb/milvus/milvus.py +76 -4
  72. agno/vectordb/mongodb/mongodb.py +76 -4
  73. agno/vectordb/pgvector/pgvector.py +50 -6
  74. agno/vectordb/pineconedb/pineconedb.py +39 -2
  75. agno/vectordb/qdrant/qdrant.py +76 -26
  76. agno/vectordb/singlestore/singlestore.py +77 -4
  77. agno/vectordb/upstashdb/upstashdb.py +42 -2
  78. agno/vectordb/weaviate/weaviate.py +39 -3
  79. agno/workflow/types.py +1 -0
  80. agno/workflow/workflow.py +58 -2
  81. {agno-2.0.10.dist-info → agno-2.1.0.dist-info}/METADATA +4 -3
  82. {agno-2.0.10.dist-info → agno-2.1.0.dist-info}/RECORD +85 -75
  83. {agno-2.0.10.dist-info → agno-2.1.0.dist-info}/WHEEL +0 -0
  84. {agno-2.0.10.dist-info → agno-2.1.0.dist-info}/licenses/LICENSE +0 -0
  85. {agno-2.0.10.dist-info → agno-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,233 @@
1
+ import fnmatch
2
+ from enum import Enum
3
+ from os import getenv
4
+ from typing import List, Optional
5
+
6
+ import jwt
7
+ from fastapi import Request, Response
8
+ from fastapi.responses import JSONResponse
9
+ from starlette.middleware.base import BaseHTTPMiddleware
10
+
11
+ from agno.utils.log import log_debug
12
+
13
+
14
+ class TokenSource(str, Enum):
15
+ """Enum for JWT token source options."""
16
+
17
+ HEADER = "header"
18
+ COOKIE = "cookie"
19
+ BOTH = "both" # Try header first, then cookie
20
+
21
+
22
+ class JWTMiddleware(BaseHTTPMiddleware):
23
+ """
24
+ JWT Middleware for validating tokens and storing JWT claims in request state.
25
+
26
+ This middleware:
27
+ 1. Extracts JWT token from Authorization header, cookies, or both
28
+ 2. Decodes and validates the token
29
+ 3. Stores JWT claims in request.state for easy access in endpoints
30
+
31
+ Token Sources:
32
+ - "header": Extract from Authorization header (default)
33
+ - "cookie": Extract from HTTP cookie
34
+ - "both": Try header first, then cookie as fallback
35
+
36
+ Claims are stored as:
37
+ - request.state.user_id: User ID from configured claim
38
+ - request.state.session_id: Session ID from configured claim
39
+ - request.state.dependencies: Dictionary of dependency claims
40
+ - request.state.session_state: Dictionary of session state claims
41
+ - request.state.authenticated: Boolean authentication status
42
+
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ app,
48
+ secret_key: Optional[str] = None,
49
+ algorithm: str = "HS256",
50
+ token_source: TokenSource = TokenSource.HEADER,
51
+ token_header_key: str = "Authorization",
52
+ cookie_name: str = "access_token",
53
+ validate: bool = True,
54
+ excluded_route_paths: Optional[List[str]] = None,
55
+ scopes_claim: Optional[str] = None,
56
+ user_id_claim: str = "sub",
57
+ session_id_claim: str = "session_id",
58
+ dependencies_claims: Optional[List[str]] = None,
59
+ session_state_claims: Optional[List[str]] = None,
60
+ ):
61
+ """
62
+ Initialize the JWT middleware.
63
+
64
+ Args:
65
+ app: The FastAPI app instance
66
+ secret_key: The secret key to use for JWT validation (optional, will use JWT_SECRET_KEY environment variable if not provided)
67
+ algorithm: The algorithm to use for JWT validation
68
+ token_header_key: The key to use for the Authorization header (only used when token_source is header)
69
+ token_source: Where to extract the JWT token from (header, cookie, or both)
70
+ cookie_name: The name of the cookie containing the JWT token (only used when token_source is cookie/both)
71
+ validate: Whether to validate the JWT token
72
+ excluded_route_paths: A list of route paths to exclude from JWT validation
73
+ scopes_claim: The claim to use for scopes extraction
74
+ user_id_claim: The claim to use for user ID extraction
75
+ session_id_claim: The claim to use for session ID extraction
76
+ dependencies_claims: A list of claims to extract from the JWT token for dependencies
77
+ session_state_claims: A list of claims to extract from the JWT token for session state
78
+ """
79
+ super().__init__(app)
80
+ self.secret_key = secret_key or getenv("JWT_SECRET_KEY")
81
+ if not self.secret_key:
82
+ raise ValueError("Secret key is required")
83
+ self.algorithm = algorithm
84
+ self.token_header_key = token_header_key
85
+ self.token_source = token_source
86
+ self.cookie_name = cookie_name
87
+ self.validate = validate
88
+ self.excluded_route_paths = excluded_route_paths
89
+ self.scopes_claim = scopes_claim
90
+ self.user_id_claim = user_id_claim
91
+ self.session_id_claim = session_id_claim
92
+ self.dependencies_claims = dependencies_claims or []
93
+ self.session_state_claims = session_state_claims or []
94
+
95
+ def _extract_token_from_header(self, request: Request) -> Optional[str]:
96
+ """Extract JWT token from Authorization header."""
97
+ authorization = request.headers.get(self.token_header_key, "")
98
+ if not authorization:
99
+ return None
100
+
101
+ try:
102
+ # Remove the "Bearer " prefix (if present)
103
+ _, token = authorization.split(" ", 1)
104
+ return token
105
+ except ValueError:
106
+ return None
107
+
108
+ def _extract_token_from_cookie(self, request: Request) -> Optional[str]:
109
+ """Extract JWT token from cookie."""
110
+ return request.cookies.get(self.cookie_name)
111
+
112
+ def _extract_token(self, request: Request) -> Optional[str]:
113
+ """Extract JWT token based on configured token source."""
114
+ if self.token_source == TokenSource.HEADER:
115
+ return self._extract_token_from_header(request)
116
+ elif self.token_source == TokenSource.COOKIE:
117
+ return self._extract_token_from_cookie(request)
118
+ elif self.token_source == TokenSource.BOTH:
119
+ # Try header first, then cookie
120
+ token = self._extract_token_from_header(request)
121
+ if token is None:
122
+ token = self._extract_token_from_cookie(request)
123
+ return token
124
+ else:
125
+ log_debug(f"Unknown token source: {self.token_source}")
126
+ return None
127
+
128
+ def _get_missing_token_error_message(self) -> str:
129
+ """Get appropriate error message for missing token based on token source."""
130
+ if self.token_source == TokenSource.HEADER:
131
+ return "Authorization header missing"
132
+ elif self.token_source == TokenSource.COOKIE:
133
+ return f"JWT cookie '{self.cookie_name}' missing"
134
+ elif self.token_source == TokenSource.BOTH:
135
+ return f"JWT token missing from both Authorization header and '{self.cookie_name}' cookie"
136
+ else:
137
+ return "JWT token missing"
138
+
139
+ def _is_route_excluded(self, path: str) -> bool:
140
+ """Check if a route path matches any of the excluded patterns."""
141
+ if not self.excluded_route_paths:
142
+ return False
143
+
144
+ for excluded_path in self.excluded_route_paths:
145
+ # Support both exact matches and wildcard patterns
146
+ if fnmatch.fnmatch(path, excluded_path):
147
+ return True
148
+
149
+ return False
150
+
151
+ async def dispatch(self, request: Request, call_next) -> Response:
152
+ if self._is_route_excluded(request.url.path):
153
+ return await call_next(request)
154
+
155
+ # Extract JWT token from configured source (header, cookie, or both)
156
+ token = self._extract_token(request)
157
+
158
+ if not token:
159
+ if self.validate:
160
+ error_msg = self._get_missing_token_error_message()
161
+ return JSONResponse(status_code=401, content={"detail": error_msg})
162
+ return await call_next(request)
163
+
164
+ # Decode JWT token
165
+ try:
166
+ payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm]) # type: ignore
167
+
168
+ # Extract scopes claims
169
+ scopes = []
170
+ if self.scopes_claim in payload:
171
+ extracted_scopes = payload[self.scopes_claim]
172
+ if isinstance(extracted_scopes, str):
173
+ scopes = extracted_scopes.split(" ")
174
+ else:
175
+ scopes = extracted_scopes
176
+ if scopes:
177
+ request.state.scopes = scopes
178
+
179
+ # Extract user information
180
+ if self.user_id_claim in payload:
181
+ user_id = payload[self.user_id_claim]
182
+ request.state.user_id = user_id
183
+ if self.session_id_claim in payload:
184
+ session_id = payload[self.session_id_claim]
185
+ request.state.session_id = session_id
186
+ else:
187
+ session_id = None
188
+
189
+ # Extract dependency claims
190
+ dependencies = {}
191
+ for claim in self.dependencies_claims:
192
+ if claim in payload:
193
+ dependencies[claim] = payload[claim]
194
+
195
+ if dependencies:
196
+ request.state.dependencies = dependencies
197
+
198
+ # Extract session state claims
199
+ session_state = {}
200
+ for claim in self.session_state_claims:
201
+ if claim in payload:
202
+ session_state[claim] = payload[claim]
203
+
204
+ if session_state:
205
+ request.state.session_state = session_state
206
+
207
+ request.state.token = token
208
+ request.state.authenticated = True
209
+
210
+ log_debug(f"JWT decoded successfully for user: {user_id}")
211
+ if dependencies:
212
+ log_debug(f"Extracted dependencies: {dependencies}")
213
+ if session_state:
214
+ log_debug(f"Extracted session state: {session_state}")
215
+
216
+ except jwt.ExpiredSignatureError:
217
+ if self.validate:
218
+ return JSONResponse(status_code=401, content={"detail": "Token has expired"})
219
+ request.state.authenticated = False
220
+ request.state.token = token
221
+
222
+ except jwt.InvalidTokenError as e:
223
+ if self.validate:
224
+ return JSONResponse(status_code=401, content={"detail": f"Invalid token: {str(e)}"})
225
+ request.state.authenticated = False
226
+ request.state.token = token
227
+ except Exception as e:
228
+ if self.validate:
229
+ return JSONResponse(status_code=401, content={"detail": f"Error decoding token: {str(e)}"})
230
+ request.state.authenticated = False
231
+ request.state.token = token
232
+
233
+ return await call_next(request)
agno/os/router.py CHANGED
@@ -16,6 +16,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
16
16
  from pydantic import BaseModel
17
17
 
18
18
  from agno.agent.agent import Agent
19
+ from agno.exceptions import InputCheckError, OutputCheckError
19
20
  from agno.media import Audio, Image, Video
20
21
  from agno.media import File as FileMedia
21
22
  from agno.os.auth import get_authentication_dependency, validate_websocket_token
@@ -74,7 +75,6 @@ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict
74
75
  kwargs = {key: value for key, value in form_data.items() if key not in known_fields}
75
76
 
76
77
  # Handle JSON parameters. They are passed as strings and need to be deserialized.
77
-
78
78
  if session_state := kwargs.get("session_state"):
79
79
  try:
80
80
  session_state_dict = json.loads(session_state) # type: ignore
@@ -82,6 +82,7 @@ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict
82
82
  except json.JSONDecodeError:
83
83
  kwargs.pop("session_state")
84
84
  log_warning(f"Invalid session_state parameter couldn't be loaded: {session_state}")
85
+
85
86
  if dependencies := kwargs.get("dependencies"):
86
87
  try:
87
88
  dependencies_dict = json.loads(dependencies) # type: ignore
@@ -245,7 +246,14 @@ async def agent_response_streamer(
245
246
  )
246
247
  async for run_response_chunk in run_response:
247
248
  yield format_sse_event(run_response_chunk) # type: ignore
248
-
249
+ except (InputCheckError, OutputCheckError) as e:
250
+ error_response = RunErrorEvent(
251
+ content=str(e),
252
+ error_type=e.type,
253
+ error_id=e.error_id,
254
+ additional_data=e.additional_data,
255
+ )
256
+ yield format_sse_event(error_response)
249
257
  except Exception as e:
250
258
  import traceback
251
259
 
@@ -274,6 +282,14 @@ async def agent_continue_response_streamer(
274
282
  )
275
283
  async for run_response_chunk in continue_response:
276
284
  yield format_sse_event(run_response_chunk) # type: ignore
285
+ except (InputCheckError, OutputCheckError) as e:
286
+ error_response = RunErrorEvent(
287
+ content=str(e),
288
+ error_type=e.type,
289
+ error_id=e.error_id,
290
+ additional_data=e.additional_data,
291
+ )
292
+ yield format_sse_event(error_response)
277
293
 
278
294
  except Exception as e:
279
295
  import traceback
@@ -281,6 +297,8 @@ async def agent_continue_response_streamer(
281
297
  traceback.print_exc(limit=3)
282
298
  error_response = RunErrorEvent(
283
299
  content=str(e),
300
+ error_type=e.type if hasattr(e, "type") else None,
301
+ error_id=e.error_id if hasattr(e, "error_id") else None,
284
302
  )
285
303
  yield format_sse_event(error_response)
286
304
  return
@@ -313,6 +331,14 @@ async def team_response_streamer(
313
331
  )
314
332
  async for run_response_chunk in run_response:
315
333
  yield format_sse_event(run_response_chunk) # type: ignore
334
+ except (InputCheckError, OutputCheckError) as e:
335
+ error_response = TeamRunErrorEvent(
336
+ content=str(e),
337
+ error_type=e.type,
338
+ error_id=e.error_id,
339
+ additional_data=e.additional_data,
340
+ )
341
+ yield format_sse_event(error_response)
316
342
 
317
343
  except Exception as e:
318
344
  import traceback
@@ -320,6 +346,8 @@ async def team_response_streamer(
320
346
  traceback.print_exc()
321
347
  error_response = TeamRunErrorEvent(
322
348
  content=str(e),
349
+ error_type=e.type if hasattr(e, "type") else None,
350
+ error_id=e.error_id if hasattr(e, "error_id") else None,
323
351
  )
324
352
  yield format_sse_event(error_response)
325
353
  return
@@ -366,9 +394,28 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
366
394
 
367
395
  await websocket_manager.register_workflow_websocket(workflow_run_output.run_id, websocket) # type: ignore
368
396
 
397
+ except (InputCheckError, OutputCheckError) as e:
398
+ await websocket.send_text(
399
+ json.dumps(
400
+ {
401
+ "event": "error",
402
+ "error": str(e),
403
+ "error_type": e.type,
404
+ "error_id": e.error_id,
405
+ "additional_data": e.additional_data,
406
+ }
407
+ )
408
+ )
369
409
  except Exception as e:
370
410
  logger.error(f"Error executing workflow via WebSocket: {e}")
371
- await websocket.send_text(json.dumps({"event": "error", "error": str(e)}))
411
+ error_payload = {
412
+ "event": "error",
413
+ "error": str(e),
414
+ "error_type": e.type if hasattr(e, "type") else None,
415
+ "error_id": e.error_id if hasattr(e, "error_id") else None,
416
+ }
417
+ error_payload = {k: v for k, v in error_payload.items() if v is not None}
418
+ await websocket.send_text(json.dumps(error_payload))
372
419
 
373
420
 
374
421
  async def workflow_response_streamer(
@@ -391,12 +438,23 @@ async def workflow_response_streamer(
391
438
  async for run_response_chunk in run_response:
392
439
  yield format_sse_event(run_response_chunk) # type: ignore
393
440
 
441
+ except (InputCheckError, OutputCheckError) as e:
442
+ error_response = WorkflowErrorEvent(
443
+ error=str(e),
444
+ error_type=e.type,
445
+ error_id=e.error_id,
446
+ additional_data=e.additional_data,
447
+ )
448
+ yield format_sse_event(error_response)
449
+
394
450
  except Exception as e:
395
451
  import traceback
396
452
 
397
453
  traceback.print_exc()
398
454
  error_response = WorkflowErrorEvent(
399
455
  error=str(e),
456
+ error_type=e.type if hasattr(e, "type") else None,
457
+ error_id=e.error_id if hasattr(e, "error_id") else None,
400
458
  )
401
459
  yield format_sse_event(error_response)
402
460
  return
@@ -464,7 +522,7 @@ def get_websocket_router(
464
522
  await websocket.send_text(json.dumps({"event": "error", "error": f"Unknown action: {action}"}))
465
523
 
466
524
  except Exception as e:
467
- if "1012" not in str(e):
525
+ if "1012" not in str(e) and "1001" not in str(e):
468
526
  logger.error(f"WebSocket error: {e}")
469
527
  finally:
470
528
  # Clean up the websocket connection
@@ -520,7 +578,7 @@ def get_base_router(
520
578
  "content": {
521
579
  "application/json": {
522
580
  "example": {
523
- "os_id": "demo",
581
+ "id": "demo",
524
582
  "description": "Example AgentOS configuration",
525
583
  "available_models": [],
526
584
  "databases": ["9c884dc4-9066-448c-9074-ef49ec7eb73c"],
@@ -582,7 +640,7 @@ def get_base_router(
582
640
  )
583
641
  async def config() -> ConfigResponse:
584
642
  return ConfigResponse(
585
- os_id=os.os_id or "Unnamed OS",
643
+ os_id=os.id or "Unnamed OS",
586
644
  description=os.description,
587
645
  available_models=os.config.available_models if os.config else [],
588
646
  databases=[db.id for db in os.dbs.values()],
@@ -669,7 +727,7 @@ def get_base_router(
669
727
  "content": {
670
728
  "text/event-stream": {
671
729
  "examples": {
672
- "event_strea": {
730
+ "event_stream": {
673
731
  "summary": "Example event stream response",
674
732
  "value": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n',
675
733
  }
@@ -692,6 +750,25 @@ def get_base_router(
692
750
  ):
693
751
  kwargs = await _get_request_kwargs(request, create_agent_run)
694
752
 
753
+ if hasattr(request.state, "user_id"):
754
+ if user_id:
755
+ log_warning("User ID parameter passed in both request state and kwargs, using request state")
756
+ user_id = request.state.user_id
757
+ if hasattr(request.state, "session_id"):
758
+ if session_id:
759
+ log_warning("Session ID parameter passed in both request state and kwargs, using request state")
760
+ session_id = request.state.session_id
761
+ if hasattr(request.state, "session_state"):
762
+ session_state = request.state.session_state
763
+ if "session_state" in kwargs:
764
+ log_warning("Session state parameter passed in both request state and kwargs, using request state")
765
+ kwargs["session_state"] = session_state
766
+ if hasattr(request.state, "dependencies"):
767
+ dependencies = request.state.dependencies
768
+ if "dependencies" in kwargs:
769
+ log_warning("Dependencies parameter passed in both request state and kwargs, using request state")
770
+ kwargs["dependencies"] = dependencies
771
+
695
772
  agent = get_agent_by_id(agent_id, os.agents)
696
773
  if agent is None:
697
774
  raise HTTPException(status_code=404, detail="Agent not found")
@@ -774,21 +851,25 @@ def get_base_router(
774
851
  media_type="text/event-stream",
775
852
  )
776
853
  else:
777
- run_response = cast(
778
- RunOutput,
779
- await agent.arun(
780
- input=message,
781
- session_id=session_id,
782
- user_id=user_id,
783
- images=base64_images if base64_images else None,
784
- audio=base64_audios if base64_audios else None,
785
- videos=base64_videos if base64_videos else None,
786
- files=input_files if input_files else None,
787
- stream=False,
788
- **kwargs,
789
- ),
790
- )
791
- return run_response.to_dict()
854
+ try:
855
+ run_response = cast(
856
+ RunOutput,
857
+ await agent.arun(
858
+ input=message,
859
+ session_id=session_id,
860
+ user_id=user_id,
861
+ images=base64_images if base64_images else None,
862
+ audio=base64_audios if base64_audios else None,
863
+ videos=base64_videos if base64_videos else None,
864
+ files=input_files if input_files else None,
865
+ stream=False,
866
+ **kwargs,
867
+ ),
868
+ )
869
+ return run_response.to_dict()
870
+
871
+ except InputCheckError as e:
872
+ raise HTTPException(status_code=400, detail=str(e))
792
873
 
793
874
  @router.post(
794
875
  "/agents/{agent_id}/runs/{run_id}/cancel",
@@ -849,11 +930,17 @@ def get_base_router(
849
930
  async def continue_agent_run(
850
931
  agent_id: str,
851
932
  run_id: str,
933
+ request: Request,
852
934
  tools: str = Form(...), # JSON string of tools
853
935
  session_id: Optional[str] = Form(None),
854
936
  user_id: Optional[str] = Form(None),
855
937
  stream: bool = Form(True),
856
938
  ):
939
+ if hasattr(request.state, "user_id"):
940
+ user_id = request.state.user_id
941
+ if hasattr(request.state, "session_id"):
942
+ session_id = request.state.session_id
943
+
857
944
  # Parse the JSON string manually
858
945
  try:
859
946
  tools_data = json.loads(tools) if tools else None
@@ -891,17 +978,21 @@ def get_base_router(
891
978
  media_type="text/event-stream",
892
979
  )
893
980
  else:
894
- run_response_obj = cast(
895
- RunOutput,
896
- await agent.acontinue_run(
897
- run_id=run_id, # run_id from path
898
- updated_tools=updated_tools,
899
- session_id=session_id,
900
- user_id=user_id,
901
- stream=False,
902
- ),
903
- )
904
- return run_response_obj.to_dict()
981
+ try:
982
+ run_response_obj = cast(
983
+ RunOutput,
984
+ await agent.acontinue_run(
985
+ run_id=run_id, # run_id from path
986
+ updated_tools=updated_tools,
987
+ session_id=session_id,
988
+ user_id=user_id,
989
+ stream=False,
990
+ ),
991
+ )
992
+ return run_response_obj.to_dict()
993
+
994
+ except InputCheckError as e:
995
+ raise HTTPException(status_code=400, detail=str(e))
905
996
 
906
997
  @router.get(
907
998
  "/agents",
@@ -1041,6 +1132,25 @@ def get_base_router(
1041
1132
  ):
1042
1133
  kwargs = await _get_request_kwargs(request, create_team_run)
1043
1134
 
1135
+ if hasattr(request.state, "user_id"):
1136
+ if user_id:
1137
+ log_warning("User ID parameter passed in both request state and kwargs, using request state")
1138
+ user_id = request.state.user_id
1139
+ if hasattr(request.state, "session_id"):
1140
+ if session_id:
1141
+ log_warning("Session ID parameter passed in both request state and kwargs, using request state")
1142
+ session_id = request.state.session_id
1143
+ if hasattr(request.state, "session_state"):
1144
+ session_state = request.state.session_state
1145
+ if "session_state" in kwargs:
1146
+ log_warning("Session state parameter passed in both request state and kwargs, using request state")
1147
+ kwargs["session_state"] = session_state
1148
+ if hasattr(request.state, "dependencies"):
1149
+ dependencies = request.state.dependencies
1150
+ if "dependencies" in kwargs:
1151
+ log_warning("Dependencies parameter passed in both request state and kwargs, using request state")
1152
+ kwargs["dependencies"] = dependencies
1153
+
1044
1154
  logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
1045
1155
 
1046
1156
  team = get_team_by_id(team_id, os.teams)
@@ -1122,18 +1232,22 @@ def get_base_router(
1122
1232
  media_type="text/event-stream",
1123
1233
  )
1124
1234
  else:
1125
- run_response = await team.arun(
1126
- input=message,
1127
- session_id=session_id,
1128
- user_id=user_id,
1129
- images=base64_images if base64_images else None,
1130
- audio=base64_audios if base64_audios else None,
1131
- videos=base64_videos if base64_videos else None,
1132
- files=document_files if document_files else None,
1133
- stream=False,
1134
- **kwargs,
1135
- )
1136
- return run_response.to_dict()
1235
+ try:
1236
+ run_response = await team.arun(
1237
+ input=message,
1238
+ session_id=session_id,
1239
+ user_id=user_id,
1240
+ images=base64_images if base64_images else None,
1241
+ audio=base64_audios if base64_audios else None,
1242
+ videos=base64_videos if base64_videos else None,
1243
+ files=document_files if document_files else None,
1244
+ stream=False,
1245
+ **kwargs,
1246
+ )
1247
+ return run_response.to_dict()
1248
+
1249
+ except InputCheckError as e:
1250
+ raise HTTPException(status_code=400, detail=str(e))
1137
1251
 
1138
1252
  @router.post(
1139
1253
  "/teams/{team_id}/runs/{run_id}/cancel",
@@ -1464,6 +1578,25 @@ def get_base_router(
1464
1578
  ):
1465
1579
  kwargs = await _get_request_kwargs(request, create_workflow_run)
1466
1580
 
1581
+ if hasattr(request.state, "user_id"):
1582
+ if user_id:
1583
+ log_warning("User ID parameter passed in both request state and kwargs, using request state")
1584
+ user_id = request.state.user_id
1585
+ if hasattr(request.state, "session_id"):
1586
+ if session_id:
1587
+ log_warning("Session ID parameter passed in both request state and kwargs, using request state")
1588
+ session_id = request.state.session_id
1589
+ if hasattr(request.state, "session_state"):
1590
+ session_state = request.state.session_state
1591
+ if "session_state" in kwargs:
1592
+ log_warning("Session state parameter passed in both request state and kwargs, using request state")
1593
+ kwargs["session_state"] = session_state
1594
+ if hasattr(request.state, "dependencies"):
1595
+ dependencies = request.state.dependencies
1596
+ if "dependencies" in kwargs:
1597
+ log_warning("Dependencies parameter passed in both request state and kwargs, using request state")
1598
+ kwargs["dependencies"] = dependencies
1599
+
1467
1600
  # Retrieve the workflow by ID
1468
1601
  workflow = get_workflow_by_id(workflow_id, os.workflows)
1469
1602
  if workflow is None:
@@ -1497,6 +1630,9 @@ def get_base_router(
1497
1630
  **kwargs,
1498
1631
  )
1499
1632
  return run_response.to_dict()
1633
+
1634
+ except InputCheckError as e:
1635
+ raise HTTPException(status_code=400, detail=str(e))
1500
1636
  except Exception as e:
1501
1637
  # Handle unexpected runtime errors
1502
1638
  raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
agno/os/routers/home.py CHANGED
@@ -30,7 +30,7 @@ def get_home_router(os: "AgentOS") -> APIRouter:
30
30
  "value": {
31
31
  "name": "AgentOS API",
32
32
  "description": "AI Agent Operating System API",
33
- "os_id": "demo-os",
33
+ "id": "demo-os",
34
34
  "version": "1.0.0",
35
35
  },
36
36
  }
@@ -45,7 +45,7 @@ def get_home_router(os: "AgentOS") -> APIRouter:
45
45
  return {
46
46
  "name": "AgentOS API",
47
47
  "description": os.description or "AI Agent Operating System API",
48
- "os_id": os.os_id or "agno-agentos",
48
+ "id": os.id or "agno-agentos",
49
49
  "version": os.version or "1.0.0",
50
50
  }
51
51